Làm thế nào để gọi base.base.method ()?


127
// Cannot change source code
class Base
{
    public virtual void Say()
    {
        Console.WriteLine("Called from Base.");
    }
}

// Cannot change source code
class Derived : Base
{
    public override void Say()
    {
        Console.WriteLine("Called from Derived.");
        base.Say();
    }
}

class SpecialDerived : Derived
{
    public override void Say()
    {
        Console.WriteLine("Called from Special Derived.");
        base.Say();
    }
}

class Program
{
    static void Main(string[] args)
    {
        SpecialDerived sd = new SpecialDerived();
        sd.Say();
    }
}

Kết quả là:

Được gọi từ Nguồn gốc Đặc biệt.
Được gọi từ Có nguồn gốc. / * điều này không được mong đợi * /
Được gọi từ Cơ sở.

Làm cách nào tôi có thể viết lại lớp SpecialDerived để phương thức của lớp trung gian "Derived" không được gọi?

CẬP NHẬT: Lý do tại sao tôi muốn kế thừa từ Derived thay vì Base là lớp Derived chứa rất nhiều triển khai khác. Vì tôi không thể làm base.base.method()ở đây, tôi đoán cách tốt nhất là làm như sau?

// Không thể thay đổi mã nguồn

class Derived : Base
{
    public override void Say()
    {
        CustomSay();

        base.Say();
    }

    protected virtual void CustomSay()
    {
        Console.WriteLine("Called from Derived.");
    }
}

class SpecialDerived : Derived
{
    /*
    public override void Say()
    {
        Console.WriteLine("Called from Special Derived.");
        base.Say();
    }
    */

    protected override void CustomSay()
    {
        Console.WriteLine("Called from Special Derived.");
    }
}

Chỉnh sửa để phù hợp với bản cập nhật của bạn.
JoshJordan

Câu trả lời:


106

Chỉ muốn thêm điều này ở đây, vì mọi người vẫn quay lại câu hỏi này ngay cả sau nhiều thời gian. Tất nhiên đó là cách làm không tốt, nhưng vẫn có thể (về nguyên tắc) làm những gì tác giả muốn với:

class SpecialDerived : Derived
{
    public override void Say()
    {
        Console.WriteLine("Called from Special Derived.");
        var ptr = typeof(Base).GetMethod("Say").MethodHandle.GetFunctionPointer();            
        var baseSay = (Action)Activator.CreateInstance(typeof(Action), this, ptr);
        baseSay();            
    }
}

46
Tôi thực sự thích câu trả lời này vì câu hỏi không phải là nó có được khuyến khích hay không hay nó có phải là một ý tưởng hay hay không, câu hỏi là liệu có cách nào để làm hay không và nếu có thì cách đó là gì.
Shavais

3
Đặc biệt hữu ích khi bạn phải xử lý các framework / libs thiếu khả năng mở rộng. Ví dụ, tôi chưa bao giờ cần dùng đến các giải pháp như vậy cho NHibernate có khả năng mở rộng cao. Nhưng để xử lý Asp.Net Identity, Entity Framework, Asp.Net Mvc, tôi thường xuyên sử dụng các bản hack như vậy để xử lý các tính năng bị thiếu của chúng hoặc các hành vi được mã hóa cứng không phù hợp với nhu cầu của tôi.
Frédéric

2
Cảm ơn vì điều đó! Tôi muốn đề nghị những người khác ngừng trích dẫn nguyên tắc để trả lời câu hỏi. Nó được hỏi cho một lý do, không phải là một lời mắng mỏ. Nếu bạn không biết, thì đừng trả lời nó! Tôi cũng muốn khuyến khích mọi người nổ mã cảnh sát để có thể sẽ không khuyến khích họ đăng những câu trả lời không phải. Nếu sau khi trả lời một câu hỏi, bạn cảm thấy bắt buộc phải trích dẫn các nguyên tắc, thì hãy tiếp tục và đề cập đến nó.
DanW

1
Điều gì sẽ xảy ra nếu chúng ta cần giá trị trả về từ hàm cơ bản?
Perkins

