C # - Sử dụng từ khóa ảo + ghi đè so với mới


204

Sự khác biệt giữa khai báo một phương thức trong loại cơ sở " virtual" và sau đó ghi đè nó trong loại con bằng cách sử dụng overridetừ khóa "" trái ngược với chỉ sử dụng newtừ khóa "" khi khai báo phương thức khớp trong loại con?


3
MSDN cho biết "Sử dụng newtạo một thành viên mới có cùng tên và khiến thành viên ban đầu bị ẩn, đồng thời overridemở rộng việc triển khai cho thành viên được kế thừa"
AminM


Câu trả lời:


181

Từ khóa "mới" không ghi đè, nó biểu thị một phương thức mới không liên quan gì đến phương thức lớp cơ sở.

public class Foo
{
     public bool DoSomething() { return false; }
}

public class Bar : Foo
{
     public new bool DoSomething() { return true; }
}

public class Test
{
    public static void Main ()
    {
        Foo test = new Bar ();
        Console.WriteLine (test.DoSomething ());
    }
}

Điều này in sai, nếu bạn sử dụng ghi đè thì nó sẽ được in đúng.

(Mã cơ sở lấy từ Joseph Daigle)

Vì vậy, nếu bạn đang thực hiện đa hình thực sự, bạn NÊN LUÔN LUÔN . Nơi duy nhất mà bạn cần sử dụng "mới" là khi phương thức không liên quan theo bất kỳ cách nào đến phiên bản lớp cơ sở.


Bạn nên sửa lại mã của mình, tôi đã làm cho các phương thức tĩnh (có giá trị sử dụng từ khóa "mới"). Nhưng tôi quyết định sử dụng các phương thức cá thể rõ ràng hơn.
Joseph Daigle

1
Cảm ơn bạn, tôi đã bỏ lỡ phần "tĩnh". Cần chú ý nhiều hơn về tương lai
albertein

1
Lưu ý rằng dòng Foo test = new Bar () rất quan trọng ở đây, từ khóa mới / ghi đè cho metods xác định metod nào được gọi khi bạn đặt Bar vào biến Foo.
Thomas N

... vậy nó giống hệt như đơn giản là khôngvirtualtrong cơ sở và overridecó nguồn gốc? Tại sao nó tồn tại? Mã vẫn sẽ chạy ngay cả w / o new- vì vậy nó hoàn toàn chỉ là khả năng đọc?
Don Cheadle

Câu trả lời của bạn thưa ông là khá mơ hồ. ghi đè mới và ảo thực hiện cùng một thứ, sự khác biệt duy nhất là HIDES phương thức mới trong lớp cha và ghi đè .. tốt, ghi đè lên nó. Tất nhiên nó in sai vì đối tượng thử nghiệm của bạn thuộc loại Foo, nếu nó là loại Bar, nó sẽ in đúng. Ví dụ khá khó khăn.
MrSilent

228

Tôi luôn thấy những thứ như thế này dễ hiểu hơn với hình ảnh:

Một lần nữa, lấy mã của joseph daigle,

public class Foo
{
     public /*virtual*/ bool DoSomething() { return false; }
}

public class Bar : Foo
{
     public /*override or new*/ bool DoSomething() { return true; }
}

Nếu sau đó bạn gọi mã như thế này:

Foo a = new Bar();
a.DoSomething();

LƯU Ý: Điều quan trọng là đối tượng của chúng tôi thực sự là một Bar, nhưng chúng tôi đang lưu trữ nó trong một biến kiểuFoo (điều này tương tự như việc đúc nó)

Sau đó, kết quả sẽ như sau, tùy thuộc vào việc bạn đã sử dụng virtual/ overridehoặc newkhi khai báo các lớp của bạn.

Hình ảnh giải thích ảo / ghi đè


3
Cảm ơn .... nhưng bạn có thể vui lòng giải thích một chút về hình ảnh ở trên liên quan đến buổi casting bạn nói không?
odiseh

Rất tiếc Nếu quên thêm vài dòng này vào trước. bình luận: bạn có nghĩa là ảo / ghi đè và không có sức sống / mới chỉ được sử dụng cho khái niệm đa hình và khi bạn chỉ đơn giản khai báo một biến (không sử dụng truyền) thì chúng không có nghĩa? Cảm ơn một lần nữa.
odiseh

