¿Cómo llamar a base.base.method ()?

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

El resultado es:

Llamado de Derivado Especial.
Llamado de derivado. / * esto no se espera * /
Llamado desde la base.

¿Cómo puedo reescribir la clase SpecialDerived para que no se invoque el método "Derivado" de la clase media?

ACTUALIZACIÓN: La razón por la que quiero heredar de Derived en lugar de Base es que la clase Derived contiene muchas otras implementaciones. Como no puedo hacer base.base.method()aquí, supongo que la mejor manera es hacer lo siguiente.

// No se puede cambiar el código fuente

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.");
    }
}
ARIZONA.
fuente
Edición para que coincida con su actualización.
JoshJordan

Respuestas:

106

Solo quiero agregar esto aquí, ya que las personas aún vuelven a esta pregunta incluso después de mucho tiempo. Por supuesto, es una mala práctica, pero aún es posible (en principio) hacer lo que el autor quiere con:

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();            
    }
}
Evk
fuente
46
Realmente me gusta esta respuesta porque la pregunta no era si se recomienda o no o si es una buena idea, la pregunta era si hay una forma de hacerlo y, de ser así, cuál es esa forma.
Shavais
3
Especialmente útil cuando tienes que lidiar con frameworks / libs que carecen de extensibilidad. Por ejemplo, nunca había necesitado recurrir a tales soluciones para NHibernate, que es altamente extensible. Pero por tratar con Asp.Net Identity, Entity Framework, Asp.Net Mvc, regularmente termino usando tales hacks para manejar sus características faltantes o comportamientos codificados inadecuados para mis necesidades.
Frédéric
2
¡Gracias por eso! Me gustaría mencionar a las otras personas que dejen de citar las pautas en respuesta a una pregunta. Fue pedido por una razón, no un regaño. Si no lo sabes, ¡no lo contestes! También me gustaría animar a todos a criticar al código policial, por lo que tal vez los desaliente a publicar no respuestas. Si después de responder una pregunta, se siente obligado a citar las pautas, continúe y mencione.
DanW
1
¿Qué pasa si necesitamos el valor de retorno de la función subyacente?
Perkins
2
No te preocupes, ya lo resolví. Emitido en Func<stuff>lugar deAction
Perkins
92

Esta es una mala práctica de programación y no está permitida en C #. Es una mala práctica de programación porque

  • Los detalles de la base son detalles de implementación de la base; no deberías depender de ellos. La clase base está proporcionando una abstracción de la gran base; deberías usar esa abstracción, no construir un bypass para evitarlo.

  • Para ilustrar un ejemplo específico del punto anterior: si se permite, este patrón sería otra forma de hacer que el código sea susceptible a fallas de clase frágil. Supongamos que Cderiva de lo Bque deriva A. Código en Cusos base.basepara llamar a un método de A. Luego, el autor se Bda cuenta de que han puesto demasiado equipo en la clase B, y un mejor enfoque es hacer una clase intermedia B2que se derive Ay se Bderive de B2. Después de ese cambio, el código in Cinvoca un método en B2, no en A, porque Cel autor asumió que la implementación detalla B, es decir, que su clase base directa esA, nunca cambiaría Muchas decisiones de diseño en C # son para mitigar la probabilidad de varios tipos de fallas de base frágil; la decisión de hacer base.baseilegal evita por completo este sabor particular de ese patrón de falla.

  • Derivó de su base porque le gusta lo que hace y quiere reutilizarlo y extenderlo. Si no le gusta lo que hace y quiere solucionarlo en lugar de trabajar con él, ¿por qué se derivó de él en primer lugar? Derive usted mismo de la base de datos si esa es la funcionalidad que desea usar y ampliar.

  • La base puede requerir ciertos invariantes por razones de seguridad o de consistencia semántica que se mantienen con los detalles de cómo la base usa los métodos de la base. Permitir que una clase derivada de la base omita el código que mantiene esos invariantes podría poner a la base en un estado inconsistente y corrupto.

Eric Lippert
fuente
44
@Jadoon: Entonces prefiera la composición a la herencia. Su clase puede tomar una instancia de Base o GrandBase, y luego puede diferir la funcionalidad de la instancia.
Eric Lippert
44
@BlackOverlord: Ya que te sientes fuertemente sobre este tema, ¿por qué no escribes tu propia respuesta a esta pregunta de siete años? De esa forma, todos nos beneficiaremos de su sabiduría sobre este tema, y ​​luego habría escrito dos respuestas en StackOverflow, duplicando su contribución total. Eso es ganar-ganar.
Eric Lippert
44
@Eric Lippert: Hay dos razones por las que no escribí mi propia respuesta: primero, no sabía cómo hacerlo, por eso encontré este tema. En segundo lugar, hay una respuesta integral de Evk en esta página.
BlackOverlord
3
@DanW: Sé la respuesta; es la primera oración de mi respuesta: la función deseada no está permitida en C # porque es una mala práctica de programación . ¿Cómo se hace esto en C #? Usted no La idea de que podría no saber la respuesta a esta pregunta es divertida, pero dejaremos que eso pase sin más comentarios. Ahora, si encuentra esta respuesta insatisfactoria, ¿por qué no escribe su propia respuesta que cree que funciona mejor? De esa manera, todos aprendemos de su sabiduría y experiencia, y también duplicaría el número de respuestas que ha publicado este año.
Eric Lippert
11
@EricLippert, ¿qué sucede si el código base tiene fallas y debe anularse como un control de un tercero? ¿Y la implementación incluye una llamada a grandbase? Para aplicaciones del mundo real, puede ser de naturaleza crítica y necesita revisión, por lo que esperar al proveedor externo podría no ser una opción. Mala práctica frente a la realidad de los entornos de producción.
Shiv
22