2
Đừng bận tâm, đã tìm ra nó. Truyền đến Func<stuff>thay vìAction
Perkins

92

Đây là một thực hành lập trình không tốt và không được phép trong C #. Đó là một thực hành lập trình tồi vì

  • Các chi tiết của cơ sở lớn là chi tiết triển khai của cơ sở; bạn không nên dựa vào chúng. Lớp cơ sở đang cung cấp phần phủ trừu tượng của grandbase; bạn nên sử dụng sự trừu tượng đó, không xây dựng một đường vòng để tránh nó.

  • Để minh họa một ví dụ cụ thể về điểm trước: nếu được cho phép, mẫu này sẽ là một cách khác để làm cho mã dễ bị lỗi lớp cơ sở giòn. Giả sử dẫn Cxuất từ Bđó suy ra từ A. Mã được Csử dụng base.baseđể gọi một phương thức của A. Sau đó, tác giả của việc Bnhận ra rằng họ đã đặt quá nhiều thiết bị vào lớp B, và một cách tiếp cận tốt hơn là tạo ra lớp trung gian B2có nguồn gốc ABxuất phát từ B2. Sau thay đổi đó, mã trong Cđang gọi một phương thức trong B2, không phải trong A, bởi vì Ctác giả của đã đưa ra giả định rằng chi tiết triển khai B, cụ thể là lớp cơ sở trực tiếp của nó làA, sẽ không bao giờ thay đổi. Nhiều quyết định thiết kế trong C # là để giảm thiểu khả năng xảy ra các loại hỏng hóc cơ bản giòn; quyết định đưa ra base.basebất hợp pháp hoàn toàn ngăn chặn hương vị cụ thể của mô hình thất bại đó.

  • Bạn xuất phát từ cơ sở của mình bởi vì bạn thích những gì nó làm và muốn sử dụng lại và mở rộng nó. Nếu bạn không thích những gì nó làm và muốn làm việc xung quanh nó hơn là làm việc với nó, thì tại sao bạn lại bắt nguồn từ nó ngay từ đầu? Tự mình bắt nguồn từ cơ sở lớn nếu đó là chức năng bạn muốn sử dụng và mở rộng.

  • Cơ sở có thể yêu cầu một số bất biến nhất định cho mục đích bảo mật hoặc nhất quán ngữ nghĩa được duy trì bởi các chi tiết về cách cơ sở sử dụng các phương thức của grandbase. Việc cho phép một lớp dẫn xuất của cơ sở bỏ qua mã duy trì những bất biến đó có thể đặt cơ sở vào trạng thái không nhất quán, bị hỏng.


4
@Jadoon: Vậy thì thích thành phần hơn là kế thừa. Lớp của bạn có thể lấy một thể hiện của Base hoặc GrandBase, và sau đó lớp có thể trì hoãn chức năng cho thể hiện đó.
Eric Lippert

4
@BlackOverlord: Vì bạn cảm thấy rất hứng thú với chủ đề này, tại sao không viết một câu trả lời của riêng bạn cho câu hỏi bảy tuổi này? Bằng cách đó, tất cả chúng ta sẽ được hưởng lợi từ sự thông thái của bạn về chủ đề này, và sau đó bạn sẽ viết hai câu trả lời trên StackOverflow, nhân đôi tổng đóng góp của bạn. Đó là một đôi bên cùng có lợi.
Eric Lippert

4
@Eric Lippert: Có hai lý do khiến tôi không viết câu trả lời của riêng mình: thứ nhất, tôi không biết phải làm thế nào, đó là lý do tại sao tôi tìm thấy chủ đề này. Thứ hai, có một câu trả lời toàn diện của Evk ở trang này.
BlackOverlord

