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

本文关键字:运行时 函数 泛型类型 实例 返回 | 更新日期: 2023-09-27 18:19:14

只是为了澄清,我有这个工作使用动态和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; }
}

都很简单。这里的目标只是在加载类时将一些元数据附加到该类。加载是通过unity使用包装器进行的,包装器使用文件搜索模式加载bin目录中的程序集,并将其添加到带有StrategyActions集合的单例类中。我不需要粘贴所有的统一代码在这里,因为我知道它的工作和注册和解决程序集。

现在进入问题的核心。我在单例上有一个执行动作的函数。这些应用于Unity。拦截HandlerAttributes并传递一个字符串,像这样(我可以发布这个代码,但我不认为这是相关的):

[ExecuteAction("MyPlugin")]

处理程序在单例类上调用以下execute函数来"执行"已注册(添加到集合)的函数。

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属性指定的返回类型。正如你所看到的,我正在捕获插件的类型和返回类型。我使用"通用"变量通过使用MakeGenericType来解析统一的类型,它工作得很好。我还创建了一个泛型,表示基于集合类型的返回类型。

这里我不喜欢的是必须使用动态将这个值返回给函数。我无法找出一种方法来返回这个作为IStrategyResult{T},因为显然调用者"动态执行(…)"不能,在运行时,暗示函数的返回类型。我仔细考虑了使用MakeGenericMethod调用来执行调用,因为我实际上有预期的类型StrategyAction。如果我能在调用过程中确定T的类型的同时返回IStrategyResult{T}的强类型结果,那将是很酷的。

我确实理解为什么我不能用我目前的实现做到这一点,我只是想找到一种方法来包装所有这些功能,而不使用动态。希望有人能给点有用的建议。如果这意味着将它与其他非泛型类的调用或类似的东西包装在一起,如果这是唯一的解决方案,那也很好。

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

你需要一个更全面的重构,而不仅仅是找出如何调用你的插件。

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

假设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的结果工作的一个例子。

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

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

坦率地说,这听起来像是一个太多的灵活性。冒着解释返回类型不正确的风险,"注册操作"的结果是相当布尔的。它起作用或者不起作用。实际上,您实现它的方式是,您的第一个插件片段确实返回bool

很有可能你也不应该使用bool。失败应该引起轰动,你会抛出异常。

为什么不这样定义一个超级接口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与非泛型IStrategyResult接口的ReturnType属性相同。