Tại sao gọi một phương thức trong lớp dẫn xuất của tôi gọi phương thức lớp cơ sở?


146

Xem xét mã này:

class Program
{
    static void Main(string[] args)
    {
        Person person = new Teacher();
        person.ShowInfo();
        Console.ReadLine();
    }
}

public class Person
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}
public class Teacher : Person
{
    public new void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

Khi tôi chạy mã này, phần sau đây được xuất ra:

Tôi là người

Tuy nhiên, bạn có thể thấy rằng đó là một ví dụ Teacher, không phải của Person. Tại sao mã làm điều đó?


3
Câu hỏi từ một người Java: là Console.ReadLine (); cần thiết cho ví dụ này?
Giàu

2
@Shahrooz Tôi không thể trả lời câu hỏi của bạn - Tôi không biết C #. Tôi đã hỏi một câu hỏi C # rất tầm thường, đó là liệu cuộc gọi đến ReadLine trong phương thức chính có cần thiết để có thể gọi WriteLine trong các lớp Người và Giáo viên hay không.
Giàu

6
Vâng, .Net sẽ tự động đóng cửa sổ giao diện điều khiển khi Main () thoát. để giải quyết vấn đề này, chúng tôi sử dụng Console.Read () hoặc Console.Readline () để chờ thêm đầu vào để giao diện điều khiển vẫn mở.
Thuyền trưởng Kenpachi

15
@ Không, không cần thiết , nhưng bạn sẽ thường thấy vì lý do này: khi chạy chương trình giao diện điều khiển từ Visual Studio, khi kết thúc chương trình, cửa sổ lệnh sẽ đóng ngay lập tức, vì vậy nếu bạn muốn xem đầu ra chương trình, bạn cần nói với nó đợi.
AakashM

1
@AakashM Cảm ơn - Tôi dành thời gian của mình ở Eclipse nơi bàn điều khiển là một phần của cửa sổ Eclipse và do đó không đóng. Điều đó làm cho ý nghĩa hoàn hảo.
Giàu

Câu trả lời:


368

Có sự khác biệt giữa newvirtual/ override.

Bạn có thể tưởng tượng rằng một lớp, khi được khởi tạo, không gì khác hơn là một bảng con trỏ, chỉ ra việc thực hiện thực tế các phương thức của nó. Hình ảnh sau đây sẽ hình dung điều này khá tốt:

Minh họa thực hiện phương pháp

Bây giờ có nhiều cách khác nhau, một phương pháp có thể được xác định. Mỗi hành vi khác nhau khi nó được sử dụng với thừa kế. Cách tiêu chuẩn luôn hoạt động như hình ảnh trên minh họa. Nếu bạn muốn thay đổi hành vi này, bạn có thể đính kèm các từ khóa khác nhau vào phương pháp của mình.

1. Các lớp trừu tượng

Đầu tiên là abstract. abstractphương pháp chỉ đơn giản là trỏ đến hư không:

Minh họa của các lớp trừu tượng

Nếu lớp của bạn chứa các thành viên trừu tượng, nó cũng cần được đánh dấu là abstract, nếu không trình biên dịch sẽ không biên dịch ứng dụng của bạn. Bạn không thể tạo các thể hiện của abstractcác lớp, nhưng bạn có thể kế thừa từ chúng và tạo các thể hiện của các lớp được kế thừa của bạn và truy cập chúng bằng định nghĩa lớp cơ sở. Trong ví dụ của bạn, nó sẽ trông như sau:

public abstract class Person
{
    public abstract void ShowInfo();
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a teacher!");
    }
}

public class Student : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a student!");
    }
}

Nếu được gọi, hành vi ShowInfokhác nhau, dựa trên việc thực hiện:

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a student!'

Cả hai StudentTeachers đều là Persons, nhưng chúng cư xử khác nhau khi chúng được yêu cầu nhắc thông tin về bản thân. Tuy nhiên, cách để yêu cầu họ nhắc thông tin của họ là như nhau: Sử dụng Persongiao diện lớp.

Vì vậy, những gì xảy ra đằng sau hậu trường, khi bạn thừa hưởng từ Person? Khi thực hiện ShowInfo, con trỏ không còn trỏ đến đâu nữa, bây giờ nó trỏ đến việc thực hiện thực tế! Khi tạo một Studentthể hiện, nó trỏ đến Students ShowInfo:

