只是为了澄清,我有这个工作使用动态和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
没有任何了解,并且必须使用dynamic
或object
,那么以下可能工作:
基础设施:
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
属性相同。