ความแตกต่างระหว่างใหม่และแทนที่


200

สงสัยว่าความแตกต่างระหว่างสิ่งต่อไปนี้:

กรณีที่ 1: คลาสพื้นฐาน

public void DoIt();

กรณีที่ 1: คลาสที่รับมา

public new void DoIt();

กรณีที่ 2: คลาสฐาน

public virtual void DoIt();

กรณีที่ 2: คลาสที่รับมา

public override void DoIt();

ทั้งกรณีที่ 1 และ 2 ดูเหมือนว่าจะมีผลเหมือนกันตามการทดสอบที่ฉันใช้ มีความแตกต่างหรือวิธีที่ต้องการหรือไม่?


2
ซ้ำคำถามจำนวนมากรวมทั้งstackoverflow.com/questions/159978/...
จอนสกีต

คำตอบ:


269

ตัวแก้ไขการแทนที่อาจใช้กับวิธีเสมือนและต้องใช้กับวิธีการแบบนามธรรม สิ่งนี้บ่งชี้ว่าคอมไพเลอร์จะใช้การใช้งานที่กำหนดไว้ล่าสุดของวิธีการ แม้ว่าวิธีการที่เรียกว่าในการอ้างอิงถึงระดับฐานนั้นจะใช้การดำเนินการแทนที่มัน

public class Base
{
    public virtual void DoIt()
    {
    }
}

public class Derived : Base
{
    public override void DoIt()
    {
    }
}

Base b = new Derived();
b.DoIt();                      // Calls Derived.DoIt

จะเรียกDerived.DoItถ้าการแทนที่Base.DoItนั้น

โมดิฟายเออร์ใหม่สั่งให้คอมไพเลอร์ใช้การใช้คลาสลูกของคุณแทนการใช้คลาสแม่ โค้ดใด ๆ ที่ไม่ได้อ้างอิงคลาสของคุณ แต่คลาสพาเรนต์จะใช้การใช้คลาสพาเรนต์

public class Base
{
    public virtual void DoIt()
    {
    }
}

public class Derived : Base
{
    public new void DoIt()
    {
    }
}

Base b = new Derived();
Derived d = new Derived();

b.DoIt();                      // Calls Base.DoIt
d.DoIt();                      // Calls Derived.DoIt

จะโทรก่อนBase.DoItแล้วDerived.DoItแล้วพวกมันมีประสิทธิภาพสองวิธีที่แยกกันอย่างสิ้นเชิงซึ่งมีชื่อเดียวกันมากกว่าวิธีที่ได้มาแทนที่วิธีการพื้นฐาน

ที่มา: บล็อก Microsoft


5
This indicates for the compiler to use the last defined implementation of a method. วิธีการหาวิธีการใช้งานที่กำหนดไว้ล่าสุดของวิธี?
AminM

5
เริ่มต้นจากคลาสที่เป็นรูปธรรมตรวจสอบว่ามีการใช้วิธีการที่น่าสนใจ ถ้าเป็นเช่นนั้นคุณก็ทำเสร็จแล้ว หากไม่มีให้ทำขั้นตอนเดียวในลำดับชั้นการสืบทอดคือตรวจสอบว่าคลาสซูเปอร์มีวิธีการที่น่าสนใจหรือไม่ ทำต่อไปจนกว่าคุณจะพบวิธีการที่น่าสนใจ
csoltenborn

2
นอกจากนี้ยังทราบว่าคุณสามารถเพียงวิธีการที่เมื่อชั้นฐานกำหนดวิธีการที่เป็นoverride virtualคำว่าvirtualคลาสพื้นฐานบอกว่า "เฮ้เมื่อฉันเรียกวิธีนี้มันอาจถูกแทนที่ด้วยการนำมาใช้จริงดังนั้นฉันไม่รู้จริงล่วงหน้าว่าการใช้วิธีใดที่ฉันเรียกใช้งานจริงในขณะvirtualนี้ ตัวยึดตำแหน่งสำหรับวิธีการนี้หมายความว่าวิธีการที่ไม่ได้ถูกทำเครื่องหมายว่าvirtualไม่สามารถเขียนทับได้ แต่คุณสามารถแทนที่วิธีการที่ไม่ใช่แบบเสมือนในคลาสที่ได้รับด้วยตัวดัดแปลงnewซึ่งสามารถเข้าถึงได้ในระดับที่ได้รับเท่านั้น
Erik Bongers