Minh họa phương pháp kế thừa

2. Phương thức ảo

Cách thứ hai là sử dụng virtualcác phương pháp. Hành vi là như nhau, ngoại trừ bạn đang cung cấp một triển khai mặc định tùy chọn trong lớp cơ sở của bạn. Các lớp với virtualcác thành viên có thể được cung cấp, tuy nhiên các lớp được kế thừa có thể cung cấp các triển khai khác nhau. Đây là những gì mã của bạn thực sự trông giống như để làm việc:

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am a person!");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a teacher!");
    }
}

Sự khác biệt chính là, thành viên cơ sở Person.ShowInfokhông còn chỉ đến nơi nào nữa. Đây cũng là lý do, tại sao bạn có thể tạo các phiên bản của Person(và do đó, nó không cần phải được đánh dấu là abstractnữa):

Minh họa của một thành viên ảo trong một lớp cơ sở

Bạn nên chú ý rằng hình ảnh này không khác với hình ảnh đầu tiên bây giờ. Điều này là do virtualphương thức này đang chỉ đến một cách thực hiện " cách tiêu chuẩn ". Sử dụng virtual, bạn có thể nói Personsrằng họ có thể (không phải ) cung cấp một triển khai khác cho ShowInfo. Nếu bạn cung cấp một triển khai khác (sử dụng override), giống như tôi đã làm Teacherở trên, hình ảnh sẽ trông giống như đối với abstract. Hãy tưởng tượng, chúng tôi không cung cấp một triển khai tùy chỉnh cho Students:

public class Student : Person
{
}

Mã sẽ được gọi như thế này:

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a person!'

Và hình ảnh Studentsẽ như thế này:

Minh họa việc thực hiện mặc định của một phương thức, sử dụng từ khóa ảo

3. Từ khóa `new` ma thuật hay còn gọi là" Shadowing "

newnhiều hơn một hack xung quanh này. Bạn có thể cung cấp các phương thức trong các lớp tổng quát, có cùng tên với các phương thức trong lớp / giao diện cơ sở. Cả hai đều chỉ ra cách thực hiện tùy chỉnh của riêng họ:

Minh họa về "cách xung quanh" bằng cách sử dụng từ khóa mới

Việc thực hiện trông giống như một, bạn cung cấp. Hành vi khác nhau, dựa trên cách bạn truy cập phương thức:

Teacher teacher = new Teacher();
Person person = (Person)teacher;

teacher.ShowInfo();    // Prints 'I am a teacher!'
person.ShowInfo();     // Prints 'I am a person!'

Hành vi này có thể được muốn, nhưng trong trường hợp của bạn nó là sai lệch.

Tôi hy vọng điều này làm cho mọi thứ rõ ràng hơn để hiểu cho bạn!


9
Cảm ơn câu trả lời tuyệt vời của bạn

6
Bạn đã sử dụng những gì để tạo ra các sơ đồ?
BlueRaja - Daniel Pflughoeft

2
Câu trả lời tuyệt vời và rất kỹ lưỡng.
Nik Bougalis

8
tl; dr bạn đã sử dụng newđể phá vỡ sự kế thừa của chức năng và làm cho chức năng mới tách biệt chức năng của siêu lớp
ratchet freak

3
@Taymon: Thật ra thì không ... Tôi chỉ muốn làm rõ rằng cuộc gọi bây giờ đi ngược lại Person, không phải Student;)
Carsten

45

Đa hình phụ trong C # sử dụng tính ảo ảo rõ ràng, tương tự như C ++ nhưng không giống như Java. Điều này có nghĩa là bạn rõ ràng phải đánh dấu các phương thức là overridable (nghĩa là virtual). Trong C #, bạn cũng phải đánh dấu rõ ràng các phương thức ghi đè là ghi đè (nghĩa là override) để ngăn lỗi chính tả.

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

Trong mã trong câu hỏi của bạn, bạn sử dụng new, cái bóng thay vì ghi đè. Shadowing chỉ ảnh hưởng đến ngữ nghĩa thời gian biên dịch chứ không phải là ngữ nghĩa thời gian chạy, do đó đầu ra ngoài ý muốn.


