将泛型类型的实例返回到在运行时解析的函数

时间:2015-10-26 17:00:17

标签: c# .net generics reflection

为了澄清,我使用动态和MakeGenericType工作。但我不能帮助,但认为有更好的方法来做到这一点。我想要做的是创建一个"插件"装载机,使用Unity。我将在发布代码时对其进行解释,以便您了解我正在做的事情。

首先我发布插件本身:

[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
        };
    }
}

在这里要注意的事情。首先是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
        };
    }
}

然后是接口:

public interface IStrategy<T>
{
    IStrategyResult<T> Execute(ISerializable info = null);
}

public interface IStrategyResult<T>
{
    bool IsValid { get; set; }
    T Value { get; set; }
}

一切都相当直接。这里的目标只是在加载时将一些元数据附加到类中。使用包装器通过统一进行加载,该包装器使用文件搜索模式简单地将组件加载到bin目录中,并将其添加到具有StrategyActions集合的单个类中。我不需要在此处粘贴所有统一代码,因为我知道它可以工作并注册和解析程序集。

现在问题的问题。我在执行动作的单例上有一个函数。这些应用于Unity.Interception HandlerAttributes并传递一个类似的字符串(我可以为此发布代码,但我认为它不相关):

[ExecuteAction("MyPlugin")]

处理程序将单例类上的以下执行函数调用到&#34;执行&#34;已注册的函数(添加到集合中)。

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

此执行包含在枚举器调用中,该调用返回结果集合,这些结果用于管理依赖关系以及不依赖关系(见下文)。调用者使用ISTrategyResult {T}的Value属性引用这些值,以执行其他业务规则定义的各种操作。

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;
    }

现在请注意,这是有效的,我得到了插件RegisterAction属性指定的返回类型。正如您所看到的,我正在捕获插件的类型和返回类型。我正在使用&#34; generic&#34;变量通过使用MakeGenericType来解析具有单位的类型,这很好。我还基于集合中的类型创建表示返回类型的泛型。

我不喜欢这里必须使用dynamic将此值返回给函数。我无法找到一种方法将其作为IStrategyResult {T}返回,因为显然调用者是&#34;动态执行(...&#34;不能,在运行时,意味着返回类型函数。我用MakeGenericMethod调用调用Execute,因为我实际上有一个预期的类型是StrategyAction。如果我可以找到一些东西来返回IStrategyResult {T}的强类型结果,那将会很酷。在通话期间确定T的类型。

我明白为什么我不能用我当前的实现做到这一点我只是试图找到一种方法来包装所有这些功能而不使用动态。并希望有人可以提供一些可能有用的建议。如果这意味着将其与其他对非泛型类或类似类的调用包装起来,那么如果这是唯一的解决方案那也没关系。

4 个答案:

答案 0 :(得分:7)

你需要一个更彻底的重构,而不仅仅是弄清楚如何调用你的插件。

[RegisterAction]属性不需要保存targetType和returnType,这些属性参数很容易与代码不同步,使它们成为潜在的漏洞。

然后从您设置的另一面思考:您如何使用数据,您如何处理IStrategyResult<> - 真的 必须是通用的还是你可以用特定的方式封装结果类型吗?我无法想象一个插件系统会向主机返回“任何东西”。提示实际上在您的dynamic Execute(...)中 - 您的参数和结果都失去了强大的输入,向您显示强插入插件无助于任何操作。只需使用object或 - 更好 - 制作一个StrategyResult类而不是当前界面,并提供必要的属性(我添加了一些无聊的例子),例如:

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;
     }
  }
}

然后您的IStrategy变为:

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

您还可以更改属性,以便更有效地加载大型插件:

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

...并使用如下属性:

[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);
      }
    }
}

答案 1 :(得分:5)

假设ExecuteActions的来电者对任何插件或结果中的T都不了解,并且无论如何都必须使用dynamicobject,那么以下可能有效:

<强>基础设施:

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

示例插件:

[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";
        }
    }
}

示例执行引擎:

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;
    }

}

请注意,加载插件时,RegisterActionAttribute信息以及加​​载的插件类型名称需要合并到StrategyAction实例中并加载到registeredActions字段中引擎。

以上允许插件使用强类型,但仍然允许引擎处理各种类型。如果您需要引擎使用更强类型的数据,那么请提供一个示例,说明ExecuteQueuedActions的调用方应如何使用ExecuteQueuedActions的结果。

答案 2 :(得分:2)

通过给RegisterActionAttribute构造函数returnType参数,你进入了这个pickle。由于您只有一个Execute()方法,因此您必须处理返回类型可以是不同类型的事实。

使用dynamic和它一样好。你可以使Execute()通用,但是你必须处理它的类型参数和属性的ResponseType之间的不匹配。不是编译器可以捕获的,这在运行时失败。它不是通用的。

坦率地说,这听起来像一个灵活性太多了。有可能解释返回类型不正确的点,&#34;注册操作的结果&#34;相当布尔。它有效或无效。实际上就是你实现它的方式,你的第一个插件片段确实返回bool

如果赔率非常高,则不应使用bool。失败应该爆炸,你会抛出异常。

答案 3 :(得分:2)

为什么不像这样定义超级接口IStrategyResult

interface IStrategyResult
{
    Type ReturnType { get; }
}

interface IStrategyResult<T> : IStrategyResult
{
    // your code here
}

然后像这样定义你的执行:

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

让您的StrategyResult : IStrategyResult<T>类设置该属性以返回typeof(T)

按照惯例,您可以假定(或强制使用abstract StrategyResult<T> : IStrategyResult<T>类的继承)T与非通用ReturnType的{​​{1}}属性相同接口