Sự khác biệt giữa mới và ghi đè


198

Tự hỏi sự khác biệt giữa những điều sau đây là gì:

Trường hợp 1: Lớp cơ sở

public void DoIt();

Trường hợp 1: Lớp kế thừa

public new void DoIt();

Trường hợp 2: Lớp cơ sở

public virtual void DoIt();

Trường hợp 2: Lớp kế thừa

public override void DoIt();

Cả hai trường hợp 1 và 2 dường như có cùng hiệu quả dựa trên các thử nghiệm tôi đã chạy. Có một sự khác biệt, hoặc một cách ưa thích?


2
Bản sao của nhiều câu hỏi, bao gồm stackoverflow.com/questions/159978/ từ
Jon Skeet

Câu trả lời:


267

Công cụ sửa đổi ghi đè có thể được sử dụng trên các phương thức ảo và phải được sử dụng trên các phương thức trừu tượng. Điều này chỉ ra cho trình biên dịch sử dụng triển khai được xác định cuối cùng của một phương thức. Ngay cả khi phương thức được gọi trên một tham chiếu đến lớp cơ sở, nó sẽ sử dụng việc thực hiện ghi đè lên nó.

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

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

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

sẽ gọi Derived.DoItnếu ghi đè Base.DoIt.

Công cụ sửa đổi mới hướng dẫn trình biên dịch sử dụng triển khai lớp con của bạn thay vì thực hiện lớp cha. Bất kỳ mã nào không tham chiếu lớp của bạn nhưng lớp cha sẽ sử dụng triển khai lớp cha.

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

Đầu tiên sẽ gọi Base.DoIt, sau đóDerived.DoIt . Chúng thực sự là hai phương thức hoàn toàn riêng biệt có cùng tên, thay vì phương thức dẫn xuất ghi đè phương thức cơ sở.

Nguồn: blog của Microsoft


5
This indicates for the compiler to use the last defined implementation of a method. Làm thế nào có thể tìm thấy thực hiện định nghĩa cuối cùng của một phương pháp ??
AminM

5
Bắt đầu từ một lớp cụ thể, kiểm tra xem nó có thực hiện phương thức quan tâm không. Nếu có, bạn đã hoàn thành. Nếu không, hãy tiến lên một bước trong hệ thống phân cấp thừa kế, tức là kiểm tra xem siêu hạng có phương thức quan tâm hay không. Tiếp tục cho đến khi bạn đã tìm thấy phương pháp quan tâm.
csoltenborn

2
Cũng lưu ý rằng bạn chỉ có thể overridemột phương thức khi lớp cơ sở định nghĩa phương thức là virtual. Từ virtualnày là lớp cơ sở có nội dung "Này, khi tôi gọi phương thức này, nó gần như đã được thay thế bằng một triển khai có nguồn gốc, vì vậy tôi không thực sự biết trước việc triển khai phương thức nào tôi thực sự gọi trong thời gian chạy. Vì vậy, virtualbiểu thị là giữ chỗ cho một phương thức. Điều này ngụ ý rằng các phương thức không được đánh dấu là virtualkhông thể bị ghi đè. Nhưng bạn có thể thay thế bất kỳ phương thức không ảo nào trong lớp dẫn xuất bằng công cụ sửa đổi new, chỉ có thể truy cập ở mức dẫn xuất.
Erik Bongers

177

ảo : chỉ ra rằng một phương thức có thể được ghi đè bởi người thừa kế

ghi đè : ghi đè chức năng của phương thức ảo trong lớp cơ sở, cung cấp chức năng khác nhau.

mới : ẩn phương thức gốc (không phải là ảo), cung cấp các chức năng khác nhau. Điều này chỉ nên được sử dụng khi thực sự cần thiết.

Khi bạn ẩn một phương thức, bạn vẫn có thể truy cập phương thức ban đầu bằng cách truyền lên lớp cơ sở. Điều này hữu ích trong một số tình huống, nhưng nguy hiểm.


2
Tại sao lên đúc một phương thức ẩn phương thức cơ bản nguy hiểm? Hay bạn đang ám chỉ rằng việc đúc lên nói chung là nguy hiểm?
Đánh dấu

3
@Mark - một người gọi có thể không nhận thức được việc thực hiện, gây ra sự lạm dụng vô tình.
Jon B