4
Ai sẽ nói OP biết những điều đó có nghĩa gì.
Cole Johnson

@ColeJohnson Tôi sẽ thêm một giải thích.

25

Bạn phải làm cho phương thức trở nên ảo và bạn phải ghi đè hàm trong lớp con, để gọi phương thức của đối tượng lớp mà bạn đặt trong tham chiếu lớp cha.

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}
public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

Phương thức ảo

Khi một phương thức ảo được gọi, loại thời gian chạy của đối tượng được kiểm tra cho một thành viên ghi đè. Thành viên ghi đè trong lớp dẫn xuất nhất được gọi, có thể là thành viên ban đầu, nếu không có lớp dẫn xuất nào ghi đè thành viên. Theo mặc định, các phương thức là không ảo. Bạn không thể ghi đè một phương thức không ảo. Bạn không thể sử dụng công cụ sửa đổi ảo với công cụ sửa đổi tĩnh, trừu tượng, riêng tư hoặc ghi đè, MSDN .

Sử dụng mới để tạo bóng

Bạn đang sử dụng từ khóa mới thay vì ghi đè, đây là những gì mới

  • Nếu phương thức trong lớp dẫn xuất không đi trước các từ khóa mới hoặc ghi đè, trình biên dịch sẽ đưa ra cảnh báo và phương thức sẽ hoạt động như thể có từ khóa mới.

  • Nếu phương thức trong lớp dẫn xuất đi trước từ khóa mới, thì phương thức được định nghĩa là độc lập với phương thức trong lớp cơ sở , bài viết MSDN này giải thích rất rõ.

Ràng buộc sớm VS ràng buộc muộn

Chúng ta có liên kết sớm tại thời gian biên dịch cho phương thức bình thường (không phải ảo) là trường hợp hiện tại trình biên dịch sẽ liên kết cuộc gọi đến phương thức của lớp cơ sở là phương thức của kiểu tham chiếu (lớp cơ sở) thay vì đối tượng được giữ trong tham chiếu của cơ sở lớp tức là đối tượng lớp dẫn xuất . Điều này là do ShowInfokhông phải là một phương pháp ảo. Liên kết muộn được thực hiện trong thời gian chạy cho (phương thức ảo / ghi đè) bằng bảng phương thức ảo (vtable).

Đối với một hàm bình thường, trình biên dịch có thể tìm ra vị trí số của nó trong bộ nhớ. Sau đó, khi hàm được gọi, nó có thể tạo một lệnh để gọi hàm tại địa chỉ này.

Đối với một đối tượng có bất kỳ phương thức ảo nào, trình biên dịch sẽ tạo một bảng v. Đây thực chất là một mảng chứa các địa chỉ của các phương thức ảo. Mỗi đối tượng có một phương thức ảo sẽ chứa một thành viên ẩn được tạo bởi trình biên dịch là địa chỉ của bảng v. Khi một hàm ảo được gọi, trình biên dịch sẽ tìm ra vị trí của phương thức thích hợp trong bảng v. Sau đó, nó sẽ tạo mã để tìm trong bảng v đối tượng và gọi phương thức ảo tại vị trí này, Reference .


7

Tôi muốn xây dựng câu trả lời của Achratt . Để đầy đủ, sự khác biệt là OP đang mong đợi newtừ khóa trong phương thức của lớp dẫn xuất để ghi đè phương thức lớp cơ sở. Những gì nó thực sự làm là ẩn phương thức lớp cơ sở.

Trong C #, như một câu trả lời khác đã đề cập, ghi đè phương thức truyền thống phải rõ ràng; phương thức lớp cơ sở phải được đánh dấu là virtualvà lớp dẫn xuất phải đặc biệt overridelà phương thức lớp cơ sở. Nếu điều này được thực hiện, thì việc đối tượng được coi là một thể hiện của lớp cơ sở hay lớp dẫn xuất không thành vấn đề; phương pháp dẫn xuất được tìm thấy và được gọi. Điều này được thực hiện theo cách tương tự như trong C ++; một phương thức được đánh dấu "ảo" hoặc "ghi đè", khi được biên dịch, được giải quyết "trễ" (trong thời gian chạy) bằng cách xác định loại thực tế của đối tượng được tham chiếu và đi qua phân cấp đối tượng xuống dưới cây từ loại biến sang loại đối tượng thực tế, để tìm ra cách thực hiện dẫn xuất nhất của phương thức được xác định bởi kiểu biến.