177

เสมือน : ระบุว่าวิธีการอาจถูกแทนที่โดยผู้สืบทอด

override : แทนที่ฟังก์ชันของเมธอดเสมือนในคลาสพื้นฐานซึ่งมีฟังก์ชันการทำงานที่แตกต่างกัน

ใหม่ : ซ่อนวิธีดั้งเดิม (ซึ่งไม่จำเป็นต้องเป็นเสมือนจริง) ซึ่งมีฟังก์ชันการทำงานที่แตกต่างกัน ควรใช้เฉพาะเมื่อจำเป็นเท่านั้น

เมื่อคุณซ่อนเมธอดคุณยังสามารถเข้าถึงวิธีดั้งเดิมได้โดยการแคสต์ไปที่คลาสพื้นฐาน สิ่งนี้มีประโยชน์ในบางสถานการณ์ แต่เป็นอันตราย


2
ทำไมการร่ายเมธอดถึงวิธีที่ซ่อนเมธอดอันตรายไว้ หรือคุณกำลังบอกว่าการหล่อโดยทั่วไปเป็นอันตรายหรือไม่?
ทำเครื่องหมาย

3
@ Mark - ผู้โทรอาจไม่ได้ตระหนักถึงการใช้งานทำให้เกิดการใช้ผิดวัตถุประสงค์โดยไม่ได้ตั้งใจ
Jon B

คุณสามารถใช้overrideและ / หรือnewไม่มีvirtualวิธีการหลักได้หรือไม่?
Aaron Franke

16

ในกรณีแรกคุณจะซ่อนคำจำกัดความในคลาสพาเรนต์ ซึ่งหมายความว่ามันจะถูกเรียกใช้เฉพาะเมื่อคุณจัดการกับวัตถุเป็นคลาสย่อย หากคุณส่งคลาสเป็นประเภทพาเรนต์เมธอดของพาเรนต์จะถูกเรียกใช้ ในอินสแตนซ์ที่สองเมธอดจะถูกเขียนทับและจะถูกเรียกใช้โดยไม่คำนึงว่าวัตถุนั้นถูกโยนเป็นคลาสย่อยหรือคลาสหลักหรือไม่


7

ลองทำดังนี้: (case1)

((BaseClass)(new InheritedClass())).DoIt()

แก้ไข: virtual + override ได้รับการแก้ไขที่ runtime (ดังนั้นแทนที่ override วิธีเสมือนจริงแทน) ในขณะที่ใหม่เพียงสร้างเมธอดใหม่ที่มีชื่อเดียวกันและซ่อนเก่ามันจะถูกแก้ไขในเวลาคอมไพล์ -> คอมไพเลอร์ของคุณจะเรียกเมธอด เห็น


3

ในกรณีที่ 1 ถ้าคุณใช้การเรียก DoIt () วิธีการของคลาสที่สืบทอดในขณะที่ประกาศประเภทเป็นคลาสฐานที่คุณจะเห็นการกระทำของชั้นฐานแม้

/* Results
Class1
Base1
Class2
Class2
*/
public abstract class Base1
{
    public void DoIt() { Console.WriteLine("Base1"); }
}
public  class Class1 : Base1 
{
    public new void DoIt() { Console.WriteLine("Class1"); }
}
public abstract class Base2
{
    public virtual void DoIt() { Console.WriteLine("Base2"); }
}
public class Class2 : Base2
{
    public override void DoIt() { Console.WriteLine("Class2"); }
}
static void Main(string[] args)
{
    var c1 = new Class1();
    c1.DoIt();
    ((Base1)c1).DoIt();

    var c2 = new Class2();
    c2.DoIt();
    ((Base2)c2).DoIt();
    Console.Read();
}

