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


79

Tôi đang khá bối rối giữa một số khái niệm về OOP: virtual, override, newsealed override. Bất cứ ai có thể giải thích sự khác biệt?

Tôi khá rõ ràng rằng nếu phương thức lớp dẫn xuất được sử dụng, người ta có thể sử dụng overridetừ khóa để phương thức lớp cơ sở sẽ được ghi đè bởi lớp dẫn xuất. Nhưng tôi không chắc về new, và sealed override.

Câu trả lời:


107

Các ảo từ khóa được sử dụng để sửa đổi một phương pháp kê khai, tài sản, indexer hoặc sự kiện, và cho phép nó được ghi đè trong một lớp học có nguồn gốc. Ví dụ: phương thức này có thể bị ghi đè bởi bất kỳ lớp nào kế thừa nó: Sử dụng công cụ sửa đổi mới để ẩn rõ ràng một thành viên được kế thừa từ lớp cơ sở. Để ẩn một thành viên được kế thừa, hãy khai báo nó trong lớp dẫn xuất bằng cách sử dụng cùng một tên và sửa đổi nó bằng công cụ sửa đổi mới.

Đây là tất cả những gì liên quan đến tính đa hình. Khi một phương thức ảo được gọi trên một tham chiếu, kiểu thực tế của đối tượng mà tham chiếu tham chiếu đến sẽ được sử dụng để quyết định sử dụng phương thức triển khai nào. Khi một phương thức của lớp cơ sở bị ghi đè trong lớp dẫn xuất, phiên bản trong lớp dẫn xuất sẽ được sử dụng, ngay cả khi mã gọi không "biết" rằng đối tượng là một thể hiện của lớp dẫn xuất. Ví dụ:

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

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

...

Base d = new Derived();
d.SomeMethod();

sẽ kết thúc việc gọi Derived.SomeMethod nếu điều đó ghi đè Base.SomeMethod.

Bây giờ, nếu bạn sử dụng từ khóa mới thay vì ghi đè , phương thức trong lớp dẫn xuất sẽ không ghi đè phương thức trong lớp cơ sở, nó chỉ ẩn nó đi. Trong trường hợp đó, hãy viết mã như sau:

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

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

...


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

Đầu tiên sẽ gọi Base.SomeOtherMethod, sau đó là Derived.SomeOtherMethod. 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ở.

Nếu bạn không chỉ định mới hoặc ghi đè, kết quả đầu ra giống như khi bạn chỉ định mới, nhưng bạn cũng sẽ nhận được cảnh báo trình biên dịch (vì bạn có thể không biết rằng bạn đang ẩn một phương thức trong lớp cơ sở hoặc thực sự bạn có thể đã muốn ghi đè nó và chỉ đơn thuần là quên bao gồm từ khóa).

Một bản kê khai tài sản ghi đè có thể bao gồm phần sửa đổi được niêm phong . Việc sử dụng công cụ sửa đổi này ngăn một lớp dẫn xuất ghi đè thuộc tính. Những người tiếp cận tài sản niêm phong cũng bị niêm phong.


cảm ơn vì đầu vào .. nhưng một điều không nhận được trong tâm trí của tôi là .. việc sử dụng Base b = new Derived () là gì? Đây là đối tượng tạo của lớp cơ sở hay lớp dẫn xuất ??
xorpower

2
Lớp có nguồn gốc. Tôi nghĩ bạn phải xem xét thêm về tính đa hình. Đây là một trong những tốt cho bạn đọc. msdn.microsoft.com/en-us/library/ms173152(v=vs.80).aspx
CharithJ

5
@Xor: Trong trường hợp đó, bạn đang tạo một thể hiện của một Derivedđối tượng và lưu trữ tham chiếu trong một Basebiến. Điều này hợp lệ vì một Derivedđối tượng cũng là một Baseđối tượng. Điều đó giống như nói rằng chúng ta cần một "người" để chúng ta có được "Johnny", người tình cờ trở thành một người. Thỏa thuận tương tự ở đây.
Jeff Mercado

Tôi chỉ muốn thêm một điểm ở đây. Base b = new Derived()nó tuyên bố rằng một Baselớp có thể được truy cập thông qua Derived classtham chiếu bởi vì a derived classlà sự đặc biệt hóa của lớp cơ sở của nó. Derivedcác lớp có thể thực hiện tất cả các hoạt động (ví dụ: gọi các phương thức của lớp cơ sở, v.v. ) mà a base classcó thể thực hiện. Nhưng a Base classkhông thể thực hiện các hoạt động mà nó Derived classcó thể làm. Vì vậy, Derived d = new Base()không đúng nhưng Base b = new Derived()là chính xác.
mmushtaq

