จะเรียก 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();
    }
}

ผลลัพธ์คือ:

เรียกจากผลพิเศษ
เรียกจาก Derived. / * ไม่คาดหวัง * /
เรียกจากฐาน

ฉันจะเขียนคลาส SpecialDerived ใหม่ได้อย่างไรเพื่อไม่ให้เมธอดของ "Derived" ระดับกลางถูกเรียก

UPDATE: เหตุผลที่ฉันต้องการสืบทอดจาก Derived แทน Base is Derived class มีการใช้งานอื่น ๆ มากมาย เนื่องจากฉันทำbase.base.method()ที่นี่ไม่ได้ฉันจึงเดาว่าวิธีที่ดีที่สุดคือทำสิ่งต่อไปนี้?

// ไม่สามารถเปลี่ยนซอร์สโค้ด

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

แก้ไขให้ตรงกับการอัปเดตของคุณ
JoshJordan

คำตอบ:


106

เพียงต้องการเพิ่มที่นี่เนื่องจากผู้คนยังคงกลับมาที่คำถามนี้แม้จะผ่านไปหลายครั้ง แน่นอนว่าเป็นการปฏิบัติที่ไม่ดี แต่ก็ยังเป็นไปได้ (โดยหลักการ) ที่จะทำในสิ่งที่ผู้เขียนต้องการด้วย:

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
ฉันชอบคำตอบนี้มากเพราะคำถามไม่ใช่ว่าจะแนะนำหรือไม่หรือเป็นความคิดที่ดีหรือไม่คำถามคือมีวิธีทำหรือไม่และถ้าเป็นเช่นนั้นจะเป็นอย่างไร
Shavais

3
มีประโยชน์อย่างยิ่งเมื่อคุณต้องจัดการกับเฟรมเวิร์ก / lib ที่ขาดความสามารถในการขยาย ตัวอย่างเช่นฉันไม่จำเป็นต้องหันไปใช้โซลูชันดังกล่าวสำหรับ NHibernate ซึ่งสามารถขยายได้สูง แต่สำหรับการจัดการกับ Asp.Net Identity, Entity Framework, Asp.Net Mvc ฉันมักจะใช้แฮ็กดังกล่าวเพื่อจัดการกับคุณสมบัติที่ขาดหายไปหรือพฤติกรรมที่เข้ารหัสยากที่ไม่เหมาะสมกับความต้องการของฉัน
Frédéric

2
ขอบคุณสำหรับสิ่งนั้น! ฉันต้องการพูดถึงคนอื่น ๆ ให้หยุดการอ้างแนวทางในการตอบคำถาม มันถามด้วยเหตุผลไม่ใช่ดุ ถ้าไม่รู้ก็ไม่ตอบ! ฉันยังอยากจะสนับสนุนให้ทุกคนระเบิดรหัสตำรวจดังนั้นบางทีอาจเป็นการกีดกันพวกเขาจากการโพสต์ที่ไม่ใช่คำตอบ หากหลังจากตอบคำถามแล้วคุณรู้สึกถูกบังคับให้อ้างแนวทางจากนั้นก็พูดถึงมัน
DanW

1
จะเกิดอะไรขึ้นถ้าเราต้องการค่าส่งคืนจากฟังก์ชันพื้นฐาน?
Perkins

2
ไม่เป็นไรคิดออก ส่งถึงFunc<stuff>แทนAction
Perkins

92