3
@DanW: Tôi biết câu trả lời; đó là câu đầu tiên trong câu trả lời của tôi: tính năng mong muốn không được phép sử dụng trong C # vì đó là một cách lập trình không tốt . Làm thế nào để bạn làm điều này trong C #? Bạn không. Ý tưởng rằng tôi có thể không biết câu trả lời cho câu hỏi này là một câu hỏi thú vị, nhưng chúng tôi sẽ để điều đó trôi qua mà không cần bình luận thêm. Bây giờ, nếu bạn thấy câu trả lời này không hài lòng, tại sao không viết câu trả lời của riêng bạn mà bạn nghĩ rằng công việc tốt hơn? Bằng cách đó, tất cả chúng ta đều học hỏi được từ sự khôn ngoan và kinh nghiệm của bạn, và bạn cũng sẽ tăng gấp đôi số câu trả lời mà bạn đã đăng trong năm nay.
Eric Lippert

11
@EricLippert điều gì sẽ xảy ra nếu mã cơ sở bị sai sót và cần được ghi đè giống như điều khiển của bên thứ 3? Và việc triển khai bao gồm một lệnh gọi đến grandbase? Đối với các ứng dụng trong thế giới thực, nó có thể có tính chất quan trọng và cần cập nhật nóng, do đó, chờ nhà cung cấp bên thứ 3 có thể không phải là một lựa chọn. Thực tiễn xấu so với thực tế của môi trường sản xuất.
Shiv

22

Bạn không thể từ C #. Từ IL, điều này thực sự được hỗ trợ. Bạn có thể thực hiện một lệnh gọi không hợp lệ tới bất kỳ lớp cha mẹ nào của mình ... nhưng xin đừng. :)


11

Câu trả lời (mà tôi biết không phải là thứ bạn đang tìm kiếm) là:

class SpecialDerived : Base
{
    public override void Say()
    {
        Console.WriteLine("Called from Special Derived.");
        base.Say();
    }
}

Sự thật là bạn chỉ có tương tác trực tiếp với lớp mà bạn kế thừa. Hãy coi lớp đó như một lớp - cung cấp nhiều hay ít chức năng của nó hoặc chức năng của lớp cha của nó như nó mong muốn cho các lớp dẫn xuất của nó.

BIÊN TẬP:

Bản chỉnh sửa của bạn hoạt động, nhưng tôi nghĩ tôi sẽ sử dụng một cái gì đó như thế này:

class Derived : Base
{
    protected bool _useBaseSay = false;

    public override void Say()
    {
        if(this._useBaseSay)
            base.Say();
        else
            Console.WriteLine("Called from Derived");
    }
}

Tất nhiên, trong một triển khai thực tế, bạn có thể làm điều gì đó tương tự như thế này để có thể mở rộng và bảo trì:

class Derived : Base
{
    protected enum Mode
    {
        Standard,
        BaseFunctionality,
        Verbose
        //etc
    }

    protected Mode Mode
    {
        get; set;
    }

    public override void Say()
    {
        if(this.Mode == Mode.BaseFunctionality)
            base.Say();
        else
            Console.WriteLine("Called from Derived");
    }
}

Sau đó, các lớp dẫn xuất có thể kiểm soát trạng thái của cha mẹ chúng một cách thích hợp.


3
Tại sao không chỉ viết một hàm được bảo vệ trong Derivedđó các lệnh gọi Base.Say, để nó có thể được gọi từ đó SpecialDerived? Đơn giản hơn, không?
nawfal

7

Tại sao không chỉ đơn giản truyền lớp con đến một lớp cha cụ thể và sau đó gọi việc triển khai cụ thể? Đây là một tình huống trường hợp đặc biệt và một giải pháp trường hợp đặc biệt nên được sử dụng. Tuy nhiên, bạn sẽ phải sử dụng newtừ khóa trong các phương thức con.

public class SuperBase
{
    public string Speak() { return "Blah in SuperBase"; }
}

public class Base : SuperBase
{
    public new string Speak() { return "Blah in Base"; }
}

public class Child : Base
{
    public new string Speak() { return "Blah in Child"; }
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Child childObj = new Child();

        Console.WriteLine(childObj.Speak());

        // casting the child to parent first and then calling Speak()
        Console.WriteLine((childObj as Base).Speak()); 

        Console.WriteLine((childObj as SuperBase).Speak());
    }
}