Bạn có thể làm rõ mục đích của việc sử dụng newbổ ngữ để làm hide a base class methodgì không? Trong ví dụ thứ hai, lời gọi b.SomeOtherMethod()gọi triển khai lớp cơ sở (người ta có thể nói rằng nó đã ẩn phương thức của lớp dẫn xuất). Nếu đó là một ví dụ điển hình về cách sử dụng, thì nó newdường như được sử dụng khi người gọi dự định có một biến của a compile-time typeđể sử dụng phương thức của nó, chứ không phải phương thức của bất kỳ biến nào runtime typescó thể được gán cho nó.
Minh Tran

35

Bất kỳ phương thức nào cũng có thể được ghi đè (= virtual) hoặc không. Quyết định được thực hiện bởi người xác định phương pháp:

class Person
{
    // this one is not overridable (not virtual)
    public String GetPersonType()
    {
        return "person";
    }

    // this one is overridable (virtual)
    public virtual String GetName()
    {
        return "generic name";
    }
}

Bây giờ bạn có thể ghi đè những phương thức có thể ghi đè:

class Friend : Person
{
    public Friend() : this("generic name") { }

    public Friend(String name)
    {
        this._name = name;
    }

    // override Person.GetName:
    public override String GetName()
    {
        return _name;
    }
}

Nhưng bạn không thể ghi đè GetPersonTypephương thức vì nó không ảo.

Hãy tạo hai phiên bản của các lớp đó:

Person person = new Person();
Friend friend = new Friend("Onotole");

Khi phương thức không ảo GetPersonTypeđược gọi theo Fiendtrường hợp, nó thực sự Person.GetPersonTypeđược gọi là:

Console.WriteLine(friend.GetPersonType()); // "person"

Khi phương thức ảo GetNameđược gọi theo Friendtrường hợp, nó Friend.GetNameđược gọi là:

Console.WriteLine(friend.GetName()); // "Onotole"

Khi phương thức ảo GetNameđược gọi theo Persontrường hợp, nó Person.GetNameđược gọi là:

Console.WriteLine(person.GetName()); // "generic name"

Khi phương thức không phải ảo được gọi, thân phương thức không được tra cứu - trình biên dịch đã biết phương thức thực cần được gọi. Trong khi với trình biên dịch các phương thức ảo không thể chắc chắn cái nào sẽ gọi và nó được tra cứu trong thời gian chạy trong hệ thống phân cấp lớp từ dưới lên bắt đầu từ kiểu thể hiện mà phương thức được gọi: vì friend.GetNamenó trông bắt đầu từ Friendlớp và tìm thấy nó ngay lập tức, đối với person.GetNamelớp học, nó bắt đầu từ Personvà tìm thấy nó ở đó.

Đôi khi bạn tạo một lớp con, ghi đè một phương thức ảo và bạn không muốn có thêm bất kỳ ghi đè nào trong hệ thống phân cấp - bạn sử dụng sealed overridecho điều đó (nói rằng bạn là người cuối cùng ghi đè phương thức):

class Mike : Friend
{
    public sealed override String GetName()
    {
        return "Mike";
    }
}

Nhưng đôi khi bạn của bạn Mike quyết định thay đổi giới tính của mình và do đó tên của anh ấy thành Alice :) Bạn có thể thay đổi mã gốc hoặc thay vào đó là phân lớp Mike:

class Alice : Mike
{
    public new String GetName()
    {
        return "Alice";
    }
}

Ở đây bạn tạo một phương thức hoàn toàn khác với cùng tên (bây giờ bạn có hai). Phương thức nào và khi nào được gọi? Nó phụ thuộc vào cách bạn gọi nó:

Alice alice = new Alice();
Console.WriteLine(alice.GetName());             // the new method is called, printing "Alice"
Console.WriteLine(((Mike)alice).GetName());     // the method hidden by new is called, printing "Mike"

Khi bạn gọi nó từ Alicequan điểm của bạn, bạn gọi Alice.GetName, khi từ góc độ Mikebạn gọi Mike.GetName. Không có tra cứu thời gian chạy nào được thực hiện ở đây - vì cả hai phương pháp đều không ảo.

