with type tutorial from example c# reflection activator

type - C#Usando Activator.CreateInstance



reflection c# tutorial (6)

Ayer hice una pregunta sobre el uso de la reflexión o el Patrón de estrategia para los métodos dinámicos de llamada.

Sin embargo, desde entonces he decidido cambiar los métodos en clases individuales que implementan una interfaz común. La razón es que cada clase, aunque tiene algunas similitudes, también realiza ciertos métodos exclusivos de esa clase.

He estado usando una estrategia como tal:

switch (method) { case "Pivot": return new Pivot(originalData); case "GroupBy": return new GroupBy(originalData); case "Standard deviation": return new StandardDeviation(originalData); case "% phospho PRAS Protein": return new PhosphoPRASPercentage(originalData); case "AveragePPPperTreatment": return new AveragePPPperTreatment(originalData); case "AvgPPPNControl": return new AvgPPPNControl(originalData); case "PercentageInhibition": return new PercentageInhibition(originalData); default: throw new Exception("ERROR: Method " + method + " does not exist."); }

Sin embargo, a medida que crezca el número de clases potenciales, tendré que seguir agregando nuevas, rompiendo así la regla de modificación de cerrado.

En cambio, he usado una solución como tal:

var test = Activator.CreateInstance(null, "MBDDXDataViews."+ _class); ICalculation instance = (ICalculation)test.Unwrap(); return instance;

Efectivamente, el parámetro _class es el nombre de la clase pasada en tiempo de ejecución. ¿Es esta una forma común de hacer esto? ¿Habrá algún problema de rendimiento con esto?

Soy bastante nuevo en la reflexión, por lo que su consejo sería bienvenido.


  1. No hay errores al verificar aquí. ¿Estás absolutamente seguro de que _class se resolverá en una clase válida? ¿Está controlando todos los valores posibles o esta cadena de alguna manera se llena por un usuario final?
  2. La reflexión es generalmente más costosa que evitarla. Los problemas de rendimiento son proporcionales a la cantidad de objetos que planea crear de esta manera.
  3. Antes de salir corriendo y usar un marco de inyección de dependencia, lea las críticas al respecto. =)

Al usar la reflexión, primero debe hacerse un par de preguntas, porque puede terminar en una solución compleja exagerada que es difícil de mantener:

  1. ¿Hay alguna manera de resolver el problema usando genericity o class / interface inheritance?
  2. ¿Puedo resolver el problema usando invocaciones dynamic (solo .NET 4.0 y superior)?
  3. ¿Es importante el rendimiento, es decir, mi método reflejado o llamada de instanciación se llamará una, dos o un millón de veces?
  4. ¿Puedo combinar tecnologías para llegar a una solución inteligente pero viable / comprensible?
  5. ¿Estoy de acuerdo con la pérdida de seguridad del tipo de tiempo de compilación?

Genericidad / dinámica

Según su descripción, supongo que no conoce los tipos en tiempo de compilación, solo sabe que comparten la interfaz ICalculation . Si esto es correcto, es probable que los números (1) y (2) anteriores no sean posibles en su escenario.

Actuación

Esta es una pregunta importante para hacer. La sobrecarga del uso de la reflexión puede impedir una penalización de más de 400 veces: eso ralentiza incluso una cantidad moderada de llamadas.

La resolución es relativamente fácil: en lugar de usar Activator.CreateInstance , use un método de fábrica (ya lo tiene), busque MethodInfo cree un delegado, MethodInfo caché y use el delegado a partir de ese momento. Esto produce solo una penalización en la primera invocación, las invocaciones subsiguientes tienen un rendimiento casi nativo.

Combina tecnologías

Mucho es posible aquí, pero realmente necesito saber más de su situación para ayudar en esta dirección. A menudo, termino combinando dynamic con genéricos, con reflejo en caché. Al utilizar ocultamiento de información (como es normal en OOP), puede terminar con una solución rápida, estable y aún así extensible.

Perder la seguridad del tipo de tiempo de compilación

De las cinco preguntas, esta es quizás la más importante de la que preocuparse. Es muy importante crear sus propias excepciones que brinden información clara sobre los errores de reflexión. Eso significa que: cada llamada a un método, constructor o propiedad basada en una cadena de entrada o información que de otro modo no se haya verificado se debe envolver en try / catch. Captura solo excepciones específicas (como siempre, quiero decir: nunca captures la Exception sí).

Focus on TargetException (el método no existe), TargetInvocationException (el método existe, pero aumentó una exc cuando se invoca), TargetParameterCountException , MethodAccessException (no los privilegios correctos, sucede mucho en ASP.NET), InvalidOperationException (ocurre con los tipos genéricos). No siempre es necesario tratar de atraparlos a todos, depende de la entrada esperada y de los objetos objetivo esperados.