Điều này khác với Java, cho phép "ghi đè ngầm"; đối với các phương thức ví dụ (không tĩnh), chỉ cần xác định một phương thức có cùng chữ ký (tên và số / loại tham số) sẽ khiến lớp con ghi đè lên lớp cha.

Vì thường hữu ích khi mở rộng hoặc ghi đè chức năng của phương thức không ảo mà bạn không kiểm soát, C # cũng bao gồm newtừ khóa theo ngữ cảnh. Các newtừ khóa "da" phương pháp cha mẹ thay vì trọng nó. Bất kỳ phương pháp kế thừa nào cũng có thể được ẩn dù nó ảo hay không; điều này cho phép bạn, nhà phát triển, tận dụng các thành viên bạn muốn thừa kế từ cha mẹ mà không phải làm việc xung quanh những người bạn không, trong khi vẫn cho phép bạn trình bày "giao diện" tương tự cho người tiêu dùng mã của bạn.

Ẩn hoạt động tương tự như ghi đè từ quan điểm của một người sử dụng đối tượng của bạn ở hoặc dưới mức kế thừa mà tại đó phương thức ẩn được xác định. Từ ví dụ của câu hỏi, một lập trình viên tạo ra một Giáo viên và lưu trữ tham chiếu đó trong một biến của loại Giáo viên sẽ thấy hành vi của việc triển khai ShowInfo () từ Giáo viên, ẩn một từ Người. Tuy nhiên, ai đó làm việc với đối tượng của bạn trong bộ sưu tập các bản ghi Person (như bạn) sẽ thấy hành vi triển khai Person của ShowInfo (); bởi vì phương thức của Giáo viên không ghi đè lên cha mẹ của nó (cũng sẽ yêu cầu Person.ShowInfo () là ảo), mã hoạt động ở cấp độ trừu tượng của Người sẽ không tìm thấy triển khai của Giáo viên và sẽ không sử dụng nó.

Ngoài ra, không chỉ newtừ khóa sẽ làm điều này một cách rõ ràng, C # cho phép ẩn phương thức ẩn; chỉ cần xác định một phương thức có cùng chữ ký với phương thức lớp cha, không có overridehoặc newsẽ ẩn nó (mặc dù nó sẽ tạo ra một cảnh báo trình biên dịch hoặc khiếu nại từ một số trợ lý tái cấu trúc nhất định như ReSharper hoặc CodeRush). Đây là sự thỏa hiệp mà các nhà thiết kế của C # đã đưa ra giữa các phần ghi đè rõ ràng của C ++ so với các ẩn của Java và trong khi thanh lịch, nó không luôn tạo ra hành vi mà bạn mong đợi nếu bạn đến từ một nền tảng trong các ngôn ngữ cũ hơn.

Đây là công cụ mới: Điều này trở nên phức tạp khi bạn kết hợp hai từ khóa trong chuỗi thừa kế dài. Hãy xem xét những điều sau đây:

class Foo { public virtual void DoFoo() { Console.WriteLine("Foo"); } }
class Bar:Foo { public override sealed void DoFoo() { Console.WriteLine("Bar"); } }
class Baz:Bar { public virtual void DoFoo() { Console.WriteLine("Baz"); } }
class Bai:Baz { public override void DoFoo() { Console.WriteLine("Bai"); } }
class Bat:Bai { public new void DoFoo() { Console.WriteLine("Bat"); } }
class Bak:Bat { }

Foo foo = new Foo();
Bar bar = new Bar();
Baz baz = new Baz();
Bai bai = new Bai();
Bat bat = new Bat();

foo.DoFoo();
bar.DoFoo();
baz.DoFoo();
bai.DoFoo();
bat.DoFoo();

Console.WriteLine("---");

Foo foo2 = bar;
Bar bar2 = baz;
Baz baz2 = bai;
Bai bai2 = bat;
Bat bat2 = new Bak();

