functions - instance dynamic object c#
¿El uso de la dinámica es considerada una mala práctica? (2)
En C #, alguien puede hacer:
MyClass myInstance = new MyClass();
dynamic mydynamicInstance = myInstance;
Y luego, invoque un método, como:
//This method takes a MyClass argument and does something.
Caller.InvokeMethod(myDynamicInstance);
Ahora, esto conducirá a la determinación del tipo myInstance en el tiempo de ejecución y, si es válido, se llamará normalmente a Caller.InvokeMethod
.
Ahora, mi pregunta es si esto se considera una mala práctica para usar dynamic
, especialmente en los siguientes casos:
1) InvokeMethod
instancia de otra instancia del tipo myDynamicInstance, usando la reflexión dentro.
2) Hay una clase base abstracta MyBaseClass
y varias subclases, incluida MyBaseClass
. Si tenemos una cantidad de métodos sobrecargados de InvokeMethod
para todas esas clases derivadas, ¿podríamos usarlo para permitir en tiempo de ejecución la determinación del tipo y luego la invocación correcta a través de la sobrecarga del método (o el enlace tardío en la llamada de un método de ese tipo? clase)?:
public abstract class MyBaseClass {/*...*/}
public class MyClass : MyBaseClass {/*...*/}
public class MyAnotherClass : MyBaseClass {/*...*/}
MyBaseClass myBaseClassRef = new MyClass();
dynamic myDynamicInstance = myBaseClassRef;
Caller.InvokeMethod(myDynamicInstance);
La respuesta corta es SÍ, es una mala práctica usar dinámica.
¿Por qué?
La palabra clave dinámica se refiere al tipo de enlace tardío, lo que significa que el sistema verificará el tipo solo durante la ejecución en lugar de durante la compilación. Entonces significará que el usuario , en lugar del programador, debe descubrir el posible error . El error podría ser una MissingMethodException, pero también podría ser una llamada no intencionada a un método existente con un mal comportamiento. Imagine una llamada a un método que termine por calcular un mal precio o al calcular un nivel de oxígeno malo.
En términos generales, la verificación de tipos ayuda a obtener una informática determinista, por lo que, cuando pueda, debe usarla. Aquí hay una pregunta sobre las deficiencias de la dinámica .
Sin embargo, la dinámica puede ser útil ...
- Interopera con COM como con Office
- Interoperabilidad con idiomas en los que los tipos dinámicos son parte del lenguaje (IronPython, IronRuby) a medida que se introdujo la dinámica para ayudar a portarlos a .Net.
- Puede reemplazar el código complejo de reflexión con una ceremonia baja, un código elegante (sin embargo, dependiendo del caso, aún debe perfilar ambos enfoques para verificar cuál es el más apropiado en términos de rendimiento y verificaciones en tiempo de compilación).
La base de código está evolucionando a lo largo de todo el ciclo de vida de la aplicación, e incluso si la dinámica parece correcta ahora, establece un precedente que puede implicar un aumento del uso de palabras clave dinámico por parte de su equipo. Puede llevar a un aumento en los costos de mantenimiento (en caso de que evolucione la firma mencionada anteriormente, puede notarlo demasiado tarde). Por supuesto, puede confiar en pruebas unitarias, pruebas humanas sin regresión, etc. Pero cuando tiene que elegir entre la calidad relacionada con la disciplina humana y la verificación automática de la calidad relacionada con la computadora, elija la más tarde. Es menos propenso a errores.
En tu caso...
En su caso, parece que puede usar el esquema de herencia común (el primero a continuación y el que menciona en su pregunta), ya que dynamic
no le brindará ningún beneficio adicional (le costará más potencia de procesamiento y le hará incurrir en el riesgo de futuros errores potenciales).
Depende de si puede cambiar el código de la jerarquía MyClass
y / o Caller.InvokeMethod
.
Vamos a enumerar las diferentes alternativas posibles al dinámico ...
- Alternativa compilada de comprobación de tipos a la llamada dinámica al método de palabras clave:
El más común es usar llamadas virtuales de interfaz como esta instancia. InvokeMethod () con herencia llamando a la implementación correcta.
public interface IInvoker : { void InvokeMethod(); }
public abstract class MyBaseClass : IInvoker { public abstract void InvokeMethod(); }
public class MyAnotherClass : MyBaseClass { public override void InvokeMethod() { /* Do something */ } }
public class MyClass : MyBaseClass { public override void InvokeMethod() { /* Do something */ } }
Otro un poco menos eficiente es mediante el uso de métodos de extensión
public static class InvokerEx:
{
public static void Invoke(this MyAnotherClass c) { /* Do something */ } }
public static void Invoke(this MyClass c) { /* Do something */ } }
}
Si hay varios "visitantes" de la jerarquía MyBaseClass, puede usar el patrón Visitor :
public interface IVisitor
{
void Visit(this MyAnotherClass c);
void Visit(this MyClass c);
}
public abstract class MyBaseClass : IInvoker { public abstract void Accept(IVisitor visitor); }
public class MyAnotherClass : MyBaseClass { public override void Accept(IVisitor visitor) { visitor.Visit(this); } }
public class MyClass : MyBaseClass { public override void Accept(IVisitor visitor) { visitor.Visit(this); } }
Otras variantes aunque no muy útiles aquí ( método genérico ) pero interesantes para la comparación del rendimiento:
public void InvokeMethod<T>(T instance) where T : IInvoker { return instance.InvokeMethod(); }
- Alternativa dinámica a la llamada dinámica al método de palabra clave:
Si necesita llamar a un método desconocido en el momento de la compilación, agregué a continuación las diferentes técnicas que podría usar y actualicé los resultados de rendimiento:
MethodInfo.CreateDelegate
_method = typeof (T).GetMethod("InvokeMethod");
_func = (Func<T, int>)_method.CreateDelegate(typeof(Func<T, int>));
Nota: Se necesita enviar a Func para evitar llamar a DynamicInvoke (ya que generalmente es más lento).
DynamicMethod e ILGenerator.Emit
De hecho, construye la llamada completa desde el principio, es la más flexible, pero debes tener algún fondo de ensamblador para apreciarla por completo.
_dynamicMethod = new DynamicMethod("InvokeMethod", typeof (int), new []{typeof(T)}, GetType().Module);
ILGenerator il = _dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, _method);
il.Emit(OpCodes.Ret);
_func = (Func<T, int>) _dynamicMethod.CreateDelegate(typeof (Func<T, int>));
Expresión Linq
Es similar a DynamicMethod, sin embargo, no controla el IL generado. Sin embargo, es realmente más legible.
_method = typeof (T).GetMethod("InvokeMethod");
var instanceParameter = Expression.Parameter(typeof (T), "instance");
var call = Expression.Call(instanceParameter, _method);
_delegate = Expression.Lambda<Func<T, int>>(call, instanceParameter).Compile();
_func = (Func<T, int>) _delegate;
MethodInfo.Invoke
La última, pero no la menos importante, la llamada de reflexión conocida estándar. Sin embargo, incluso si es fácil meterse con él, no lo use, ya que es realmente un mal desempeño (mire los resultados de referencia). Prefiere CreateDelegate, que es realmente más rápido.
_method = typeof (T).GetMethod("InvokeMethod");
return (int)_method.Invoke(instance, _emptyParameters);
El código de la prueba de referencia se puede encontrar en GitHub .
Punto de referencia de los diferentes métodos para obtener un orden de magnitud (para 10 millones de llamadas) (.NET Framework 4.5) :
For Class standard call:
Elapsed: 00:00:00.0532945
Call/ms: 188679
For MethodInfo.CreateDelegate call:
Elapsed: 00:00:00.1131495
Call/ms: 88495
For Keyword dynamic call:
Elapsed: 00:00:00.3805229
Call/ms: 26315
For DynamicMethod.Emit call:
Elapsed: 00:00:00.1152792
Call/ms: 86956
For Linq Expression call:
Elapsed: 00:00:00.3158967
Call/ms: 31746
For Extension Method call:
Elapsed: 00:00:00.0637817
Call/ms: 158730
For Generic Method call:
Elapsed: 00:00:00.0772658
Call/ms: 129870
For Interface virtual call:
Elapsed: 00:00:00.0778103
Call/ms: 129870
For MethodInfo Invoke call:
Elapsed: 00:00:05.3104416
Call/ms: 1883
For Visitor Accept/Visit call:
Elapsed: 00:00:00.1384779
Call/ms: 72463
== SUMMARY ==
Class standard call: 1
Extension Method call : 1,19
Generic Method call : 1,45
Interface virtual call : 1,45
MethodInfo.CreateDelegate call : 2,13
DynamicMethod.Emit call : 2,17
Visitor Accept/Visit call : 2,60
Linq Expression call : 5,94
Keyword dynamic call : 7,17
MethodInfo Invoke call : 100,19
EDITAR:
Entonces, comparando con el patrón Visitor, el despacho dinámico es aproximadamente 3 veces más lento . Puede ser aceptable para algunas aplicaciones, ya que puede eliminar el código engorroso. Siempre depende de ti elegir.
Solo tenga en cuenta todos los inconvenientes.
EDITAR: (como respuesta al beneficio de despacho múltiple)
Usar un nombre de patrón moderno como '' envío múltiple '' y simplemente decir que es más limpio porque usa menos código, no lo convierte en un beneficio adicional en mi humilde opinión. Si desea escribir un código de moda o no le importa la seguridad de tipo y la estabilidad de producción, ya hay mucho lenguaje disponible que ofrece una función de tipeo dinámico completo. Veo dynamic
introducción dynamic
palabras clave en C # como una forma de cerrar la brecha entre la sólida familia de idiomas tipeados y no tan fuertemente tipada en otros idiomas. No significa que deba cambiar la forma en que desarrolla y poner controles de tipo en la papelera.
ACTUALIZACIÓN: 2016/11/08 (.NET Framework 4.6.1)
Los pedidos de magnitud siguen siendo los mismos (incluso si algunos de ellos han mejorado un poco):
Class standard call: 1
Extension Method call : 1,19
Interface virtual call : 1,46
Generic Method call : 1,54
DynamicMethod.Emit call : 2,07
MethodInfo.CreateDelegate call : 2,13
Visitor Accept/Visit call : 2,64
Linq Expression call : 5,55
Keyword dynamic call : 6,70
MethodInfo Invoke call : 102,96
No estoy totalmente de acuerdo con Fabien en que no le da beneficios adicionales. Lo que está resolviendo con el patrón de visitante se llama Despacho múltiple y la dinámica puede proporcionar una solución limpia a esto también. Seguro que debes saber las implicaciones que Fabien mencionó como el rendimiento, la comprobación de tipo estático ...
public abstract class MyBaseClass
{
}
public class MyClass : MyBaseClass
{
}
public class MyAnotherClass : MyBaseClass
{
}
public class ClassThatIsUsingBaseClass
{
public static void PrintName(MyBaseClass baseClass)
{
Console.WriteLine("MyBaseClass");
}
public static void PrintName(MyClass baseClass)
{
Console.WriteLine("MyClass");
}
public static void PrintName(MyAnotherClass baseClass)
{
Console.WriteLine("MyAnotherClass");
}
public static void PrintNameMultiDispatch(MyBaseClass baseClass)
{
ClassThatIsUsingBaseClass.PrintName((dynamic)baseClass);
}
}
Y el uso es
static void Main(string[] args)
{
MyBaseClass myClass = new MyClass();
MyBaseClass myAnotherClass = new MyAnotherClass();
ClassThatIsUsingBaseClass.PrintName(myClass);
ClassThatIsUsingBaseClass.PrintName(myAnotherClass);
ClassThatIsUsingBaseClass.PrintNameMultiDispatch(myClass);
ClassThatIsUsingBaseClass.PrintNameMultiDispatch(myAnotherClass);
Console.ReadLine();
}
La salida es
MyBaseClass
MyBaseClass
MyClass
MyAnotherClass
Busque "Despacho múltiple" y "Despacho múltiple C #" para obtener más información.