นี่เป็นการฝึกเขียนโปรแกรมที่ไม่ดีและไม่ได้รับอนุญาตใน C # เป็นการฝึกเขียนโปรแกรมที่ไม่ดีเพราะ

  • รายละเอียดของ grandbase เป็นรายละเอียดการใช้งานของฐาน คุณไม่ควรพึ่งพาพวกเขา คลาสฐานกำลังจัดเตรียมสิ่งที่เป็นนามธรรมส่วนบนของแกรนด์เบส คุณควรใช้สิ่งที่เป็นนามธรรมไม่ใช่สร้างทางเลี่ยงเพื่อหลีกเลี่ยง

  • เพื่อแสดงตัวอย่างเฉพาะของจุดก่อนหน้า: หากอนุญาตรูปแบบนี้จะเป็นอีกวิธีหนึ่งในการทำให้โค้ดมีความอ่อนไหวต่อความล้มเหลวของระดับพื้นฐาน สมมติว่าCมาจากที่เกิดขึ้นจากB Aรหัสที่Cใช้ในการเรียกวิธีการของbase.base Aจากนั้นผู้เขียนBตระหนักว่าพวกเขาได้ใส่เกียร์มากเกินไปในชั้นเรียนBและวิธีการที่ดีคือการทำให้ระดับกลางB2ที่มาจากAและมาจากB B2หลังจากการเปลี่ยนแปลงนั้นรหัสในCกำลังเรียกใช้เมธอดในB2ไม่ใช่ในAเนื่องจากCผู้เขียนตั้งสมมติฐานว่ารายละเอียดการนำไปใช้งานBกล่าวคือคลาสฐานโดยตรงคือAจะไม่มีวันเปลี่ยนแปลง การตัดสินใจออกแบบหลายอย่างใน C # คือการลดโอกาสที่จะเกิดความล้มเหลวของฐานที่เปราะบางประเภทต่างๆ การตัดสินใจที่จะทำbase.baseผิดกฎหมายโดยสิ้นเชิงจะป้องกันรสชาติเฉพาะของรูปแบบความล้มเหลวนั้น

  • คุณได้มาจากฐานของคุณเพราะคุณชอบในสิ่งที่ทำและต้องการนำกลับมาใช้และต่อยอด หากคุณไม่ชอบสิ่งที่ทำและต้องการหลีกเลี่ยงมากกว่าทำงานกับมันทำไมคุณถึงได้มาจากสิ่งนี้ตั้งแต่แรก? มาจาก grandbase ด้วยตัวคุณเองหากนั่นคือฟังก์ชันที่คุณต้องการใช้และขยาย

  • ฐานอาจต้องการค่าคงที่บางอย่างเพื่อวัตถุประสงค์ด้านความปลอดภัยหรือความสอดคล้องทางความหมายที่คงไว้โดยรายละเอียดว่าฐานใช้วิธีการของ grandbase อย่างไร การอนุญาตให้คลาสที่ได้รับของฐานข้ามรหัสที่รักษาค่าคงที่เหล่านั้นอาจทำให้ฐานอยู่ในสถานะที่ไม่สอดคล้องกันและเสียหาย


4
@Jadoon: จากนั้นชอบองค์ประกอบเพื่อสืบทอด คลาสของคุณสามารถใช้อินสแตนซ์ของ Base หรือ GrandBase จากนั้นคลาสสามารถเลื่อนฟังก์ชันการทำงานไปยังอินสแตนซ์ได้
Eric Lippert

4
@BlackOverlord: เนื่องจากคุณรู้สึกอย่างยิ่งกับหัวข้อนี้ทำไมไม่เขียนคำตอบของคุณเองสำหรับคำถามเจ็ดปีนี้ ด้วยวิธีนี้เราทุกคนจะได้รับประโยชน์จากภูมิปัญญาของคุณในเรื่องนี้จากนั้นคุณจะได้เขียนคำตอบสองข้อใน StackOverflow ซึ่งจะเพิ่มการมีส่วนร่วมของคุณเป็นสองเท่า นั่นคือ win-win
Eric Lippert

4
@ เอริกลิปเพิร์ต: มีสองเหตุผลที่ฉันไม่ได้เขียนคำตอบของตัวเอง: ประการแรกฉันไม่รู้ว่าจะทำอย่างไรทำไมฉันถึงพบหัวข้อนี้ ประการที่สองมีคำตอบที่ครอบคลุมโดย Evk ในหน้านี้
BlackOverlord

3
@ DanW: ฉันรู้คำตอบ; มันเป็นประโยคที่แรกของคำตอบของฉัน: คุณลักษณะที่ต้องการไม่ได้รับอนุญาตใน C # เพราะมันเป็นวิธีการเขียนโปรแกรมที่ไม่ดี คุณทำสิ่งนี้ใน C # ได้อย่างไร? คุณไม่ทำ ความคิดที่ว่าฉันอาจไม่รู้คำตอบสำหรับคำถามนี้เป็นเรื่องที่น่าขบขัน แต่เราจะปล่อยให้มันผ่านไปโดยไม่แสดงความคิดเห็นเพิ่มเติม ตอนนี้หากคุณพบว่าคำตอบนี้ไม่น่าพอใจทำไมไม่เขียนคำตอบของคุณเองที่คุณคิดว่าได้งานที่ดีกว่า ด้วยวิธีนี้เราทุกคนจะเรียนรู้จากภูมิปัญญาและประสบการณ์ของคุณและคุณจะเพิ่มจำนวนคำตอบที่คุณโพสต์ในปีนี้เป็นสองเท่า
Eric Lippert