foo2.DoFoo();
bar2.DoFoo();
baz2.DoFoo();
bai2.DoFoo();    

Console.WriteLine("---");

Foo foo3 = bak;
Bar bar3 = bak;
Baz baz3 = bak;
Bai bai3 = bak;
Bat bat3 = bak;

foo3.DoFoo();
bar3.DoFoo();
baz3.DoFoo();
bai3.DoFoo();    
bat3.DoFoo();

Đầu ra:

Foo
Bar
Baz
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat

Bộ năm đầu tiên là tất cả được mong đợi; bởi vì mỗi cấp độ có một triển khai và được tham chiếu như một đối tượng cùng loại như đã được khởi tạo, thời gian chạy giải quyết mỗi cuộc gọi đến mức kế thừa được tham chiếu bởi loại biến.

Bộ năm thứ hai là kết quả của việc gán từng thể hiện cho một biến của kiểu cha ngay lập tức. Bây giờ, một số khác biệt trong hành vi rũ bỏ; foo2, mà thực sự là một Bardiễn viên như một Foo, vẫn sẽ tìm thấy phương thức dẫn xuất nhiều hơn của loại đối tượng thực tế Bar. bar2là một Baz, nhưng không giống như foo2, bởi vì Baz không ghi đè rõ ràng việc triển khai của Bar (không thể; Bar sealednó), nó không được nhìn thấy bởi thời gian chạy khi nhìn "từ trên xuống", do đó, việc triển khai của Bar được gọi thay thế. Lưu ý rằng Baz không phải sử dụng newtừ khóa; bạn sẽ nhận được cảnh báo trình biên dịch nếu bạn bỏ qua từ khóa, nhưng hành vi ngụ ý trong C # là ẩn phương thức cha. baz2là một Bai, mà ghi đè Baz'snewthực hiện, vì vậy hành vi của nó tương tự như foo2của; thực hiện loại đối tượng thực tế trong Bai được gọi. bai2là một Bat, một lần nữa che giấu Baiviệc thực hiện phương thức của cha mẹ nó và nó hoạt động giống như bar2mặc dù việc triển khai của Bai không bị niêm phong, vì vậy về mặt lý thuyết, Bat có thể đã ghi đè thay vì ẩn phương thức. Cuối cùng, bat2là một Bak, trong đó không có thực hiện ghi đè của một trong hai loại, và chỉ đơn giản sử dụng của cha mẹ của nó.

Bộ thứ ba gồm năm minh họa hành vi phân giải từ trên xuống đầy đủ. Mọi thứ thực sự đang tham chiếu một thể hiện của lớp dẫn xuất nhất trong chuỗi, Baknhưng độ phân giải ở mọi cấp độ của loại biến được thực hiện bằng cách bắt đầu ở mức đó của chuỗi thừa kế và đi sâu vào ghi đè rõ ràng nhất có nguồn gốc của phương thức, đó là những người ở Bar, BaiBat. Phương thức ẩn do đó "phá vỡ" chuỗi thừa kế ghi đè; bạn phải làm việc với đối tượng ở hoặc dưới mức kế thừa ẩn phương thức để sử dụng phương thức ẩn. Mặt khác, phương thức ẩn là "không che" và được sử dụng thay thế.


4

Vui lòng đọc về đa hình trong C #: Đa hình (Hướng dẫn lập trình C #)

Đây là một ví dụ từ đó:

Khi từ khóa mới được sử dụng, các thành viên lớp mới được gọi thay vì các thành viên lớp cơ sở đã được thay thế. Những thành viên lớp cơ sở được gọi là thành viên ẩn. Các thành viên của lớp ẩn vẫn có thể được gọi nếu một thể hiện của lớp dẫn xuất được chuyển thành một thể hiện của lớp cơ sở. Ví dụ:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

3

Bạn cần phải làm cho nó virtualvà sau đó ghi đè chức năng đó Teacher. Khi bạn đang kế thừa và sử dụng con trỏ cơ sở để tham chiếu đến một lớp dẫn xuất, bạn cần ghi đè nó bằng cách sử dụng virtual. newlà để ẩn basephương thức lớp trên một tham chiếu lớp dẫn xuất và không phải là basetham chiếu lớp.


