2009-12-05 10 views
65

¿Hay alguna manera de anular un método no virtual? o algo que da resultados similares (aparte de crear un nuevo método para llamar al método deseado)?¿Es posible anular un método no virtual?

Me gustaría sobrescribir un método de Microsoft.Xna.Framework.Graphics.GraphicsDevice teniendo en cuenta las pruebas unitarias.

+7

¿Se refiere a * overload * or * override *? Sobrecarga = agregue un método con el mismo nombre pero diferentes parámetros (por ejemplo, diferentes sobrecargas de Console.WriteLine). Anular = (aproximadamente) cambiar 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 * anulación * solo se aplica a métodos virtuales. – itowlson

Respuesta

81

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

Pero incluso ocultar un método no le dará el tiempo de ejecución del envío polimórfico de llamadas a métodos como lo haría una verdadera llamada a un 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 las dos llamadas a la impresión MFoo.M método. Como puede ver, este enfoque le permite tener una nueva implementación para un método, siempre que la referencia a ese objeto sea del tipo derivado correcto, pero si se oculta un método base , se rompe el polimorfismo.

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

Tiendo a lado con aquellos que favorecen el comportamiento predeterminado de C# que los métodos no son virtuales por defecto (a diferencia de Java). Yo iría más allá 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 anular el método.

Editar: "tiempo de ejecución de despacho polimórfica":

Lo que quiero decir con esto es el comportamiento por defecto que ocurre en tiempo de ejecución cuando se llama a los 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 reemplazado también.

Si tuviera que llamar b.Foo en ese caso, el CLR sería determinar correctamente el tipo de objeto que los b puntos de referencia a como Bar y se enviará la llamada a M apropiadamente.

+4

Aunque "despacho polimórfico de tiempo de ejecución" es técnicamente la forma correcta de decirlo, ¡me imagino que esto probablemente le pasa a la cabeza a casi todo el mundo! –

+1

Si bien es cierto que el autor puede haber tenido la intención de no permitir la anulación del método, no es cierto que fuera necesariamente lo correcto. Siento que el equipo de XNA debería haber implementado una interfaz IGraphicsDevice para permitirle al usuario más flexibilidad en la depuración y pruebas unitarias. Me veo obligado a hacer algunas cosas muy feas y esto debería haber sido previsto por el equipo. Puede encontrar más información aquí: http://forums.xna.com/forums/p/30645/226222.aspx – zfedoran

+0

@Orion - lo suficiente, he agregado una explicación :) –

17

No, no puedes.

Sólo se puede anular un método virtual - ver el MSDN here:

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

  • El método de la clase base debe definirse como virtual.
4

Si la clase base no está sellado, entonces pueden heredar de ella y escribir un nuevo método que oculta la base de una (utilizar la "nueva" palabra clave en la declaración del método). De lo contrario, no, no puede anularlo porque nunca fue la intención original del autor que se anule, por lo que no es virtual.

3

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

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

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

-4

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

Considere

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

    void Method1() 
    { 
    } 

    void Method2() 
    { 
    } 

    ... 
} 

Ahora, si usted desea tener diferentes versiones del método MethodToBeTested(), entonces cambio de clase de base a una clase abstracta y el método MethodToBeTested() como un método abstracto

abstract Class Base 
{ 

    abstract void MethodToBeTested(); 

    void Method1() 
    { 
    } 

    void Method2() 
    { 
    } 

    ... 
} 

Con abstract void MethodToBeTested() viene un problema; la implementación se ha ido. Para obtener la implementación predeterminada, cree un class DefaultBaseImplementation : Base.

Y cree otro class UnitTestImplementation : Base para tener una implementación de 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 usted tienen 2 clases que implementan (anulando) MethodToBeTested().

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

+0

@slavoo: Hola, slavoo. Gracias por la actualización del código. ¿Pero podría decirme por qué razón bajó la votación? – ShivanandSK

+3

Porque no responde la pregunta. Le pregunta si puede anular los miembros que no están marcados como virtuales. Has demostrado que tienes que implementar miembros marcados como abstractos. –

0

En el caso de que esté heredando de una clase no derivada, podría simplemente crear una superclase abstracta y heredarla de la cadena descendente.

0

¿Hay alguna manera de anular un método no virtual? o algo que da resultados similares (aparte de crear un nuevo método para llamar al método deseado)?

No puede anular un método no virtual. Sin embargo, usted puede utilizar la palabra clave new modificador para conseguir 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()); 

Usted también querrá asegurarse de que el access modifier es también la misma, de lo contrario no obtendrá la herencia en la línea.Si otra clase hereda de Class1, la palabra clave new en 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 la misma:

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()); 

... frente 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

Cuestiones relacionadas