11
@EricLippert จะเกิดอะไรขึ้นถ้ารหัสพื้นฐานมีข้อบกพร่องและจำเป็นต้องถูกแทนที่เช่นการควบคุมของบุคคลที่สาม? และการใช้งานรวมถึงการโทรไปยัง grandbase? สำหรับแอปพลิเคชันในโลกแห่งความเป็นจริงอาจมีลักษณะสำคัญและจำเป็นต้องมีโปรแกรมแก้ไขด่วนดังนั้นการรอผู้จำหน่ายบุคคลที่สามจึงอาจไม่ใช่ทางเลือก การปฏิบัติที่ไม่ดีเทียบกับความเป็นจริงของสภาพแวดล้อมการผลิต
Shiv

22

คุณไม่สามารถจาก C # จาก IL สิ่งนี้ได้รับการสนับสนุนจริง คุณสามารถโทรหาชั้นเรียนผู้ปกครองของคุณได้อย่างไม่ถูกต้อง ... แต่โปรดอย่า :)


11

คำตอบ (ซึ่งฉันรู้ว่าไม่ใช่สิ่งที่คุณกำลังมองหา) คือ:

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

ความจริงก็คือคุณมีปฏิสัมพันธ์โดยตรงกับคลาสที่คุณสืบทอดมาเท่านั้น คิดว่าคลาสนั้นเป็นเลเยอร์ - ให้มากหรือน้อยหรือน้อยมากหรือฟังก์ชันการทำงานของผู้ปกครองตามที่ต้องการสำหรับคลาสที่ได้รับมา

แก้ไข:

การแก้ไขของคุณใช้งานได้ แต่ฉันคิดว่าฉันจะใช้สิ่งนี้:

class Derived : Base
{
    protected bool _useBaseSay = false;

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

แน่นอนว่าในการนำไปใช้งานจริงคุณอาจทำสิ่งนี้เพิ่มเติมเพื่อความสามารถในการขยายและการบำรุงรักษา:

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

จากนั้นชั้นเรียนที่ได้รับจะสามารถควบคุมสถานะของผู้ปกครองได้อย่างเหมาะสม


3
ทำไมไม่เพียงแค่เขียนฟังก์ชันที่มีการป้องกันในการDerivedโทรBase.Sayเพื่อที่จะสามารถเรียกใช้จากSpecialDerived? ง่ายกว่าไม่?
nawfal

7

ทำไมไม่เพียงแค่ส่งคลาสย่อยไปยังคลาสพาเรนต์ที่เฉพาะเจาะจงและเรียกใช้การใช้งานเฉพาะ นี่เป็นสถานการณ์กรณีพิเศษและควรใช้วิธีแก้ปัญหากรณีพิเศษ คุณจะต้องใช้newคำหลักในวิธีการเด็กแม้ว่า

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
หากคุณมีเครื่องมือที่ไม่สามารถรู้เกี่ยวกับฐานหรือเด็กและการพูดต้องทำงานได้อย่างถูกต้องเมื่อเรียกโดยเครื่องมือนั้นการพูดจะต้องมีการลบล้างไม่ใช่สิ่งใหม่ หากเด็กต้องการฟังก์ชันการทำงานของฐาน 99% แต่ในกรณีที่พูดได้ก็ต้องการฟังก์ชันของ superbase ... นั่นเป็นสถานการณ์ที่ฉันเข้าใจว่า OP จะพูดถึงซึ่งในกรณีนี้วิธีนี้จะใช้ไม่ได้ ไม่ใช่เรื่องแปลกและความกังวลด้านความปลอดภัยที่ก่อให้เกิดพฤติกรรมของ C # มักไม่ค่อยเกี่ยวข้องมากนัก
Shavais

โปรดทราบในกรณีของการควบคุมและสายเหตุการณ์วิธีนี้มักจะได้รับการป้องกันดังนั้นจึงไม่สามารถเข้าถึงได้เช่นนี้
Shiv

2
สิ่งนี้ไม่ได้ใช้การสืบทอดคุณอาจตั้งชื่อที่ไม่ซ้ำกันให้กับ Speak แต่ละคนได้เช่นกัน
Nick Sotiros

ใช่มันไม่ใช่มรดก 100% แต่ใช้อินเทอร์เฟซจากผู้ปกครอง
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

คุณยังสามารถสร้างฟังก์ชันอย่างง่ายในคลาสที่ได้รับระดับแรกเพื่อเรียกฟังก์ชันฐานใหญ่


แน่นอนและสิ่งนี้รักษารูปแบบนามธรรมทั้งหมดที่ทุกคนกังวลและเน้นย้ำว่าบางครั้งรูปแบบนามธรรมก็มีปัญหามากกว่าที่ควรจะเป็น
Shavais

3

2c ของฉันสำหรับสิ่งนี้คือการใช้ฟังก์ชันที่คุณต้องการเพื่อเรียกใช้ในคลาสชุดเครื่องมือและเรียกใช้จากทุกที่ที่คุณต้องการ:

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

สิ่งนี้ต้องใช้ความคิดในการเข้าถึงสิทธิ์คุณอาจต้องเพิ่มinternalวิธีการเข้าถึงเพื่ออำนวยความสะดวกในการทำงาน


1

ในกรณีที่คุณไม่สามารถเข้าถึงซอร์สคลาสที่ได้รับมา แต่ต้องการซอร์สทั้งหมดของคลาสที่ได้รับนอกเหนือจากเมธอดปัจจุบันฉันขอแนะนำให้คุณทำคลาสที่ได้รับและเรียกใช้คลาสที่ได้รับ

นี่คือตัวอย่าง:

//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

ดังที่เห็นได้จากโพสต์ก่อนหน้านี้เราสามารถโต้แย้งได้ว่าหากจำเป็นต้องหลีกเลี่ยงการทำงานของคลาสแล้วมีบางอย่างผิดปกติในสถาปัตยกรรมคลาส นั่นอาจเป็นความจริง แต่ก็ไม่สามารถปรับโครงสร้างหรือปรับโครงสร้างคลาสใหม่ในโครงการที่โตเต็มที่ได้ การจัดการการเปลี่ยนแปลงในระดับต่างๆอาจเป็นปัญหาหนึ่ง แต่เพื่อให้ฟังก์ชันการทำงานที่มีอยู่ทำงานเหมือนเดิมหลังจากการปรับโครงสร้างใหม่ไม่ใช่งานที่ไม่สำคัญเสมอไปโดยเฉพาะอย่างยิ่งหากมีข้อ จำกัด ด้านเวลา ในโครงการที่ครบกำหนดแล้วอาจเป็นเรื่องที่ต้องดำเนินการเพื่อป้องกันไม่ให้การทดสอบการถดถอยต่างๆผ่านไปหลังจากการปรับโครงสร้างรหัส มักจะมี "สิ่งแปลกประหลาด" ที่คลุมเครือปรากฏขึ้น เรามีปัญหาที่คล้ายกันในบางกรณีฟังก์ชันที่สืบทอดมาไม่ควรดำเนินการ (หรือควรดำเนินการอย่างอื่น) แนวทางที่เราทำตามด้านล่างนี้ คือการใส่รหัสพื้นฐานที่จำเป็นต้องยกเว้นในฟังก์ชันเสมือนที่แยกต่างหาก จากนั้นฟังก์ชันนี้สามารถถูกแทนที่ในคลาสที่ได้รับและฟังก์ชันจะถูกยกเว้นหรือเปลี่ยนแปลง ในตัวอย่างนี้ "Text 2" สามารถป้องกันไม่ให้เอาต์พุตในคลาสที่ได้รับ

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

ดูเหมือนจะมีคำถามมากมายเกี่ยวกับการสืบทอดเมธอดสมาชิกจากคลาสปู่ย่าตายายโดยแทนที่มันในคลาสที่สองจากนั้นเรียกใช้วิธีการอีกครั้งจากคลาสหลาน ทำไมไม่เพียงแค่สืบทอดสมาชิกของปู่ย่าตายายลงไปถึงรุ่นหลาน?

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"

ดูเหมือนง่าย .... หลานสืบทอดวิธีการปู่ย่าที่นี่ ลองคิดดู ..... นั่นคือวิธีที่ "Object" และสมาชิกของมันเช่น ToString () ถูกสืบทอดไปยังคลาสทั้งหมดใน C # ฉันคิดว่า Microsoft ยังอธิบายมรดกพื้นฐานได้ไม่ดีนัก มีการให้ความสำคัญกับความหลากหลายและการนำไปใช้งานมากเกินไป เมื่อฉันอ่านเอกสารของพวกเขาไม่มีตัวอย่างของแนวคิดพื้นฐานนี้ :(


-2

หากคุณต้องการเข้าถึงข้อมูลคลาสพื้นฐานคุณต้องใช้คีย์เวิร์ด "this" หรือคุณใช้คีย์เวิร์ดนี้อ้างอิงสำหรับคลาส

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;
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.