3

Tôi muốn thêm một vài ví dụ nữa để mở rộng thông tin xung quanh vấn đề này. Hy vọng điều này cũng giúp:

Dưới đây là một mẫu mã giúp làm sạch không khí xung quanh những gì xảy ra khi một loại dẫn xuất được gán cho một loại cơ sở. Những phương thức nào có sẵn và sự khác biệt giữa các phương thức bị ghi đè và ẩn trong bối cảnh này.

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.foo();        // A.foo()
            a.foo2();       // A.foo2()

            a = new B();    
            a.foo();        // B.foo()
            a.foo2();       // A.foo2()
            //a.novel() is not available here

            a = new C();
            a.foo();        // C.foo()
            a.foo2();       // A.foo2()

            B b1 = (B)a;    
            b1.foo();       // C.foo()
            b1.foo2();      // B.foo2()
            b1.novel();     // B.novel()

            Console.ReadLine();
        }
    }


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

        public void foo2()
        {
            Console.WriteLine("A.foo2()");
        }
    }

    class B : A
    {
        public override void foo()
        {
            // This is an override
            Console.WriteLine("B.foo()");
        }

        public new void foo2()      // Using the 'new' keyword doesn't make a difference
        {
            Console.WriteLine("B.foo2()");
        }

        public void novel()
        {
            Console.WriteLine("B.novel()");
        }
    }

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

        public new void foo2()
        {
            Console.WriteLine("C.foo2()");
        }
    }
}

Một điều bất thường nữa là, đối với dòng mã sau:

A a = new B();    
a.foo(); 

Trình biên dịch VS (intellisense) sẽ hiển thị a.foo () là A.foo ().

Do đó, rõ ràng rằng khi một loại dẫn xuất hơn được gán cho loại cơ sở, biến 'loại cơ sở' đóng vai trò là loại cơ sở cho đến khi một phương thức được ghi đè trong loại dẫn xuất được tham chiếu. Điều này có thể trở nên hơi phản trực quan với các phương thức hoặc phương thức ẩn có cùng tên (nhưng không bị ghi đè) giữa các kiểu cha và con.

Mẫu mã này sẽ giúp phân định các cảnh báo này!


2

C # khác với java trong hành vi ghi đè lớp cha / con. Theo mặc định trong Java, tất cả các phương thức đều là ảo, vì vậy hành vi mà bạn muốn được hỗ trợ ngoài hộp.

Trong C #, bạn phải đánh dấu một phương thức là ảo trong lớp cơ sở, sau đó bạn sẽ nhận được những gì bạn muốn.


2

Các mới từ khóa nói rằng các phương thức trong lớp hiện tại sẽ chỉ làm việc nếu bạn có một thể hiện của các giáo viên lớp lưu trữ trong một biến kiểu giáo viên. Hoặc bạn có thể kích hoạt nó bằng cách sử dụng vật đúc: ((Giáo viên) Người) .ShowInfo ()


1

Loại biến 'giáo viên' ở đây là typeof(Person)và loại này không biết gì về lớp Giáo viên và không cố gắng tìm kiếm bất kỳ phương thức nào trong các loại dẫn xuất. Để gọi phương thức của lớp Giáo viên, bạn nên bỏ biến : (person as Teacher).ShowInfo().

Để gọi phương thức cụ thể dựa trên loại giá trị, bạn nên sử dụng từ khóa 'ảo' trong lớp cơ sở của mình và ghi đè các phương thức ảo trong các lớp dẫn xuất. Cách tiếp cận này cho phép thực hiện các lớp dẫn xuất có hoặc không ghi đè các phương thức ảo. Các phương thức của lớp cơ sở sẽ được gọi cho các kiểu mà không bị ảo hóa.

public class Program
{
    private static void Main(string[] args)
    {
        Person teacher = new Teacher();
        teacher.ShowInfo();

        Person incognito = new IncognitoPerson ();
        incognito.ShowInfo();

        Console.ReadLine();
    }
}

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

public class IncognitoPerson : Person
{

}

1

Có thể là quá muộn ... Nhưng câu hỏi rất đơn giản và câu trả lời nên có cùng mức độ phức tạp.