Bạn có thể sử dụng overridevà / hoặc newkhông có virtualphương thức cha?
Aaron Franke

16

Trong trường hợp đầu tiên, bạn đang ẩn định nghĩa trong lớp cha. Điều này có nghĩa là nó sẽ chỉ được gọi khi bạn đang xử lý đối tượng là lớp con. Nếu bạn chuyển lớp thành kiểu cha của nó, phương thức của cha mẹ sẽ được gọi. Trong trường hợp thứ hai, phương thức bị ghi đè và sẽ được gọi bất kể đối tượng được chọn là lớp con hay lớp cha.


7

thử làm theo: (case1)

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

Chỉnh sửa: ghi đè ảo + được giải quyết trong thời gian chạy (vì vậy, ghi đè thực sự ghi đè các phương thức ảo), trong khi mới chỉ tạo phương thức mới có cùng tên và ẩn cái cũ, nó được giải quyết tại thời gian biên dịch -> trình biên dịch của bạn sẽ gọi phương thức đó ' nhìn'


3

Trong trường hợp 1 nếu bạn đã sử dụng hãy gọi phương thức DoIt () của lớp được kế thừa trong khi kiểu được khai báo là lớp cơ sở, bạn sẽ thấy hành động của lớp cơ sở chẵn.

/* 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();
}

Bạn có thể gửi cảnh báo hoặc lỗi bạn đang nhận được. Mã này hoạt động tốt khi tôi ban đầu đăng nó.
Matthew Whited

Tất cả điều này nên được dán trong lớp điểm nhập cảnh của bạn (Chương trình). Điều đó đã được gỡ bỏ để cho phép định dạng tốt hơn trên trang web này.
Matthew Whited

3

Sự khác biệt giữa hai trường hợp là trong trường hợp 1, DoItphương thức cơ sở không bị ghi đè, chỉ bị ẩn. Điều này có nghĩa là tùy thuộc vào loại biến phụ thuộc vào phương thức nào sẽ được gọi. Ví dụ:

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

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

Điều này có thể thực sự khó hiểu và dẫn đến hành vi không mong đợi và nên tránh nếu có thể. Vì vậy, cách ưa thích sẽ là trường hợp 2.


3
  • newcó nghĩa là tôn trọng loại TÀI LIỆU THAM KHẢO của bạn (phía bên trái =), do đó chạy phương thức của loại tham chiếu. Nếu phương thức được xác định lại không có newtừ khóa, thì nó được xử lý như nó có. Hơn nữa, nó còn được gọi là di truyền không đa hình . Đó là, tôi đang tạo ra một phương thức hoàn toàn mới trong lớp dẫn xuất hoàn toàn không liên quan gì đến bất kỳ phương thức nào có cùng tên trong lớp cơ sở. - bởi Whitaker nói
  • override, phải được sử dụng với virtual từ khóa trong lớp cơ sở của nó, có nghĩa là tôn trọng loại ĐỐI TƯỢNG của bạn (phía bên phải =), do đó chạy phương thức ghi đè lên bất kể loại tham chiếu nào. Hơn nữa, nó còn được gọi là di truyền đa hình .

Cách của tôi để ghi nhớ cả hai từ khóa mà chúng trái ngược nhau.

override: virtualtừ khóa phải được xác định để ghi đè phương thức. Phương thức sử dụng overridetừ khóa bất kể loại tham chiếu (tham chiếu của lớp cơ sở hoặc lớp dẫn xuất) nếu nó được khởi tạo với lớp cơ sở, phương thức của lớp cơ sở chạy. Mặt khác, phương thức của lớp dẫn xuất chạy.

new: nếu từ khóa được sử dụng bởi một phương thức, không giống như override từ khóa, loại tham chiếu rất quan trọng. Nếu nó được khởi tạo với lớp dẫn xuất và kiểu tham chiếu là lớp cơ sở, phương thức của lớp cơ sở chạy. Nếu nó được khởi tạo với lớp dẫn xuất và kiểu tham chiếu là lớp dẫn xuất, phương thức của lớp dẫn xuất chạy. Cụ thể, nó là sự tương phản của overridetừ khóa. En passant, nếu bạn quên hoặc bỏ qua để thêm từ khóa mới vào phương thức, trình biên dịch sẽ hoạt động theo mặc định khi newtừ khóa được sử dụng.

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";
    }
}

Gọi chính:

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());

Đầu ra:

A
B
B
base test
derived test

Ví dụ mã mới,

Chơi với mã bằng cách bình luận từng cái một.

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

Nếu từ khóa override được sử dụng trong lớp dẫn xuất thì nó sẽ ghi đè phương thức cha.

Nếu Keyword newđược sử dụng trong lớp dẫn xuất thì phương thức phái sinh được ẩn bởi phương thức cha.


1

Tôi có cùng một câu hỏi và nó thực sự khó hiểu, bạn nên xem xét rằng ghi đè và từ khóa mới chỉ hoạt động với các đối tượng của lớp cơ sở loại và giá trị của lớp dẫn xuất. Trong trường hợp này, chỉ bạn sẽ thấy hiệu ứng ghi đè và mới: Vì vậy, nếu bạn có class AB, Bthừa hưởng từ A, thì bạn khởi tạo một đối tượng như thế này:

A a = new B();

Bây giờ về phương thức gọi sẽ xem xét trạng thái của nó. Ghi đè : có nghĩa là nó mở rộng chức năng của phương thức, sau đó nó sử dụng phương thức trong lớp dẫn xuất, trong khi đó mới nói với trình biên dịch để ẩn phương thức trong lớp dẫn xuất và thay vào đó sử dụng phương thức trong lớp cơ sở. Đây là một cảnh rất tốt cho chủ đề đó:

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


1

Bài viết dưới đây là trong vb.net nhưng tôi nghĩ rằng lời giải thích về ghi đè mới rất dễ nắm bắt.

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

Tại một số điểm trong bài viết, có câu này:

Nói chung, Shadows giả định chức năng liên quan đến loại được gọi, trong khi Overrides giả định việc thực hiện đối tượng được thực thi.

Câu trả lời được chấp nhận cho câu hỏi này là hoàn hảo nhưng tôi nghĩ bài viết này cung cấp các ví dụ tốt để thêm ý nghĩa tốt hơn về sự khác biệt giữa hai từ khóa này.


1

Trong số tất cả, mới là khó hiểu nhất. Thông qua thử nghiệm, từ khóa mới giống như cung cấp cho các nhà phát triển tùy chọn ghi đè triển khai lớp kế thừa với triển khai lớp cơ sở bằng cách xác định rõ ràng loại. Nó giống như suy nghĩ theo cách khác.

Trong ví dụ dưới đây, kết quả sẽ trả về "Kết quả đã tạo" cho đến khi loại được xác định rõ ràng là kiểm tra BaseClass, chỉ sau đó "Kết quả cơ sở" sẽ được trả về.

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
Thêm bình luận của bạn nếu bạn phản đối. Đánh và chạy là rất hèn.
hữu íchBee

0

Sự khác biệt về chức năng sẽ không được hiển thị trong các thử nghiệm này:

BaseClass bc = new BaseClass();

bc.DoIt();

DerivedClass dc = new DerivedClass();

dc.ShowIt();

Trong ví dụ này, Doit được gọi là cái mà bạn mong đợi được gọi.

Để thấy sự khác biệt, bạn phải làm điều này:

BaseClass obj = new DerivedClass();

obj.DoIt();

Bạn sẽ thấy nếu bạn chạy thử nghiệm đó trong trường hợp 1 (như bạn đã xác định), thì DoIt()in BaseClassđược gọi, trong trường hợp 2 (như bạn đã xác định), DoIt()in DerivedClassđược gọi.


-1

Trong trường hợp đầu tiên, nó sẽ gọi phương thức DoIt () dẫn xuất vì từ khóa mới ẩn phương thức DoIt () của lớp cơ sở.

Trong trường hợp thứ hai, nó sẽ gọi overriden 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()");
    }
}

hãy tạo cá thể của các lớp này

   A instanceA = new A();

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

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

Tất cả mọi thứ được mong đợi ở trên. Hãy đặt instanceB và instanceC thành instanceA và gọi phương thức DoIt () và kiểm tra kết quả.

    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

dụC.DoIt (); sẽ cung cấp cho bạn C :: DoIt (), không phải B :: DoIt ()
BYS2

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.