virtuales referencia que new modificador metodos metodo funciones and c# inheritance virtual override

referencia - Funcionamientos internos de C#Virtual y Override



virtual c# referencia (6)

El tema de cómo el mecanismo virtual e invalidación de C # funciona internamente ha sido discutido hasta la muerte entre los programadores ... pero después de media hora en google, no puedo encontrar una respuesta a la siguiente pregunta (ver a continuación):

Usando un código simple:

public class BaseClass { public virtual SayNo() { return "NO!!!"; } } public class SecondClass: BaseClass { public override SayNo() { return "No."; } } public class ThirdClass: SecondClass { public override SayNo() { return "No..."; } } class Program { static void Main() { ThirdClass thirdclass = new ThirdClass(); string a = thirdclass.SayNo(); // this would return "No..." // Question: // Is there a way, not using the "new" keyword and/or the "hide" // mechansim (i.e. not modifying the 3 classes above), can we somehow return // a string from the SecondClass or even the BaseClass only using the // variable "third"? // I know the lines below won''t get me to "NO!!!" BaseClass bc = (BaseClass)thirdclass; string b = bc.SayNo(); // this gives me "No..." but how to I get to "NO!!!"? } }

Creo que no puedo acceder a los métodos de la clase base o la clase derivada intermedia simplemente usando la instancia más derivada (sin modificar las firmas de método de las 3 clases). Pero me gustaría confirmar y consolidar mi comprensión ...

Gracias.


C # no puede hacer esto, pero en realidad es posible en IL usando call lugar de callvirt . Por lo tanto, puede DynamicMethod la limitación de C # utilizando Reflection.Emit en combinación con DynamicMethod .

Aquí hay un ejemplo muy simple para ilustrar cómo funciona esto. Si realmente desea usar esto, envuélvalo dentro de una función agradable para que funcione con diferentes tipos de delegados.

delegate string SayNoDelegate(BaseClass instance); static void Main() { BaseClass target = new SecondClass(); var method_args = new Type[] { typeof(BaseClass) }; var pull = new DynamicMethod("pull", typeof(string), method_args); var method = typeof(BaseClass).GetMethod("SayNo", new Type[] {}); var ilgen = pull.GetILGenerator(); ilgen.Emit(OpCodes.Ldarg_0); ilgen.EmitCall(OpCodes.Call, method, null); ilgen.Emit(OpCodes.Ret); var call = (SayNoDelegate)pull.CreateDelegate(typeof(SayNoDelegate)); Console.WriteLine("callvirt, in C#: {0}", target.SayNo()); Console.WriteLine("call, in IL: {0}", call(target)); }

Huellas dactilares:

callvirt, in C#: No. call, in IL: NO!!!


No puede acceder a los métodos básicos de una anulación. No importa cómo proyecte el objeto, siempre se usa la última anulación en la instancia.


Por supuesto...

BaseClass bc = new BaseClass(); string b = bc.SayNo();

"Virtual" significa que la implementación que se ejecutará se basa en el tipo REAL del objeto subyacente, no en el tipo de la variable en la que está rellenado ... Entonces, si el objeto real es un ThirdClass, esa es la implementación que obtendrá , no importa a qué lo arrojes. Si desea el comportamiento que describe arriba, no haga que los métodos sean virtuales ...

Si te estás preguntando "¿cuál es el punto?" es para ''polimorfismo''; para que pueda declarar una colección, o un parámetro de método, como un tipo base, e incluir / pasar una mezcla de tipos derivados, y aún cuando, dentro del código, aunque cada objeto se asigne a una variable de referencia declarada como tipo de base, para cada uno, la implementación real que se ejecutará para cualquier llamada de método virtual será esa implementación definida en la definición de clase para el tÃtulo REAL de cada objeto ...


Si está respaldado con un campo, puede sacar el campo usando la reflexión.

Incluso si llevas a cabo la información del método usando la reflexión de typeof (BaseClass) aún terminarás ejecutando tu método reemplazado


Sin modificaciones a su muestra y descartando la reflexión, no, no hay forma. La intención del sistema virtual es hacer que las llamadas sean más derivadas sin importar nada y el CLR es bueno en su trabajo.

Sin embargo, hay un par de formas en que puedes evitar esto.

Opción 1: podría agregar el siguiente método a ThirdClass

public void SayNoBase() { base.SayNo(); }

Esto forzaría la invocación de SecondClass.SayNo

Opción 2: el principal problema aquí es que desea invocar un método virtual de forma no virtual. C # solo proporciona una forma de hacerlo a través del modificador de base. Esto hace que sea imposible llamar a un método dentro de su propia clase de una manera no virtual. Puede solucionar esto descomponiéndolo en un segundo método y proxying.

public overrides void SayNo() { SayNoHelper(); } public void SayNoHelper() { Console.WriteLine("No"); }


Usar base en C # solo funciona para la base inmediata. No puede acceder a un miembro base-base.

Parece que alguien más me golpeó hasta el golpe con la respuesta de que es posible hacerlo en Illinois.

Sin embargo, creo que la forma en que hice el código gen tiene algunas ventajas, así que lo publicaré de todos modos.

Lo que hice de manera diferente es usar árboles de expresiones, que te permiten usar el compilador de C # para realizar la resolución de sobrecarga y la sustitución de argumentos genéricos.

Eso es complicado, y no querrás tener que replicarte tú mismo si puedes evitarlo. En tu caso, el código funcionaría así:

var del = CreateNonVirtualCall<Program, BaseClass, Action<ThirdClass>> ( x=>x.SayNo() );

Probablemente desee almacenar el delegado en un campo estático de solo lectura, para que solo tenga que compilarlo una vez.

Necesita especificar 3 argumentos genéricos:

  1. El tipo de propietario: esta es la clase de la que habría invocado el código si no estuviera utilizando "CreateNonVirtualCall".

  2. La clase base: esta es la clase de la que desea realizar la llamada no virtual desde

  3. Un tipo de delegado. Esto debería representar la firma del método que se está llamando con un parámetro adicional para el argumento "this". Es posible eliminar esto, pero requiere más trabajo en el método de código genético.

El método toma un solo argumento, una lambda que representa la llamada. Tiene que ser una llamada y solo una llamada. Si desea extender el gen del código, puede admitir cosas más complejas.

Por simplicidad, el cuerpo lambda está restringido a solo poder acceder a los parámetros lambda, y solo puede pasarlos directamente a la función. Puede eliminar esta restricción si amplía el código gen en el cuerpo del método para admitir todos los tipos de expresiones. Eso tomaría un poco de trabajo sin embargo. Puedes hacer lo que quieras con el delegado que vuelve, por lo que la restricción no es demasiado importante.

Es importante tener en cuenta que este código no es perfecto. Podría usar mucha más validación, y no funciona con los parámetros "ref" o "out" debido a las limitaciones del árbol de expresiones.

Lo probé en casos de muestra con métodos vacíos, métodos de devolución de valores y métodos genéricos, y funcionó. Sin embargo, estoy seguro de que puede encontrar algunos casos extremos que no funcionan.

En cualquier caso, aquí está el Código Gen de IL:

public static TDelegate CreateNonVirtCall<TOwner, TBase, TDelegate>(Expression<TDelegate> call) where TDelegate : class { if (! typeof(Delegate).IsAssignableFrom(typeof(TDelegate))) { throw new InvalidOperationException("TDelegate must be a delegate type."); } var body = call.Body as MethodCallExpression; if (body.NodeType != ExpressionType.Call || body == null) { throw new ArgumentException("Expected a call expression", "call"); } foreach (var arg in body.Arguments) { if (arg.NodeType != ExpressionType.Parameter) { //to support non lambda parameter arguments, you need to add support for compiling all expression types. throw new ArgumentException("Expected a constant or parameter argument", "call"); } } if (body.Object != null && body.Object.NodeType != ExpressionType.Parameter) { //to support a non constant base, you have to implement support for compiling all expression types. throw new ArgumentException("Expected a constant base expression", "call"); } var paramMap = new Dictionary<string, int>(); int index = 0; foreach (var item in call.Parameters) { paramMap.Add(item.Name, index++); } Type[] parameterTypes; parameterTypes = call.Parameters.Select(p => p.Type).ToArray(); var m = new DynamicMethod ( "$something_unique", body.Type, parameterTypes, typeof(TOwner) ); var builder = m.GetILGenerator(); var callTarget = body.Method; if (body.Object != null) { var paramIndex = paramMap[((ParameterExpression)body.Object).Name]; builder.Emit(OpCodes.Ldarg, paramIndex); } foreach (var item in body.Arguments) { var param = (ParameterExpression)item; builder.Emit(OpCodes.Ldarg, paramMap[param.Name]); } builder.EmitCall(OpCodes.Call, FindBaseMethod(typeof(TBase), callTarget), null); if (body.Type != typeof(void)) { builder.Emit(OpCodes.Ret); } var obj = (object) m.CreateDelegate(typeof (TDelegate)); return obj as TDelegate; }