Trong biến mã của bạn, người đó không biết gì về teacher.ShowInfo (). Không có cách nào để gọi phương thức cuối cùng từ tham chiếu lớp cơ sở, bởi vì nó không phải là ảo.

Có một cách tiếp cận hữu ích để kế thừa - hãy thử tưởng tượng bạn muốn nói gì với hệ thống phân cấp mã của bạn. Cũng cố gắng tưởng tượng những gì một hoặc một công cụ khác nói về chính nó. Ví dụ: nếu bạn thêm chức năng ảo vào một lớp cơ sở, bạn cho rằng: 1. nó có thể có cài đặt mặc định; 2. nó có thể được thực hiện lại trong lớp dẫn xuất. Nếu bạn thêm chức năng trừu tượng, điều đó chỉ có nghĩa là một điều - lớp con phải tạo ra một triển khai. Nhưng trong trường hợp bạn có chức năng đơn giản - bạn không mong đợi ai thay đổi cách thực hiện.


0

Trình biên dịch thực hiện điều này bởi vì nó không biết rằng nó là một Teacher. Tất cả những gì nó biết là nó là một Personhoặc một cái gì đó bắt nguồn từ nó. Vì vậy, tất cả những gì nó có thể làm là gọi Person.ShowInfo()phương thức.


0

Chỉ muốn đưa ra một câu trả lời ngắn gọn -

Bạn nên sử dụng virtualoverridetrong các lớp có thể bị ghi đè. Sử dụng virtualcho các phương thức có thể được ghi đè bởi các lớp con và sử dụng overridecho các phương thức nên ghi đè các virtualphương thức đó.


0

Tôi đã viết mã giống như bạn đã đề cập ở trên trong java ngoại trừ một số thay đổi và nó hoạt động tốt như ngoại trừ. Phương thức của lớp cơ sở bị ghi đè và do đó đầu ra được hiển thị là "Tôi là giáo viên".

Lý do: Vì chúng ta đang tạo một tham chiếu của lớp cơ sở (có khả năng có thể hiện tham chiếu của lớp dẫn xuất) mà thực sự có chứa tham chiếu của lớp dẫn xuất. Và như chúng ta biết rằng cá thể luôn xem xét các phương thức của nó trước tiên nếu nó tìm thấy nó ở đó nó thực thi nó và nếu nó không tìm thấy định nghĩa ở đó thì nó sẽ đi lên trong hệ thống phân cấp.

public class inheritance{

    public static void main(String[] args){

        Person person = new Teacher();
        person.ShowInfo();
    }
}

class Person{

    public void ShowInfo(){
        System.out.println("I am Person");
    }
}

class Teacher extends Person{

    public void ShowInfo(){
        System.out.println("I am Teacher");
    }
}

0

Dựa trên trình diễn xuất sắc của Keith S. và câu trả lời chất lượng của mọi người khác và vì mục đích hoàn thiện của uber, hãy tiếp tục và đưa các triển khai giao diện rõ ràng vào để chứng minh cách thức hoạt động. Hãy xem xét những điều dưới đây:

không gian tên LinqConsoleApp {

class Program
{

    static void Main(string[] args)
    {


        Person person = new Teacher();
        Console.Write(GetMemberName(() => person) + ": ");
        person.ShowInfo();

        Teacher teacher = new Teacher();
        Console.Write(GetMemberName(() => teacher) + ": ");
        teacher.ShowInfo();

        IPerson person1 = new Teacher();
        Console.Write(GetMemberName(() => person1) + ": ");
        person1.ShowInfo();

        IPerson person2 = (IPerson)teacher;
        Console.Write(GetMemberName(() => person2) + ": ");
        person2.ShowInfo();

        Teacher teacher1 = (Teacher)person1;
        Console.Write(GetMemberName(() => teacher1) + ": ");
        teacher1.ShowInfo();

        Person person4 = new Person();
        Console.Write(GetMemberName(() => person4) + ": ");
        person4.ShowInfo();

        IPerson person3 = new Person();
        Console.Write(GetMemberName(() => person3) + ": ");
        person3.ShowInfo();

        Console.WriteLine();

        Console.ReadLine();

    }

    private static string GetMemberName<T>(Expression<Func<T>> memberExpression)
    {
        MemberExpression expressionBody = (MemberExpression)memberExpression.Body;
        return expressionBody.Member.Name;
    }

}
interface IPerson
{
    void ShowInfo();
}
public class Person : IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Person == " + this.GetType());
    }
    void IPerson.ShowInfo()
    {
        Console.WriteLine("I am interface Person == " + this.GetType());
    }
}
public class Teacher : Person, IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Teacher == " + this.GetType());
    }
}

}