No puedes desde C #. Desde IL, esto es realmente compatible. Puedes hacer una llamada no virtuosa a cualquiera de tus clases para padres ... pero no lo hagas. :)

rh.
fuente
11

La respuesta (que sé que no es lo que estás buscando) es:

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

La verdad es que solo tienes interacción directa con la clase de la que heredas. Piense en esa clase como una capa, proporcionando tanto o tan poca o la funcionalidad de su padre como lo desee a sus clases derivadas.

EDITAR:

Tu edición funciona, pero creo que usaría algo como esto:

class Derived : Base
{
    protected bool _useBaseSay = false;

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

Por supuesto, en una implementación real, puede hacer algo más como esto para ampliar y mantener:

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

Luego, las clases derivadas pueden controlar el estado de sus padres de manera apropiada.

JoshJordan
fuente
3
¿Por qué no simplemente escribir una función protegida en la Derivedque se llama Base.Say, para que se pueda llamar SpecialDerived? Más simple, no?
nawfal
7

¿Por qué no simplemente convertir la clase secundaria en una clase primaria específica e invocar la implementación específica entonces? Esta es una situación de caso especial y se debe utilizar una solución de caso especial. Sin newembargo , tendrá que usar la palabra clave en los métodos secundarios.

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());
    }
}
Kruczkowski
fuente
2
Si tiene un motor que no puede conocer la base o el niño, y hablar necesita funcionar correctamente cuando lo llama ese motor, hablar debe ser una anulación, no un nuevo. Si el niño necesita el 99% de la funcionalidad de la base, pero en el caso de una sola palabra, necesita la funcionalidad de la superbase ... ese es el tipo de situación de la que entiendo que está hablando el OP, en cuyo caso este método no funcionará. No es raro y las preocupaciones de seguridad que han dado lugar al comportamiento de C # generalmente no son realmente muy preocupantes.
Shavais
Solo una nota en el caso de controles y cadenas de llamadas de eventos, los métodos a menudo están protegidos y, por lo tanto, no son accesibles de esta manera.
Shiv
2
Esto no está usando la herencia, también puede darle a cada Speak un nombre completamente único.
Nick Sotiros
sí, no es 100% de herencia, pero está utilizando la interfaz del padre
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(); 
    }
}
Pavel
fuente
3

También puede hacer una función simple en la clase derivada de primer nivel, para llamar a la función de base grande

Rajesh
fuente
Exactamente así, y esto preserva todo el esquema de abstracción por el que todos están tan preocupados, y resalta la forma en que los esquemas de abstracción a veces son más problemáticos de lo que valen.
Shavais
3

Mi 2c para esto es implementar la funcionalidad que necesita para ser llamado en una clase de kit de herramientas y llamarlo desde donde lo necesite:

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

Esto requiere cierta reflexión sobre el privilegio de acceso, es posible que deba agregar algunos internalmétodos de acceso para facilitar la funcionalidad.

Julian Gold
fuente
1

En los casos en que no tenga acceso a la fuente de la clase derivada, pero necesite toda la fuente de la clase derivada además del método actual, le recomendaría que también haga una clase derivada y llame a la implementación de la clase derivada.

Aquí hay un ejemplo:

//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();  
     }         
}
yoel halb
fuente
0

Como se puede ver en publicaciones anteriores, uno puede argumentar que si la funcionalidad de la clase necesita ser burlada, entonces algo está mal en la arquitectura de la clase. Eso puede ser cierto, pero no siempre se puede reestructurar o refactorizar la estructura de clases en un proyecto grande y maduro. Los diversos niveles de gestión de cambios pueden ser un problema, pero mantener la funcionalidad existente funcionando igual después de la refactorización no siempre es una tarea trivial, especialmente si se aplican restricciones de tiempo. En un proyecto maduro, puede ser una tarea difícil evitar que pasen varias pruebas de regresión después de una reestructuración del código; a menudo hay "rarezas" oscuras que aparecen. Tuvimos un problema similar en algunos casos, la funcionalidad heredada no debería ejecutarse (o debería realizar otra cosa). El enfoque que seguimos a continuación, fue poner el código base que debe excluirse en una función virtual separada. Esta función puede ser anulada en la clase derivada y la funcionalidad excluida o alterada. En este ejemplo, se puede evitar que "Texto 2" salga en la clase derivada.

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.
    }
}
Pierre
fuente
0

Parece que hay muchas de estas preguntas que rodean la herencia de un método miembro de una clase de abuelo, anulándolo en una segunda clase y luego llamando a su método nuevamente de una clase de nieto. ¿Por qué no simplemente heredar los miembros de los abuelos hasta los nietos?

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"

Parece simple ... el nieto hereda el método de los abuelos aquí. Piénselo ... así es como "Object" y sus miembros como ToString () se heredan en todas las clases en C #. Creo que Microsoft no ha hecho un buen trabajo al explicar la herencia básica. Hay demasiado énfasis en el polimorfismo y la implementación. Cuando reviso su documentación no hay ejemplos de esta idea tan básica. :(

Stokely
fuente
-2

Si desea acceder a los datos de la clase base, debe usar "esta" palabra clave o usar esta palabra clave como referencia para la clase.

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;
        }
    }
}
ankit ranjan
fuente