Câu trả lời này chính xác là những gì tôi đang tìm kiếm. Vấn đề duy nhất tôi gặp phải là hình ảnh flickr bị chặn tại nơi làm việc của tôi vì vậy tôi phải truy cập nó qua điện thoại của mình ...
mezoid

7
Nỗ lực thực sự để trả lời câu hỏi.
iMatoria

Tôi sẽ upvote nếu bạn có thể sửa hình ảnh? Nó bị chặn đằng sau tường lửa corp ...
Jeremy Thompson

43

Đây là một số mã để hiểu sự khác biệt trong hành vi của các phương thức ảo và không ảo:

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

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

class Program
{
    static int Main(string[] args)
    {
        B b = new B();
        A a = b;
        a.foo(); // Prints A::foo
        b.foo(); // Prints B::foo
        a.bar(); // Prints B::bar
        b.bar(); // Prints B::bar
        return 0;
    }
}

cảm ơn bạn vì điều này - nhưng tại sao lại sử dụng newđể "ẩn" phương thức cơ sở, khi đơn giản là không sử dụng ghi đè dường như làm tương tự?
Don Cheadle

1
Tôi không nghĩ rằng nó được tạo ra để ngăn lớp cơ sở bị quá tải. Tôi nghĩ rằng nó được tạo ra để tránh xung đột tên vì bạn không thể ghi đè một phương thức không phải virtualvà trình biên dịch sẽ phàn nàn nếu nó nhìn thấy cùng tên hàm trên một "dòng máu" không có virtualchữ ký
mr5

19

Các newtừ khóa thực sự tạo ra một thành viên hoàn toàn mới mà chỉ tồn tại trên loại cụ thể.

Ví dụ

public class Foo
{
     public bool DoSomething() { return false; }
}

public class Bar : Foo
{
     public new bool DoSomething() { return true; }
}

Phương pháp tồn tại trên cả hai loại. Khi bạn sử dụng sự phản chiếu và nhận được các thành viên của loại Bar, bạn thực sự sẽ tìm thấy 2 phương thức được gọi là DoSomething()trông giống hệt nhau. Bằng cách sử dụng, newbạn có hiệu quả che giấu việc thực hiện trong lớp cơ sở, để khi các lớp xuất phát từ Bar(trong ví dụ của tôi), phương thức gọi base.DoSomething()đến Barvà không Foo.


9

virtual / override cho trình biên dịch biết rằng hai phương thức này có liên quan với nhau và trong một số trường hợp khi bạn nghĩ rằng bạn đang gọi phương thức (ảo) đầu tiên thì thực sự đúng khi gọi phương thức thứ hai (ghi đè). Đây là nền tảng của đa hình.

(new SubClass() as BaseClass).VirtualFoo()

Sẽ gọi phương thức VirtualFoo () của SubClass.

new nói với trình biên dịch rằng bạn đang thêm một phương thức vào một lớp dẫn xuất có cùng tên với một phương thức trong lớp cơ sở, nhưng chúng không có mối quan hệ nào với nhau.

(new SubClass() as BaseClass).NewBar()

Sẽ gọi phương thức NewBar () của BaseClass, trong khi:

(new SubClass()).NewBar()

Sẽ gọi phương thức NewBar () của SubClass.


thực sự thích câu này "nói với trình biên dịch"
Mina Gabriel

"nền tảng của đa hình" là một câu nói kinh điển khác :)
Jeremy Thompson

8

Ngoài các chi tiết kỹ thuật, tôi nghĩ rằng việc sử dụng ảo / ghi đè truyền đạt rất nhiều thông tin ngữ nghĩa trên thiết kế. Khi bạn khai báo một phương thức ảo, bạn chỉ ra rằng bạn mong đợi rằng các lớp triển khai có thể muốn cung cấp các triển khai không mặc định của riêng chúng. Bỏ qua điều này trong một lớp cơ sở, tương tự, tuyên bố kỳ vọng rằng phương thức mặc định phải đủ cho tất cả các lớp thực hiện. Tương tự, người ta có thể sử dụng các khai báo trừu tượng để buộc các lớp thực hiện cung cấp việc thực hiện riêng của chúng. Một lần nữa, tôi nghĩ rằng điều này truyền đạt rất nhiều về cách lập trình viên mong đợi mã được sử dụng. Nếu tôi đang viết cả lớp cơ sở và lớp thực hiện và thấy mình đang sử dụng mới, tôi sẽ suy nghĩ lại một cách nghiêm túc về quyết định không biến phương thức này thành cha mẹ và tuyên bố cụ thể ý định của tôi.