Đây là đầu ra:

người: Tôi là người == LinqConsoleApp.Teacher

giáo viên: Tôi là giáo viên == LinqConsoleApp.Teacher

person1: Tôi là giáo viên == LinqConsoleApp.Teacher

person2: Tôi là giáo viên == LinqConsoleApp.Teacher

giáo viên1: Tôi là giáo viên == LinqConsoleApp.Teacher

person4: Tôi là Person == LinqConsoleApp.Person

person3: Tôi là giao diện Person == LinqConsoleApp.Person

Hai điều cần lưu ý:
Phương thức teacher.ShowInfo () bỏ qua từ khóa mới. Khi mới bị bỏ qua, hành vi của phương thức giống như khi từ khóa mới được xác định rõ ràng.

Bạn chỉ có thể sử dụng từ khóa ghi đè kết hợp với từ khóa ảo. Phương thức lớp cơ sở phải là ảo. Hoặc trừu tượng trong trường hợp đó lớp cũng phải trừu tượng.

người nhận được triển khai cơ sở của ShowInfo vì lớp Giáo viên không thể ghi đè triển khai cơ sở (không có khai báo ảo) và người là .GetType (Giáo viên) vì vậy nó che giấu việc triển khai của lớp Giáo viên.

giáo viên nhận được triển khai Giáo viên xuất phát của ShowInfo vì giáo viên vì đó là Typeof (Giáo viên) và nó không ở cấp độ kế thừa Person.

person1 nhận được triển khai Giáo viên dẫn xuất bởi vì nó là .GetType (Giáo viên) và từ khóa mới ngụ ý ẩn việc thực hiện cơ sở.

person2 cũng nhận được triển khai Giáo viên dẫn xuất mặc dù nó thực hiện IPerson và nó được phân phối rõ ràng cho IPerson. Điều này một lần nữa bởi vì lớp Giáo viên không triển khai rõ ràng phương thức IPerson.ShowInfo ().

teacher1 cũng nhận được triển khai Giáo viên dẫn xuất bởi vì nó là .GetType (Giáo viên).

Chỉ person3 có được triển khai IPerson của ShowInfo vì chỉ có lớp Person thực hiện rõ ràng phương thức và person3 là một thể hiện của kiểu IPerson.

Để thực hiện rõ ràng một giao diện, bạn phải khai báo một thể hiện var của loại giao diện đích và một lớp phải thực hiện rõ ràng (đủ điều kiện) (các) thành viên giao diện.

Thông báo thậm chí person4 không nhận được triển khai IPerson.ShowInfo. Điều này là do mặc dù person4 là .GetType (Person) và mặc dù Person thực hiện IPerson, person4 không phải là một thể hiện của IPerson.


Tôi thấy mã định dạng đúng là một thách thức. Không có thời gian để khá nó lên ngay bây giờ ...
Steely

0

Mẫu LinQPad để khởi chạy một cách mù quáng và giảm trùng lặp mã mà tôi nghĩ là những gì bạn đã cố gắng làm.

void Main()
{
    IEngineAction Test1 = new Test1Action();
    IEngineAction Test2 = new Test2Action();
    Test1.Execute("Test1");
    Test2.Execute("Test2");
}

public interface IEngineAction
{
    void Execute(string Parameter);
}

public abstract class EngineAction : IEngineAction
{
    protected abstract void PerformAction();
    protected string ForChildren;
    public void Execute(string Parameter)
    {  // Pretend this method encapsulates a 
       // lot of code you don't want to duplicate 
      ForChildren = Parameter;
      PerformAction();
    }
}

public class Test1Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Performed: " + ForChildren).Dump();
    }
}

public class Test2Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Actioned: " + ForChildren).Dump();
    }
}
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.