En resumen

Deshágase de su Activator.CreateInstance y use MethodInfo para encontrar el método creado en fábrica, y use Delegate.CreateDelegate para crear y almacenar en caché el delegado. Simplemente guárdelo en un Dictionary estático donde la clave es igual a la cadena de clase en su código de ejemplo. A continuación se muestra una forma rápida pero no tan sucia de hacerlo de manera segura y sin perder demasiada seguridad.

Código de muestra

public class TestDynamicFactory { // static storage private static Dictionary<string, Func<ICalculate>> InstanceCreateCache = new Dictionary<string, Func<ICalculate>>(); // how to invoke it static int Main() { // invoke it, this is lightning fast and the first-time cache will be arranged // also, no need to give the full method anymore, just the classname, as we // use an interface for the rest. Almost full type safety! ICalculate instanceOfCalculator = this.CreateCachableICalculate("RandomNumber"); int result = instanceOfCalculator.ExecuteCalculation(); } // searches for the class, initiates it (calls factory method) and returns the instance // TODO: add a lot of error handling! ICalculate CreateCachableICalculate(string className) { if(!InstanceCreateCache.ContainsKey(className)) { // get the type (several ways exist, this is an eays one) Type type = TypeDelegator.GetType("TestDynamicFactory." + className); // NOTE: this can be tempting, but do NOT use the following, because you cannot // create a delegate from a ctor and will loose many performance benefits //ConstructorInfo constructorInfo = type.GetConstructor(Type.EmptyTypes); // works with public instance/static methods MethodInfo mi = type.GetMethod("Create"); // the "magic", turn it into a delegate var createInstanceDelegate = (Func<ICalculate>) Delegate.CreateDelegate(typeof (Func<ICalculate>), mi); // store for future reference InstanceCreateCache.Add(className, createInstanceDelegate); } return InstanceCreateCache[className].Invoke(); } } // example of your ICalculate interface public interface ICalculate { void Initialize(); int ExecuteCalculation(); } // example of an ICalculate class public class RandomNumber : ICalculate { private static Random _random; public static RandomNumber Create() { var random = new RandomNumber(); random.Initialize(); return random; } public void Initialize() { _random = new Random(DateTime.Now.Millisecond); } public int ExecuteCalculation() { return _random.Next(); } }


Básicamente, parece que quieres el patrón de fábrica. En esta situación, define una asignación de entrada a los tipos de salida y luego crea una instancia del tipo en tiempo de ejecución como lo está haciendo.

Ejemplo:

Tienes X cantidad de clases, y todas comparten una interfaz común de IDoSomething.

public interface IDoSomething { void DoSomething(); } public class Foo : IDoSomething { public void DoSomething() { // Does Something specific to Foo } } public class Bar : IDoSomething { public void DoSomething() { // Does something specific to Bar } } public class MyClassFactory { private static Dictionary<string, Type> _mapping = new Dictionary<string, Type>(); static MyClassFactory() { _mapping.Add("Foo", typeof(Foo)); _mapping.Add("Bar", typeof(Bar)); } public static void AddMapping(string query, Type concreteType) { // Omitting key checking code, etc. Basically, you can register new types at runtime as well. _mapping.Add(query, concreteType); } public IDoSomething GetMySomething(string desiredThing) { if(!_mapping.ContainsKey(desiredThing)) throw new ApplicationException("No mapping is defined for: " + desiredThing); return Activator.CreateInstance(_mapping[desiredThing]) as IDoSomething; } }


Le sugiero que le dé a su implementación de fábrica un método RegisterImplementation . Entonces, cada clase nueva es solo una llamada a ese método y no estás cambiando el código de tu fábrica.

ACTUALIZAR:
Lo que quiero decir es algo como esto:

Crea una interfaz que define un cálculo. De acuerdo con tu código, ya hiciste esto. Por el bien de estar completo, voy a utilizar la siguiente interfaz en el resto de mi respuesta:

public interface ICalculation { void Initialize(string originalData); void DoWork(); }

Tu fábrica se verá más o menos así:

public class CalculationFactory { private readonly Dictionary<string, Func<string, ICalculation>> _calculations = new Dictionary<string, Func<string, ICalculation>>(); public void RegisterCalculation<T>(string method) where T : ICalculation, new() { _calculations.Add(method, originalData => { var calculation = new T(); calculation.Initialize(originalData); return calculation; }); } public ICalculation CreateInstance(string method, string originalData) { return _calculations[method](originalData); } }

Esta simple clase de fábrica carece de verificación de errores por razones de simplicidad.

ACTUALIZACIÓN 2:
Lo inicializaría así en algún lugar de la rutina de inicialización de sus aplicaciones:

CalculationFactory _factory = new CalculationFactory(); public void RegisterCalculations() { _factory.RegisterCalculation<Pivot>("Pivot"); _factory.RegisterCalculation<GroupBy>("GroupBy"); _factory.RegisterCalculation<StandardDeviation>("Standard deviation"); _factory.RegisterCalculation<PhosphoPRASPercentage>("% phospho PRAS Protein"); _factory.RegisterCalculation<AveragePPPperTreatment>("AveragePPPperTreatment"); _factory.RegisterCalculation<AvgPPPNControl>("AvgPPPNControl"); _factory.RegisterCalculation<PercentageInhibition>("PercentageInhibition"); }


Solo como un ejemplo de cómo agregar inicialización en el constructor:

Algo similar a:

Activator.CreateInstance(Type.GetType("ConsoleApplication1.Operation1"), initializationData); pero escrito con Linq Expression, parte del código se toma here :

public class Operation1 { public Operation1(object data) { } } public class Operation2 { public Operation2(object data) { } } public class ActivatorsStorage { public delegate object ObjectActivator(params object[] args); private readonly Dictionary<string, ObjectActivator> activators = new Dictionary<string,ObjectActivator>(); private ObjectActivator CreateActivator(ConstructorInfo ctor) { Type type = ctor.DeclaringType; ParameterInfo[] paramsInfo = ctor.GetParameters(); ParameterExpression param = Expression.Parameter(typeof(object[]), "args"); Expression[] argsExp = new Expression[paramsInfo.Length]; for (int i = 0; i < paramsInfo.Length; i++) { Expression index = Expression.Constant(i); Type paramType = paramsInfo[i].ParameterType; Expression paramAccessorExp = Expression.ArrayIndex(param, index); Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType); argsExp[i] = paramCastExp; } NewExpression newExp = Expression.New(ctor, argsExp); LambdaExpression lambda = Expression.Lambda(typeof(ObjectActivator), newExp, param); return (ObjectActivator)lambda.Compile(); } private ObjectActivator CreateActivator(string className) { Type type = Type.GetType(className); if (type == null) throw new ArgumentException("Incorrect class name", "className"); // Get contructor with one parameter ConstructorInfo ctor = type.GetConstructors() .SingleOrDefault(w => w.GetParameters().Length == 1 && w.GetParameters()[0].ParameterType == typeof(object)); if (ctor == null) throw new Exception("There is no any constructor with 1 object parameter."); return CreateActivator(ctor); } public ObjectActivator GetActivator(string className) { ObjectActivator activator; if (activators.TryGetValue(className, out activator)) { return activator; } activator = CreateActivator(className); activators[className] = activator; return activator; } }

El uso es siguiente:

ActivatorsStorage ast = new ActivatorsStorage(); var a = ast.GetActivator("ConsoleApplication1.Operation1")(initializationData); var b = ast.GetActivator("ConsoleApplication1.Operation2")(initializationData);

Lo mismo se puede implementar con DynamicMethods.

Además, no se requiere que las clases sean heredadas de la misma interfaz o clase base.

Gracias, Vitaliy


Una estrategia que uso en casos como este es marcar mis diversas implementaciones con un atributo especial para indicar su clave y escanear los ensamblados activos para los tipos con esa clave:

[AttributeUsage(AttributeTargets.Class)] public class OperationAttribute : System.Attribute { public OperationAttribute(string opKey) { _opKey = opKey; } private string _opKey; public string OpKey {get {return _opKey;}} } [Operation("Standard deviation")] public class StandardDeviation : IOperation { public void Initialize(object originalData) { //... } } public interface IOperation { void Initialize(object originalData); } public class OperationFactory { static OperationFactory() { _opTypesByKey = (from a in AppDomain.CurrentDomain.GetAssemblies() from t in a.GetTypes() let att = t.GetCustomAttributes(typeof(OperationAttribute), false).FirstOrDefault() where att != null select new { ((OperationAttribute)att).OpKey, t}) .ToDictionary(e => e.OpKey, e => e.t); } private static IDictionary<string, Type> _opTypesByKey; public IOperation GetOperation(string opKey, object originalData) { var op = (IOperation)Activator.CreateInstance(_opTypesByKey[opKey]); op.Initialize(originalData); return op; } }

De esta forma, con solo crear una nueva clase con una nueva cadena de clave, puede "conectar" automáticamente a la fábrica, sin tener que modificar el código de fábrica en absoluto.

También notará que, en lugar de depender de cada implementación para proporcionar un constructor específico, he creado un método Initialize en la interfaz que espero que implementen las clases. Mientras implementen la interfaz, podré enviarles el "originalData" sin ninguna rareza de reflexión.

También sugiero usar un marco de inyección de dependencia como Ninject en lugar de usar Activator.CreateInstance. De esta forma, las implementaciones de su operación pueden usar inyección de constructor para sus diversas dependencias.