¿Es posible anular un método no virtual?

92

¿Hay alguna forma de anular un método no virtual? o algo que dé resultados similares (además de crear un nuevo método para llamar al método deseado)?

Me gustaría anular un método Microsoft.Xna.Framework.Graphics.GraphicsDeviceteniendo en cuenta las pruebas unitarias.

zfedoran
fuente
8
¿Te refieres a sobrecargar o anular ? Sobrecarga = agrega un método con el mismo nombre pero con diferentes parámetros (por ejemplo, diferentes sobrecargas de Console.WriteLine). Override = (aproximadamente) cambia el comportamiento predeterminado del método (por ejemplo, un método Shape.Draw que tiene un comportamiento diferente para Circle, Rectangle, etc.). Siempre puede sobrecargar un método en una clase derivada, pero la invalidación solo se aplica a los métodos virtuales.
itowlson

Respuestas:

112

No, no puede anular un método no virtual. Lo más cercano que puede hacer es ocultar el método creando un newmétodo con el mismo nombre, pero esto no es recomendable ya que rompe los buenos principios de diseño.

Pero incluso ocultar un método no le dará tiempo de ejecución de envío polimórfico de llamadas de método como lo haría una verdadera llamada de método virtual. Considere este ejemplo:

using System;

class Example
{
    static void Main()
    {
        Foo f = new Foo();
        f.M();

        Foo b = new Bar();
        b.M();
    }
}

class Foo
{
    public void M()
    {
        Console.WriteLine("Foo.M");
    }
}

class Bar : Foo
{
    public new void M()
    {
        Console.WriteLine("Bar.M");
    }
}

En este ejemplo, ambas llamadas al Mmétodo print Foo.M. Como se puede ver este enfoque permitirá tener una nueva implementación de un método siempre que la referencia a ese objeto es del tipo derivado correcta, pero que oculta un método de base hace polimorfismo descanso.

Le recomendaría que no oculte los métodos base de esta manera.

Tiendo a ponerme del lado de aquellos que favorecen el comportamiento predeterminado de C # de que los métodos no son virtuales por defecto (a diferencia de Java). Yo iría aún más lejos y diría que las clases también deberían estar selladas por defecto. La herencia es difícil de diseñar correctamente y el hecho de que haya un método que no esté marcado como virtual indica que el autor de ese método nunca tuvo la intención de que el método fuera anulado.

Editar: "envío polimórfico en tiempo de ejecución" :

Lo que quiero decir con esto es el comportamiento predeterminado que ocurre en el momento de la ejecución cuando llama a métodos virtuales. Digamos, por ejemplo, que en mi ejemplo de código anterior, en lugar de definir un método no virtual, de hecho definí un método virtual y un verdadero método anulado también.

Si b.Footuviera que llamar en ese caso, el CLR determinaría correctamente el tipo de objeto al que bapunta la referencia Bary enviaría la llamada de manera Mapropiada.

Andrew Hare
fuente
6
Aunque "despacho polimórfico en tiempo de ejecución" es técnicamente la forma correcta de decirlo, ¡imagino que probablemente esto pasa por encima de la cabeza de casi todo el mundo!
Orion Edwards
3
Si bien es cierto que el autor puede haber tenido la intención de rechazar la anulación del método, no es cierto que sea necesariamente lo correcto. Creo que el equipo de XNA debería haber implementado una interfaz IGraphicsDevice para permitir al usuario más flexibilidad en la depuración y las pruebas unitarias. Me veo obligado a hacer cosas muy feas y esto debería haber sido previsto por el equipo. Se puede encontrar más discusión aquí: forums.xna.com/forums/p/30645/226222.aspx
zfedoran
2
@Orion Yo tampoco lo entendí, pero después de un rápido google, aprecié el uso de los términos "correctos".
zfedoran
10
No creo en absoluto en el argumento de que "el autor no lo hizo". Eso es como decir que el inventor de la rueda no anticipó que se colocarían ruedas en los automóviles, por lo que no podemos usarlas en los automóviles. Sé cómo funciona la rueda / método y quiero hacer algo ligeramente diferente con él. No me importa si el creador quería que lo hiciera o no. Perdón por intentar revivir un hilo muerto. Solo necesito desahogarme antes de encontrar una forma realmente inconveniente de solucionar este problema en el que estoy :)
Jeff
2
Entonces, ¿qué pasa cuando hereda una gran base de código y necesita agregar una prueba para una nueva funcionalidad, pero para probar eso necesita instanciar una gran cantidad de maquinaria? En Java, simplemente extendería esas partes y anularía los métodos requeridos con stubs. En C #, si los métodos no están marcados como virtuales, tengo que encontrar algún otro mecanismo (biblioteca burlona o tal, e incluso entonces).
Adam Parkin
22

No, no puedes.

Solo puede anular un método virtual; consulte MSDN aquí :

En C #, las clases derivadas pueden contener métodos con el mismo nombre que los métodos de la clase base.

  • El método de la clase base debe definirse como virtual.
ChrisF
fuente
6

Si la clase base no está sellada, puede heredarla y escribir un nuevo método que oculte el base (use la palabra clave "nuevo" en la declaración del método). De lo contrario, no, no puede anularlo porque nunca fue la intención del autor original que se anulara, por lo que no es virtual.

babosa
fuente
3