Bạn luôn có thể tạo newcác phương thức - cho dù các phương thức bạn đang ẩn có phải là ảo hay không.

Điều này cũng áp dụng cho các thuộc tính và sự kiện - chúng được biểu diễn dưới dạng các phương thức bên dưới.


1
Không có câu trả lời nào đơn giản và đầy đủ hơn câu trả lời mà tôi tìm thấy ở bất cứ đâu. Cảm ơn Loki
Reevs

19

Theo mặc định, một phương thức không thể được ghi đè trong một lớp dẫn xuất trừ khi nó được khai báo virtual, hoặc abstract. virtualcó nghĩa là kiểm tra các triển khai mới hơn trước khi gọiabstractcó nghĩa là giống nhau, nhưng nó được đảm bảo sẽ được ghi đè trong tất cả các lớp dẫn xuất. Ngoài ra, không cần triển khai trong lớp cơ sở vì nó sẽ được định nghĩa lại ở nơi khác.

Ngoại lệ cho phần trên là newbổ ngữ. Một phương thức không được khai báo virtualhoặc abstractcó thể được định nghĩa lại với newsửa đổi trong một lớp dẫn xuất. Khi phương thức được gọi trong lớp cơ sở, phương thức cơ sở được thực thi và khi được gọi trong lớp dẫn xuất, phương thức mới được thực thi. Tất cả những gì newtừ khóa cho phép bạn làm là có hai phương thức có cùng tên trong một hệ thống phân cấp lớp.

Cuối cùng, một công cụ sealedsửa đổi phá vỡ chuỗi virtualphương thức và khiến chúng không thể ghi đè lần nữa. Điều này không được sử dụng thường xuyên, nhưng tùy chọn ở đó. Sẽ có ý nghĩa hơn với một chuỗi 3 lớp, mỗi lớp bắt nguồn từ lớp trước

A -> B -> C

nếu Acó một virtualhoặc abstractphương pháp, đó là overriddentrong B, sau đó nó cũng có thể ngăn chặn Ctừ việc thay đổi nó một lần nữa bằng cách tuyên bố nó sealedtrong B.

sealedcũng được sử dụng trong classes, và đó là nơi bạn thường gặp từ khóa này.

Tôi hi vọng cái này giúp được.


8
 public class Base 
 {
   public virtual void SomeMethod()
   {
     Console.WriteLine("B");
   }
  }

public class Derived : Base
{
   //Same method is written 3 times with different keywords to explain different behaviors. 


   //This one is Simple method
  public void SomeMethod()
  {
     Console.WriteLine("D");
  }

  //This method has 'new' keyword
  public new void SomeMethod()
  {
     Console.WriteLine("D");
  }

  //This method has 'override' keyword
  public override void SomeMethod()
  {
     Console.WriteLine("D");
  }
}

Bây giờ điều đầu tiên đầu tiên

 Base b=new Base();
 Derived d=new Derived();
 b.SomeMethod(); //will always write B
 d.SomeMethod(); //will always write D

Bây giờ các từ khóa đều là về Đa hình

 Base b = new Derived();
  1. Sử dụng virtualtrong lớp cơ sở và ghi đè lên Derivedsẽ cho D (Đa hình).
  2. Sử dụng overridemà không có virtualtrong Basesẽ báo lỗi.
  3. Tương tự khi viết một phương thức (không ghi đè) với virtualsẽ viết 'B' với cảnh báo (vì không có đa hình nào được thực hiện).
  4. Để ẩn cảnh báo như ở trên, hãy viết newtrước phương thức đơn giản đó trong Derived.
  5. new từ khóa là một câu chuyện khác, nó chỉ đơn giản là ẩn cảnh báo cho biết rằng thuộc tính có cùng tên ở đó trong lớp cơ sở.
  6. virtualhoặc newcả hai đều giống nhau ngoại trừ công cụ sửa đổi mới

  7. newoverridekhông thể được sử dụng trước cùng một phương thức hoặc thuộc tính.

  8. sealed trước khi bất kỳ lớp hoặc phương thức nào khóa nó được sử dụng trong lớp Derived và nó gây ra lỗi thời gian biên dịch.

Xin lỗi, nhưng -1 vì nhiều lỗi biên dịch: phương thức được khai báo nhiều lần với cùng một tham số, không có dấu ngoặc kép xung quanh chuỗi B & D ...
DeveloperDan
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.