【问题标题】:Returning an instance of a generic type to a function resolved at runtime将泛型类型的实例返回给在运行时解析的函数
【发布时间】:2015-10-26 17:00:16
【问题描述】:

澄清一下,我使用动态和 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")]

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

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 调用对 Execute 的调用,因为我实际上具有 StrategyAction 的预期类型。如果我能弄清楚在调用期间确定 T 的类型时如何返回 IStrategyResult {T} 的强类型结果,那就太酷了.

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

【问题讨论】:

  • 获得结果后如何处理?看起来您需要一些相应的IHandler&lt;T&gt; 接口来处理结果。然后您可以将ExecuteHandle 方法组合为Actions。
  • 我添加了一个包装函数,我可以使用它来聚合调用结果,希望能回答你的问题。还是我误解了你的回答?
  • 我想我明白你在说什么,这是有道理的。但是,鉴于我需要返回其中一些值,我认为会遇到相同的问题,即知道调用者中 T 的类型。我知道这是这样做的固有问题。我想这只是野兽的本性,我唯一能做的就是在调用者中添加一些检查/强制转换以适当地处理结果。关于动态的酷(但危险)的事情是我不需要强制转换来引用结果。但一旦它处于动态状态,它就会处于哲学上的“灰色”区域。
  • 您如何汇总结果?你在检查 IStrategyResult 中 T 的类型吗?如果你能提供一个样本就好了。
  • 您能否举例说明ExecuteQueuedActions 的调用者希望对结果执行什么操作? IStrategy&lt;T&gt; 中的 T 是否应该以某种方式受到约束或限制为一组固定的类型?

标签: c# .net generics reflection


【解决方案1】:

您需要进行更全面的重构,而不仅仅是弄清楚如何调用您的插件。

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

然后从您的设置的另一端思考:您如何使用数据,您如何处理您的 IStrategyResult&lt;&gt; - 它真的必须是通用的还是有特定的方法你可以封装结果的类型?我无法想象一个向主机返回“任何东西”的插件系统。提示确实在您的 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);
      }
    }
}

【讨论】:

  • 很抱歉大家回复晚了,我最终被安排在另一个项目上。现在终于回到这个话题了。
  • 插件的强类型是为了加快处理速度。我不想做一堆条件块(这限制了插件的可扩展性,而不必对核心进行更改)或调用者中的反射(考虑到插件调用的数量可能会减慢它可能是这个插件系统应该工作的方式,它可能会在枚举集合时调用这些东西成百上千次。
  • 为了同时实现(避免冗长的 if 和性能),在插件中添加一个知道如何将自己的数据粘合到上下文数据的方法 - ApplyResultToContext 专门用于此目的
【解决方案2】:

假设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 的结果。

【讨论】:

    【解决方案3】:

    您通过为 RegisterActionAttribute 构造函数提供 returnType 参数来进入这个泡菜。由于您只有一个 Execute() 方法,因此您不得不处理返回类型可以是不同类型的事实。

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

    坦率地说,这听起来像是一种太多的灵活性。冒着错误解释返回类型的风险,“注册操作”的结果是相当布尔值。它工作或没有工作。实际上是您实现它的方式,您的第一个插件 sn-p 确实返回 bool

    您也不应该使用bool 的可能性非常高。失败应该引起轰动,你会抛出异常。

    【讨论】:

      【解决方案4】:

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

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

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

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

      并让您的StrategyResult : IStrategyResult&lt;T&gt; 类将属性设置为返回typeof(T)

      按照惯例,您可以假设(或强制在 abstract StrategyResult&lt;T&gt; : IStrategyResult&lt;T&gt; 类上使用继承)T 与非泛型 IStrategyResult 接口的 ReturnType 属性相同。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-11
        • 2022-07-27
        • 2019-02-28
        • 1970-01-01
        相关资源
        最近更新 更多