2
Nếu bạn có một engine không thể biết về base hoặc child, và speak cần hoạt động chính xác khi được engine đó gọi, thì speak cần là một bản ghi đè, không phải là một cái mới. Nếu con cần 99% chức năng của base, nhưng trong một trường hợp nói, nó cần chức năng của superbase ... đó là tình huống mà tôi hiểu OP sẽ nói đến, trong trường hợp đó phương pháp này sẽ không hoạt động. Nó không phải là hiếm và những lo ngại về bảo mật đã dẫn đến hành vi của C # thường không thực sự đáng quan tâm.
Shavais

Chỉ cần lưu ý trong trường hợp điều khiển và chuỗi cuộc gọi sự kiện, các phương thức thường được bảo vệ và do đó không thể truy cập như thế này.
Shiv

2
Điều này không sử dụng tính năng kế thừa, bạn cũng có thể đặt cho mỗi Speak một tên hoàn toàn duy nhất.
Nick Sotiros

yeah nó không phải là 100% thừa kế nhưng nó là sử dụng giao diện từ cha mẹ
Kruczkowski

5
public class A
{
    public int i = 0;
    internal virtual void test()
    {
        Console.WriteLine("A test");
    }
}

public class B : A
{
    public new int i = 1;
    public new void test()
    {
        Console.WriteLine("B test");
    }
}

public class C : B
{
    public new int i = 2;
    public new void test()
    {
        Console.WriteLine("C test - ");
        (this as A).test(); 
    }
}

4

Bạn cũng có thể tạo một hàm đơn giản trong lớp dẫn xuất cấp đầu tiên, để gọi hàm cơ sở lớn


Chính xác là như vậy, và điều này bảo tồn toàn bộ sơ đồ trừu tượng mà mọi người đều lo lắng về nó, và nó làm nổi bật cách các lược đồ trừu tượng đôi khi rắc rối hơn giá trị của chúng.
Shavais

3

2c của tôi cho việc này là triển khai chức năng bạn yêu cầu để được gọi trong một lớp bộ công cụ và gọi chức năng đó từ bất cứ đâu bạn cần:

// Util.cs
static class Util 
{
    static void DoSomething( FooBase foo ) {}
}

// FooBase.cs
class FooBase
{
    virtual void Do() { Util.DoSomething( this ); }
}


// FooDerived.cs
class FooDerived : FooBase
{
    override void Do() { ... }
}

// FooDerived2.cs
class FooDerived2 : FooDerived
{
    override void Do() { Util.DoSomething( this ); }
}

Điều này yêu cầu một số suy nghĩ như để truy cập đặc quyền, bạn có thể cần thêm một số internalphương thức truy cập để hỗ trợ chức năng.


1

Trong trường hợp bạn không có quyền truy cập vào nguồn lớp dẫn xuất, nhưng cần tất cả nguồn của lớp dẫn xuất bên cạnh phương thức hiện tại, thì tôi khuyên bạn cũng nên tạo một lớp dẫn xuất và gọi việc triển khai lớp dẫn xuất.

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

//No access to the source of the following classes
public class Base
{
     public virtual void method1(){ Console.WriteLine("In Base");}
}
public class Derived : Base
{
     public override void method1(){ Console.WriteLine("In Derived");}
     public void method2(){ Console.WriteLine("Some important method in Derived");}
}

//Here should go your classes
//First do your own derived class
public class MyDerived : Base
{         
}

//Then derive from the derived class 
//and call the bass class implementation via your derived class
public class specialDerived : Derived
{
     public override void method1()
     { 
          MyDerived md = new MyDerived();
          //This is actually the base.base class implementation
          MyDerived.method1();  
     }         
}

0