Creo que se está sobrecargando y anulando confuso, sobrecargar significa que tiene dos o más métodos con el mismo nombre pero diferentes conjuntos de parámetros, mientras que anular significa que tiene una implementación diferente para un método en una clase derivada (reemplazando o modificando así el comportamiento en su clase base).

Si un método es virtual, puede anularlo mediante la palabra clave anular en la clase derivada. Sin embargo, los métodos no virtuales solo pueden ocultar la implementación base utilizando la nueva palabra clave en lugar de la palabra clave anular. La ruta no virtual es inútil si la persona que llama accede al método a través de una variable escrita como tipo base, ya que el compilador usaría un envío estático al método base (lo que significa que nunca se llamaría al código en su clase derivada).

Nunca hay nada que le impida agregar una sobrecarga a una clase existente, pero solo el código que conoce su clase podría acceder a ella.

Rory
fuente
2

No puede anular el método no virtual de ninguna clase en C # (sin piratear CLR), pero puede anular cualquier método de interfaz que implemente la clase. Considere que no hemos sellado

class GraphicsDevice: IGraphicsDevice {
    public void DoWork() {
        Console.WriteLine("GraphicsDevice.DoWork()");
    }
}

// with its interface
interface IGraphicsDevice {
    void DoWork();
}

// You can't just override DoWork in a child class,
// but if you replace usage of GraphicsDevice to IGraphicsDevice,
// then you can override this method (and, actually, the whole interface).

class MyDevice: GraphicsDevice, IGraphicsDevice {
    public new void DoWork() {
        Console.WriteLine("MyDevice.DoWork()");
        base.DoWork();
    }
}

Y aquí está la demostración

class Program {
    static void Main(string[] args) {

        IGraphicsDevice real = new GraphicsDevice();
        var myObj = new MyDevice();

        // demo that interface override works
        GraphicsDevice myCastedToBase = myObj;
        IGraphicsDevice my = myCastedToBase;

        // obvious
        Console.WriteLine("Using real GraphicsDevice:");
        real.DoWork();

        // override
        Console.WriteLine("Using overriden GraphicsDevice:");
        my.DoWork();

    }
}
Vyacheslav Napadovsky
fuente
@Dan aquí está la demostración en vivo: dotnetfiddle.net/VgRwKK
Vyacheslav Napadovsky
0

En el caso de que esté heredando de una clase no derivada, simplemente podría crear una superclase abstracta y heredar de ella en su lugar.

usuario3853059
fuente
0

¿Hay alguna forma de anular un método no virtual? o algo que dé resultados similares (además de crear un nuevo método para llamar al método deseado)?

No puede anular un método no virtual. Sin embargo, puede utilizar la newpalabra clave modificadora para obtener resultados similares:

class Class0
{
    public int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    public new int Test()
    {
        return 1;
    }
}
. . .
// result of 1
Console.WriteLine(new Class1().Test());

También querrá asegurarse de que el modificador de acceso también sea el mismo; de lo contrario, no obtendrá herencia en el futuro. Si otra clase hereda de Class1la newpalabra clave Class1, no afectará a los objetos que hereden de ella, a menos que el modificador de acceso sea el mismo.

Si el modificador de acceso no es el mismo:

class Class0
{
    protected int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    // different access modifier
    new int Test()
    {
        return 1;
    }
}

class Class2 : Class1
{
    public int Result()
    {
        return Test();
    }
}
. . .
// result of 0
Console.WriteLine(new Class2().Result());

... versus si el modificador de acceso es el mismo:

class Class0
{
    protected int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    // same access modifier
    protected new int Test()
    {
        return 1;
    }
}

class Class2 : Class1
{
    public int Result()
    {
        return Test();
    }
}
. . .
// result of 1
Console.WriteLine(new Class2().Result());

Como se señaló en una respuesta anterior, este no es un buen principio de diseño.

Aaron Thomas
fuente
-5

Hay una forma de lograr esto usando la clase abstracta y el método abstracto.

Considerar

Class Base
{
     void MethodToBeTested()
     {
        ...
     }

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

Ahora, si desea tener diferentes versiones del método MethodToBeTested (), cambie Class Base a una clase abstracta y el método MethodToBeTested () como método abstracto

abstract Class Base
{

     abstract void MethodToBeTested();

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

Con MethodToBeTested () vacío abstracto viene un problema; la implementación se ha ido.

Por lo tanto, cree un class DefaultBaseImplementation : Basepara tener la implementación predeterminada.

Y cree otro class UnitTestImplementation : Basepara implementar la prueba unitaria.

Con estas 2 nuevas clases, se puede anular la funcionalidad de la clase base.

Class DefaultBaseImplementation : Base    
{
    override void MethodToBeTested()    
    {    
        //Base (default) implementation goes here    
    }

}

Class UnitTestImplementation : Base
{

    override void MethodToBeTested()    
    {    
        //Unit test implementation goes here    
    }

}

Ahora tiene 2 clases implementadas (anuladas) MethodToBeTested().

Puede crear una instancia de la clase (derivada) según sea necesario (es decir, con la implementación base o con la implementación de la prueba unitaria).

ShivanandSK
fuente
@slavoo: Hola slavoo. Gracias por la actualización del código. Pero, ¿podría decirme la razón por la que rechazo esto?
ShivanandSK
6
Porque no responde a la pregunta. Pregunta si puede anular a los miembros que no están marcados como virtuales. Ha demostrado que debe implementar miembros marcados como abstractos.
Lee Louviere