คุณสามารถโพสต์คำเตือนหรือข้อผิดพลาดที่คุณได้รับ รหัสนี้ทำงานได้ดีเมื่อฉันโพสต์มัน
Matthew Whited

ทั้งหมดนี้ควรถูกวางไว้ในคลาสจุดเริ่มต้นของคุณ (โปรแกรม) ที่ถูกลบเพื่อให้การจัดรูปแบบที่ดีขึ้นในเว็บไซต์นี้
Matthew Whited

3

ความแตกต่างระหว่างสองกรณีคือในกรณีที่ 1 DoItวิธีฐานไม่ได้ถูกแทนที่ถูกซ่อนเพียง สิ่งนี้หมายความว่าขึ้นอยู่กับชนิดของตัวแปรขึ้นอยู่กับวิธีที่จะได้รับเรียก ตัวอย่างเช่น:

BaseClass instance1 = new SubClass();
instance1.DoIt(); // Calls base class DoIt method

SubClass instance2 = new SubClass();
instance2.DoIt(); // Calls sub class DoIt method

สิ่งนี้อาจสร้างความสับสนและทำให้เกิดพฤติกรรมที่ไม่คาดคิดและควรหลีกเลี่ยงหากเป็นไปได้ ดังนั้นวิธีที่ต้องการคือกรณีที่ 2


3
  • newหมายถึงเคารพประเภทการอ้างอิงของคุณ (ด้านซ้ายมือ=) ดังนั้นจึงใช้วิธีการของประเภทการอ้างอิง หากวิธีการที่นิยามใหม่ไม่มีnewคำหลักมันจะทำงานเหมือนที่มันเป็น นอกจากนี้ยังเป็นที่รู้จักกันเป็นมรดกที่ไม่ใช่ polymorphic นั่นคือ“ ฉันกำลังสร้างวิธีการใหม่เอี่ยมในคลาสที่ได้มาซึ่งไม่มีอะไรเกี่ยวข้องกับวิธีการใด ๆ ด้วยชื่อเดียวกันในคลาสพื้นฐาน” - โดยวิเทเกอร์กล่าว
  • overrideซึ่งจะต้องใช้กับvirtualคำหลักในระดับฐานของมันหมายถึงเคารพประเภท OBJECT ของคุณ (ด้านขวามือของ=) ซึ่งจะทำให้วิธีการทำงานลดลงโดยไม่คำนึงถึงประเภทการอ้างอิง นอกจากนี้ยังเป็นที่รู้จักมรดก polymorphic

วิธีที่ฉันควรคำนึงถึงคำหลักทั้งสองที่อยู่ตรงข้ามกัน

override: virtualคำหลักจะต้องกำหนดเพื่อแทนที่วิธี วิธีการใช้overrideคำสำคัญที่ไม่คำนึงถึงประเภทการอ้างอิง (การอ้างอิงของคลาสฐานหรือคลาสที่ได้รับ) หากมีการสร้างอินสแตนซ์ของคลาสพื้นฐานเมธอดของคลาสพื้นฐานจะทำงาน มิฉะนั้นวิธีการเรียนที่ได้รับการทำงาน

new: หากวิธีการใช้overrideคำหลักซึ่งแตกต่างจากคำหลักประเภทการอ้างอิงเป็นสิ่งสำคัญ หากมีการสร้างอินสแตนซ์ของคลาสที่ได้รับและประเภทการอ้างอิงคือคลาสพื้นฐานเมธอดของคลาสพื้นฐานจะทำงาน หากมีการสร้างอินสแตนซ์ของคลาสที่ได้รับและประเภทการอ้างอิงคือคลาสที่ได้รับเมธอดของคลาสที่ได้รับมาจะทำงาน กล่าวคือมันต่างจากoverrideคำหลัก ถ้าคุณลืมหรือไม่ใส่คำหลักใหม่ลงในเมธอดคอมไพเลอร์จะทำงานตามค่าเริ่มต้นเมื่อใช้newคำหลัก