Như có thể thấy từ các bài viết trước, người ta có thể tranh luận rằng nếu chức năng của lớp cần được phá vỡ thì có gì đó sai trong kiến ​​trúc lớp. Điều đó có thể đúng, nhưng không phải lúc nào người ta cũng có thể tái cấu trúc hoặc tái cấu trúc lại cấu trúc lớp trong một dự án lớn đã trưởng thành. Các cấp độ quản lý thay đổi khác nhau có thể là một vấn đề, nhưng để giữ cho chức năng hiện có hoạt động như cũ sau khi cấu trúc lại không phải lúc nào cũng là một nhiệm vụ nhỏ, đặc biệt nếu áp dụng các hạn chế về thời gian. Đối với một dự án trưởng thành, có thể là một việc khá khó khăn để giữ cho các bài kiểm tra hồi quy khác nhau không vượt qua sau khi tái cấu trúc mã; thường có những "điều kỳ quặc" mờ mịt lộ ra. Chúng tôi đã gặp sự cố tương tự trong một số trường hợp, chức năng kế thừa không được thực thi (hoặc phải thực hiện điều gì đó khác). Cách tiếp cận chúng tôi đã làm theo bên dưới, là đặt mã cơ sở cần được loại trừ trong một hàm ảo riêng biệt. Sau đó, chức năng này có thể được ghi đè trong lớp dẫn xuất và chức năng bị loại trừ hoặc thay đổi. Trong ví dụ này, "Văn bản 2" có thể được ngăn chặn từ đầu ra trong lớp dẫn xuất.

public class Base
{
    public virtual void Foo()
    {
        Console.WriteLine("Hello from Base");
    }
}

public class Derived : Base
{
    public override void Foo()
    {
        base.Foo();
        Console.WriteLine("Text 1");
        WriteText2Func();
        Console.WriteLine("Text 3");
    }

    protected virtual void WriteText2Func()
    {  
        Console.WriteLine("Text 2");  
    }
}

public class Special : Derived
{
    public override void WriteText2Func()
    {
        //WriteText2Func will write nothing when 
        //method Foo is called from class Special.
        //Also it can be modified to do something else.
    }
}

0

Có vẻ như có rất nhiều câu hỏi xung quanh việc kế thừa một phương thức thành viên từ Lớp cha, ghi đè nó trong Lớp thứ hai, sau đó gọi lại phương thức của nó từ Lớp cháu. Tại sao không chỉ kế thừa các thành viên của ông bà xuống các cháu?

class A
{
    private string mystring = "A";    
    public string Method1()
    {
        return mystring;
    }
}

class B : A
{
    // this inherits Method1() naturally
}

class C : B
{
    // this inherits Method1() naturally
}


string newstring = "";
A a = new A();
B b = new B();
C c = new C();
newstring = a.Method1();// returns "A"
newstring = b.Method1();// returns "A"
newstring = c.Method1();// returns "A"

Có vẻ đơn giản .... đứa cháu kế thừa phương pháp của ông bà đây. Hãy nghĩ về nó ..... đó là cách "Object" và các thành viên của nó như ToString () được kế thừa cho tất cả các lớp trong C #. Tôi nghĩ rằng Microsoft đã không làm tốt công việc giải thích sự kế thừa cơ bản. Có quá nhiều tập trung vào tính đa hình và triển khai. Khi tôi tìm hiểu tài liệu của họ, không có ví dụ nào về ý tưởng cơ bản này. :(


-2

Nếu bạn muốn truy cập vào dữ liệu lớp cơ sở, bạn phải sử dụng từ khóa "this" hoặc bạn sử dụng từ khóa này làm tham chiếu cho lớp.

namespace thiskeyword
{
    class Program
    {
        static void Main(string[] args)
        {
            I i = new I();
            int res = i.m1();
            Console.WriteLine(res);
            Console.ReadLine();
        }
    }

    public class E
    {
        new public int x = 3;
    }

    public class F:E
    {
        new public int x = 5;
    }

    public class G:F
    {
        new public int x = 50;
    }

    public class H:G
    {
        new public int x = 20;
    }

    public class I:H
    {
        new public int x = 30;

        public int m1()
        {
           // (this as <classname >) will use for accessing data to base class

            int z = (this as I).x + base.x + (this as G).x + (this as F).x + (this as E).x; // base.x refer to H
            return z;
        }
    }
}
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.