Các giải thích kỹ thuật của ghi đè mới so với ghi đè là âm thanh, nhưng câu trả lời này dường như giúp ích nhiều nhất cho các nhà phát triển hướng dẫn sử dụng.
Sully

4

Sự khác biệt giữa từ khóa ghi đè và từ khóa mới là phương thức trước ghi đè phương thức và phương thức ẩn sau.

Kiểm tra các liên kết theo dõi để biết thêm thông tin ...

MSDNkhác


3
  • newtừ khóa là để ẩn. - có nghĩa là bạn đang ẩn phương thức của mình trong thời gian chạy. Đầu ra sẽ dựa trên phương thức lớp cơ sở.
  • overridecho ghi đè. - có nghĩa là bạn đang gọi phương thức lớp dẫn xuất của bạn với tham chiếu của lớp cơ sở. Đầu ra sẽ dựa trên phương thức lớp dẫn xuất.

1

Phiên bản giải thích của tôi đến từ việc sử dụng các thuộc tính để giúp hiểu được sự khác biệt.

overridelà đủ đơn giản, phải không? Kiểu cơ bản ghi đè lên cha mẹ.

newcó lẽ là sai lệch (đối với tôi nó là). Với các thuộc tính dễ hiểu hơn:

public class Foo
{
    public bool GetSomething => false;
}

public class Bar : Foo
{
    public new bool GetSomething => true;
}

public static void Main(string[] args)
{
    Foo foo = new Bar();
    Console.WriteLine(foo.GetSomething);

    Bar bar = new Bar();
    Console.WriteLine(bar.GetSomething);
}

Sử dụng một trình gỡ lỗi bạn có thể nhận thấy rằng Foo foo2 GetSomething thuộc tính, vì nó thực sự có 2 phiên bản của tài sản, Foo's và Bar' s, và để biết cái nào để sử dụng, c # 'cuốc' tài sản cho các loại hình hiện tại.

Nếu bạn muốn sử dụng phiên bản của Bar, bạn sẽ sử dụng ghi đè hoặc sử dụng Foo foothay thế.

Bar barchỉ có 1 , vì nó muốn hành vi hoàn toàn mới cho GetSomething.


0

Không đánh dấu một phương thức bằng bất cứ điều gì có nghĩa là: Ràng buộc phương thức này bằng cách sử dụng kiểu biên dịch của đối tượng, không phải kiểu thời gian chạy (liên kết tĩnh).

Đánh dấu một phương thức bằng virtualphương tiện: Ràng buộc phương thức này bằng cách sử dụng kiểu thời gian chạy của đối tượng, không phải kiểu thời gian biên dịch (liên kết động).

Đánh dấu một virtualphương thức lớp cơ sở với overridetrong lớp dẫn xuất có nghĩa là: Đây là phương thức được ràng buộc bằng cách sử dụng kiểu thời gian chạy của đối tượng (liên kết động).

Đánh dấu một virtualphương thức lớp cơ sở với newtrong lớp dẫn xuất có nghĩa là: Đây là một phương thức mới, không có liên quan đến một phương thức có cùng tên trong lớp cơ sở và nó phải được ràng buộc bằng cách sử dụng loại thời gian biên dịch của đối tượng (liên kết tĩnh).

Không đánh dấu một virtualphương thức lớp cơ sở trong lớp dẫn xuất có nghĩa là: Phương thức này được đánh dấu là new(liên kết tĩnh).

Đánh dấu một phương thức abstractcó nghĩa là: Phương thức này là ảo, nhưng tôi sẽ không khai báo một phần thân cho nó và lớp của nó cũng trừu tượng (ràng buộc động).

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.