with parameter multiple method has generic create attribute c# .net generics reflection

parameter - generic restrictions c#



Devolver una instancia de un tipo genérico a una función resuelta en tiempo de ejecución (4)

Solo para aclarar, tengo este trabajo usando dynamic y MakeGenericType. Pero no puedo evitar pensar que hay una mejor manera de hacerlo. Lo que estoy tratando de hacer es crear un cargador de "plug-in", usando Unity. Solo lo explicaré cuando publique el código para que pueda tener una idea de lo que estoy haciendo.

Primero solo publicaré el plug-in:

[RegisterAction("MyPlugin", typeof(bool), typeof(MyPlugin))] public class MyPlugin: IStrategy<bool> { public IStrategyResult<bool> Execute(ISerializable info = null) { bool result; try { // do stuff result = true; } catch (Exception) { result = false; } return new StrategyResult<bool> { Value = result }; } }

Un par de cosas a tener en cuenta aquí. Primero está el RegisterActionAttribute:

[AttributeUsage(AttributeTargets.Class)] public sealed class RegisterActionAttribute : Attribute { public StrategyAction StrategyAction { get; } public RegisterActionAttribute(string actionName, Type targetType, Type returnType, params string[] depdencies) { StrategyAction = new StrategyAction { Name = actionName, StrategyType = targetType, ResponseType = returnType, Dependencies = depdencies }; } }

Luego las interfaces:

public interface IStrategy<T> { IStrategyResult<T> Execute(ISerializable info = null); } public interface IStrategyResult<T> { bool IsValid { get; set; } T Value { get; set; } }

Todo bastante sencillo. El objetivo aquí es simplemente adjuntar algunos metadatos a la clase cuando se carga. La carga se realiza a través de la unidad mediante un envoltorio que simplemente carga los ensamblajes en el directorio bin mediante un patrón de búsqueda de archivos y lo agrega a una clase de singleton con una colección de StrategyActions. No necesito pegar todo el código de la unidad aquí, ya que sé que funciona, registra y resuelve los ensamblajes.

Así que ahora a la carne de la pregunta. Tengo una función en el singleton que ejecuta acciones. Estos se aplican con Unity.Interception HandlerAttributes y pasan una cadena como esta (puedo publicar el código para esto pero no creo que sea relevante):

[ExecuteAction("MyPlugin")]

El controlador llama a la siguiente función de ejecución en la clase singleton para "ejecutar" las funciones que están registradas (agregadas a la colección).

public dynamic Execute(string action, params object[] parameters) { var strategyAction = _registeredActions.FirstOrDefault(a => a.Name == action); if (strategyAction == null) return null; var type = typeof (IStrategy<>); var generic = type.MakeGenericType(strategyAction.StrategyType); var returnType = typeof (IStrategyResult<>); var genericReturn = returnType.MakeGenericType(strategyAction.ResponseType); var instance = UnityManager.Container.Resolve(generic, strategyAction.Name); var method = instance.GetType().GetMethod("Execute"); return method.Invoke(instance, parameters); }

Esta ejecución está envuelta en una llamada de enumerador que devuelve una colección de resultados, que se ordenan para administrar las dependencias y cuáles no (ver más abajo). El llamante hace referencia a estos valores utilizando la propiedad Value de ISTrategyResult {T} para hacer varias cosas definidas por otras reglas comerciales.

public List<dynamic> ExecuteQueuedActions() { var results = new List<dynamic>(); var actions = _queuedActions.AsQueryable(); var sortedActions = TopologicalSort.Sort(actions, action => action.Dependencies, action => action.Name); foreach(var strategyAction in sortedActions) { _queuedActions.Remove(strategyAction); results.Add(Execute(strategyAction.Name)); } return results; }

Ahora fíjese, esto funciona, y obtengo el tipo de retorno especificado por el atributo RegisterAction de los complementos. Como puede ver, estoy capturando el tipo de complemento y el tipo de retorno. Estoy usando la variable "genérica" ​​para resolver el tipo con unidad mediante el uso de MakeGenericType, que funciona bien. También estoy creando un genérico que representa el tipo de retorno basado en el tipo de la colección.

Lo que no me gusta aquí es tener que usar la dinámica para devolver este valor a una función. No puedo encontrar una manera de devolver esto como un resultado de estrategia de IS {T} porque, obviamente, el llamador a "Ejecución dinámica (..." no puede, en tiempo de ejecución, implicar el tipo de retorno de la función. la llamada a Ejecutar con una llamada MakeGenericMethod ya que tengo el tipo esperado StrategyAction. Sería bueno si pudiera averiguar cómo devolver un resultado fuertemente tipado de IStrategyResult {T} mientras se determina el tipo de T durante la llamada .

Entiendo por qué no puedo hacer esto con mi implementación actual. Solo estoy tratando de encontrar una manera de envolver toda esta funcionalidad sin usar dinámica. Y esperaba que alguien pudiera proporcionar algún consejo que pudiera ser útil. Si eso significa envolver esto con otras llamadas a clases no genéricas o algo así, también estaría bien si esa es la única solución.


¿Por qué no definir una super interfaz IStrategyResult como esta:

interface IStrategyResult { Type ReturnType { get; } } interface IStrategyResult<T> : IStrategyResult { // your code here }

Luego define tu ejecución como esta:

public IStrategyResult Execute(string action, params object[] parameters)

Y StrategyResult : IStrategyResult<T> clase StrategyResult : IStrategyResult<T> establezca la propiedad para devolver typeof(T)

Por convención, podría asumir (o imponer el uso de la herencia en una clase abstract StrategyResult<T> : IStrategyResult<T> ) que la T sea ​​la misma que la propiedad ReturnType de la interfaz IStrategyResult no genérica.


Necesita un refactor más amplio que solo averiguar cómo llamar a su complemento.

No es necesario que el atributo [RegisterAction] mantenga targetType y returnType; estos parámetros del atributo pueden desincronizarse fácilmente con el código, lo que los convierte en un agujero potencial en el que caer.

Luego piense desde el otro lado de su configuración: ¿cómo consume los datos, qué hace con su IStrategyResult<> - realmente tiene que ser genérico o hay una manera específica en la que podría resumir el tipo de resultados? No puedo imaginar un sistema de complementos que devuelva "cualquier cosa" al host. La sugerencia está realmente en su dynamic Execute(...) : sus parámetros y su resultado han perdido la tipificación fuerte, lo que demuestra que la tipificación fuerte del complemento no ayuda en nada. Simplemente use el object o, mejor, cree una clase StrategyResult lugar de la interfaz actual y proporcione las propiedades que sean necesarias allí (he agregado algunos ejemplos frívolos), como:

public class StrategyResult{ public object Result{get;set;} public Type ResultType {get;set;} // frivolous examples public bool IsError {get;set;} public string ErrorMessage {get;set;} // really off-the-wall example public Func<StrategyHostContext,bool> ApplyResultToContext {get;set;} public StrategyResult(){ } public StrategyResult FromStrategy(IStrategy strategy){ return new StrategyResult{ ResultType = strategy.ResultType } } public StrategyResult FromStrategyExecute(IStrategy strategy, ISerializable info = null){ var result = FromStrategy(strategy); try{ strategy.Execute(info); } catch (Exception x){ result.IsError = true; result.ErrorMessage = x.Message; } } }

Entonces su IStrategy convierte en:

public interface IStrategy{ Type ResultType {get;} void Initialize(SomeContextClassMaybe context); StrategyResult Execute(ISerializable info = null); }

También puede cambiar su atributo para que sea más eficiente cargar grandes complementos:

[AttributeUsage(AttributeTargets.Assembly)] public sealed class AddinStrategyAttribute : Attribute { public Type StategyType {get; private set;} public AddinStrategyAttribute(Type strategyType){ StrategyType = strategyType; } }

... y usa el atributo así:

[assembly:AddinStrategy(typeof(BoolStrategy))] // note it''s outside the namespace namespace MyNamespace{ public class BoolStrategy: IStrategy{ public Type ResultType { get{ return typeof(bool);}} public void Initialize (SomeContextClassMaybe context){ } public StrategyResult Execute(ISerializable info = null){ return StrategyResult.FromStrategyExecute(this,info); } } }


Suponiendo que la persona que llama a ExecuteActions no tiene ningún conocimiento sobre la T en ninguno de los complementos o resultados y debe trabajar con dynamic u object todos modos, lo siguiente puede funcionar:

Infraestructura:

public interface IStrategy { IStrategyResult Execute(ISerializable info = null); } public interface IStrategyResult { bool IsValid { get; } dynamic Value { get; } } public class StrategyResult<T> : IStrategyResult { public T Value { get; private set; } public StrategyResult(T value) { this.Value = value; } public bool IsValid { get { throw new NotImplementedException(); } } dynamic IStrategyResult.Value { get { return this.Value; } } } [AttributeUsage(AttributeTargets.Class)] public sealed class RegisterActionAttribute : Attribute { public List<string> Dependencies { get; private set; } public RegisterActionAttribute(params string[] depdencies) { this.Dependencies = new List<string>(depdencies); } } public class StrategyAction { public string Name; public List<string> Dependencies; } public abstract class BasePlugin<T> : IStrategy { public IStrategyResult Execute(ISerializable info = null) { return new StrategyResult<T>(this.execute(info)); } protected abstract T execute(ISerializable info); }

Ejemplo de complemento:

[RegisterAction] public class MyFirstPlugin: BasePlugin<bool> { protected override bool execute(ISerializable info = null) { try { // do stuff return true; } catch (Exception) { return false; } } } [RegisterAction("MyFirstPlugin")] public class MySecondPlugin: BasePlugin<string> { protected override string execute(ISerializable info = null) { try { // do stuff return "success"; } catch (Exception) { return "failed"; } } }

Ejemplo de motor de ejecución:

public class Engine { public List<StrategyAction> registeredActions = new List<StrategyAction>(); private List<StrategyAction> queuedActions = new List<StrategyAction>(); public IStrategyResult Execute(string action, ISerializable info = null) { if (this.registeredActions.FirstOrDefault(a=>a.Name == action) == null) return null; // This code did not appear to be used anyway //var returnType = typeof (IStrategyResult<>); //var genericReturn = returnType.MakeGenericType(strategyAction.ResponseType); var instance = (IStrategy) UnityManager.Container.Resolve(typeof(IStrategy), action); return instance.Execute(info); } public List<IStrategyResult> ExecuteQueuedActions() { var results = new List<IStrategyResult>(); var actions = this.queuedActions.AsQueryable(); var sortedActions = TopologicalSort.Sort(actions, action => action.Dependencies, action => action.Name); foreach(var strategyAction in sortedActions) { this.queuedActions.Remove(strategyAction); results.Add(Execute(strategyAction.Name)); } return results; } }

Tenga en cuenta que cuando se cargan los complementos, la información de RegisterActionAttribute junto con el nombre del tipo de complemento cargado deben combinarse en una instancia de StrategyAction y cargarse en el campo registeredActions del motor.

Lo anterior permite que los complementos funcionen con tipos fuertes, pero aún así permite que el motor se ocupe de una variedad de tipos. Si necesita que el motor trabaje con datos más tipográficos, proporcione un ejemplo de cómo se espera que los llamadores de ExecuteQueuedActions funcionen con los resultados de ExecuteQueuedActions .


returnType en este encurtido dándole a tu constructor RegisterActionAttribute el argumento returnType . Como solo tiene un método Execute (), se ve obligado a lidiar con el hecho de que el tipo de retorno puede ser de diferentes tipos.

Usar la dynamic es tan bueno como es posible. Puede hacer que Execute () sea genérico, pero luego tendrá que lidiar con una falta de coincidencia entre su parámetro de tipo y el tipo de respuesta del atributo. No es una que el compilador pueda detectar, esto falla en el tiempo de ejecución. No es genérico.

Francamente, esto suena fuertemente como una flexibilidad demasiada. A riesgo de interpretar el punto de tener un tipo de retorno incorrecto, el resultado de una "acción de registro" es más bien booleano. Funcionó o no funcionó. Y es de hecho la forma en que lo implementó, su primer fragmento de plugin devuelve bool .

Con probabilidades muy altas de que no deberías usar bool tampoco. El fracaso debería hacer una explosión, usted lanzaría una excepción.