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

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

然后是接口(interface):

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} 的强类型结果的方法,那就太好了.

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

最佳答案

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

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

然后从设置的另一面思考:如何使用数据,如何处理 IStrategyResult<> - 它真的必须是通用的还是有一种特定的方法可以封装结果类型?我无法想象一个插件系统会向主机返回“任何东西”。提示确实在您的 dynamic Execute(...) 中- 您的参数和结果都失去了强类型,这表明强类型插件对任何事情都没有帮助。只需使用 object或者 - 更好 - 制作 StrategyResult class 而不是当前接口(interface),并提供那里所需的任何属性(我添加了一些琐碎的示例),例如:

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

关于c# - 将泛型类型的实例返回到在运行时解析的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33351398/

相关文章:

java - 如何从命令行读取数组并使用JAVA中的通用方法显示它们?

c# - 方法中的泛型,c#

c# - Convert.ToString 方法和 Object.ToString() 在全局化方面的区别

c# - SQLite 删除列

c# - MVVM Light + Unity 还是 Prism?

swift - 使用或不使用泛型的 'where' 子句有什么区别?

c# - GetHashCode() 返回值应该基于原始对象的状态还是修改后的对象的状态?

c# - 在 C# 中使用多个分隔符拆分字符串

c# - 这是什么魔法?

c# - 单例如何妨碍可测试性