class A 
{
    public string Foo() 
    {
        return "A";
    }

    public virtual string Test()
    {
        return "base test";
    }
}

class B: A
{
    public new string Foo() 
    {
        return "B";
    }
}

class C: B 
{
    public string Foo() 
    {
        return "C";
    }

    public override string Test() {
        return "derived test";
    }
}

โทรเข้าหลัก:

A AClass = new B();
Console.WriteLine(AClass.Foo());
B BClass = new B();
Console.WriteLine(BClass.Foo());
B BClassWithC = new C();
Console.WriteLine(BClassWithC.Foo());

Console.WriteLine(AClass.Test());
Console.WriteLine(BClassWithC.Test());

เอาท์พุท:

A
B
B
base test
derived test

ตัวอย่างรหัสใหม่

เล่นกับรหัสโดยแสดงความคิดเห็นในแบบหนึ่งต่อหนึ่ง

class X
{
    protected internal /*virtual*/ void Method()
    {
        WriteLine("X");
    }
}
class Y : X
{
    protected internal /*override*/ void Method()
    {
        base.Method();
        WriteLine("Y");
    }
}
class Z : Y
{
    protected internal /*override*/ void Method()
    {
        base.Method();
        WriteLine("Z");
    }
}

class Programxyz
{
    private static void Main(string[] args)
    {
        X v = new Z();
        //Y v = new Z();
        //Z v = new Z();
        v.Method();
}

1

หากใช้คีย์เวิร์ดoverrideในคลาสที่ได้รับดังนั้นจะแทนที่เมธอด parent

หากใช้คำหลักnewในคลาสสืบทอดมาจากนั้นวิธีสืบทอดมาจะถูกซ่อนโดยวิธีหลัก


1

ฉันมีคำถามเดียวกันและมันสับสนจริง ๆ คุณควรพิจารณาว่าการแทนที่และคำหลักใหม่ทำงานเฉพาะกับออบเจ็กต์ประเภทคลาสพื้นฐานและค่าของคลาสที่ได้รับ ในกรณีนี้เพียงคุณเท่านั้นที่จะเห็นผลของการแทนที่และใหม่นี้ดังนั้นถ้าคุณมีclass AและB, BสืบทอดจากAนั้นคุณ instantiate วัตถุเช่นนี้

A a = new B();

ตอนนี้วิธีการโทรจะคำนึงถึงรัฐด้วย Override : หมายความว่ามันขยายการทำงานของวิธีการจากนั้นจะใช้วิธีการในชั้นเรียนที่ได้รับในขณะที่ใหม่บอกคอมไพเลอร์ที่จะซ่อนวิธีการในชั้นเรียนที่ได้รับและใช้วิธีการในชั้นฐานแทน นี่เป็นภาพที่ดีมากสำหรับเรื่องนั้น:

https://msdn.microsoft.com/EN-US/library/ms173153%28v=VS.140,d=hv.2%29.aspx?f=255&MSPPError=-2147217396


1

บทความด้านล่างนี้อยู่ใน vb.net แต่ฉันคิดว่าคำอธิบายเกี่ยวกับการแทนที่ vs ใหม่นั้นง่ายต่อการเข้าใจ

https://www.codeproject.com/articles/17477/the-dark-shadow-of-overrides

ในบางจุดของบทความมีประโยคนี้:

โดยทั่วไปแล้ว Shadows จะถือว่าฟังก์ชันที่เกี่ยวข้องกับชนิดนั้นถูกเรียกใช้ในขณะที่การแทนที่จะถือว่ามีการใช้งานวัตถุ

คำตอบที่ยอมรับสำหรับคำถามนี้สมบูรณ์แบบ แต่ฉันคิดว่าบทความนี้มีตัวอย่างที่ดีเพื่อเพิ่มความหมายที่ดีขึ้นเกี่ยวกับความแตกต่างระหว่างคำหลักสองคำนี้


1

ออกจากทั้งหมดเหล่านั้นใหม่คือสิ่งที่สับสนมากที่สุด ผ่านการทดสอบคำหลักใหม่ก็เหมือนกับให้นักพัฒนามีตัวเลือกเพื่อแทนที่การใช้งานคลาสที่สืบทอดมาพร้อมกับการใช้งานคลาสฐานโดยการกำหนดประเภทอย่างชัดเจน มันก็เหมือนกับคิดวิธีอื่น ๆ

ในตัวอย่างด้านล่างผลลัพธ์จะส่งกลับ "ผลลัพธ์ที่ได้รับ" จนกว่าจะมีการกำหนดประเภทอย่างชัดเจนว่าเป็นการทดสอบ BaseClass จากนั้นจะส่งคืน "ผลลัพธ์พื้นฐาน" เท่านั้น

class Program
{
    static void Main(string[] args)
    {
        var test = new DerivedClass();
        var result = test.DoSomething();
    }
}

class BaseClass
{
    public virtual string DoSomething()
    {
        return "Base result";
    }
}

class DerivedClass : BaseClass
{
    public new string DoSomething()
    {
        return "Derived result";
    }
}

3
เพิ่มความคิดเห็นของคุณหากคุณคัดค้าน ตีแล้ววิ่งก็ขี้ขลาด
UsefulBee

0

ความแตกต่างการทำงานจะไม่แสดงในการทดสอบเหล่านี้:

BaseClass bc = new BaseClass();

bc.DoIt();

DerivedClass dc = new DerivedClass();

dc.ShowIt();

ในตัวอย่างนี้ Doit ที่ถูกเรียกคือสิ่งที่คุณคาดว่าจะได้รับการเรียก

เพื่อที่จะเห็นความแตกต่างคุณต้องทำสิ่งนี้:

BaseClass obj = new DerivedClass();

obj.DoIt();

คุณจะดูว่าคุณเรียกว่าการทดสอบว่าในกรณีที่ 1 (ตามที่คุณกำหนดได้) ที่DoIt()อยู่ในBaseClassที่เรียกว่าในกรณีที่ 2 (ตามที่คุณกำหนดได้) ที่DoIt()อยู่ในDerivedClassที่เรียกว่า


-1

ในกรณีแรกมันจะเรียกคลาส DoIt () method ที่ได้รับเพราะคำหลักใหม่ซ่อนเมธอด DoIt () คลาสพื้นฐาน

ในกรณีที่สองจะเรียก OverIten DoIt ()

  public class A
{
    public virtual void DoIt()
    {
        Console.WriteLine("A::DoIt()");
    }
}

public class B : A
{
    new public void DoIt()
    {
        Console.WriteLine("B::DoIt()");
    }
}

public class C : A
{
    public override void DoIt()
    {
        Console.WriteLine("C::DoIt()");
    }
}

ให้สร้างตัวอย่างของคลาสเหล่านี้

   A instanceA = new A();

    B instanceB = new B();
    C instanceC = new C();

    instanceA.DoIt(); //A::DoIt()
    instanceB.DoIt(); //B::DoIt()
    instanceC.DoIt(); //B::DoIt()

ทุกอย่างถูกคาดหวังไว้ที่ด้านบน ให้ตั้งค่า instanceB และ instanceC เป็น instanceA และเรียกใช้ DoIt () method และตรวจสอบผลลัพธ์

    instanceA = instanceB;
    instanceA.DoIt(); //A::DoIt() calls DoIt method in class A

    instanceA = instanceC;
    instanceA.DoIt();//C::DoIt() calls DoIt method in class C because it was overriden in class C

instanceC.DoIt (); จะให้คุณ C :: DoIt () ไม่ใช่ B :: DoIt ()
BYS2

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.