将泛型类型的实例返回到在运行时解析的函数
Returning an instance of a generic type to a function resolved at runtime
澄清一下,我使用动态和 MakeGenericType 进行了这项工作。但我忍不住认为有更好的方法可以做到这一点。我想做的是使用 Unity 创建一个 "plug-in" 加载器。我将在 post 代码中对其进行解释,以便您了解我在做什么。
首先,我只是 post 插件本身:
[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; }
}
一切都相当简单。这里的目标只是在 class 加载时附加一些元数据。加载是通过统一使用包装器进行的,该包装器使用文件搜索模式简单地将程序集加载到 bin 目录中,并将其添加到具有 StrategyActions 集合的单例 class 中。我不需要在这里粘贴所有统一代码,因为我知道它可以工作并注册和解析程序集。
那么现在进入问题的核心。我在执行操作的单例上有一个函数。这些应用了 Unity.Interception HandlerAttributes 并像这样传递了一个字符串(我可以 post 代码但我认为它不相关):
[ExecuteAction("MyPlugin")]
处理程序在已注册(添加到集合中)的单例 class 到 "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);
}
此执行包含在一个枚举器调用中,该调用 return 是一个结果集合,用于管理依赖项和其他内容(见下文)。调用者使用 ISTrategyResult{T} 的值 属性 引用这些值来执行其他业务规则定义的各种操作。
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 属性指定的 return 类型。如您所见,我正在捕获插件的类型和 return 类型。我正在使用 "generic" 变量通过使用 MakeGenericType 统一解析类型,效果很好。我还根据集合中的类型创建了一个代表 return 类型的泛型。
我在这里不喜欢的是必须使用动态 return 这个值到一个函数。我想不出一种方法来 return 作为 IStrategyResult{T} 因为显然 "dynamic Execute(..." 的调用者不能在 运行 时暗示 return 类型的功能。我仔细考虑了通过 MakeGenericMethod 调用来调用 Execute,因为我实际上有预期的类型 StrategyAction。如果我能在调用期间确定 T 的类型时找出 return IStrategyResult{T} 的强类型结果,那就太好了。
我明白为什么我不能用我当前的实现来做到这一点我只是想找到一种方法来包装所有这些功能而不使用动态。并希望有人可以提供一些可能有用的建议。如果这意味着将其与对非通用 classes 或类似内容的其他调用包装起来,那么如果这是唯一的解决方案,那也很好。
您通过为 RegisterActionAttribute 构造函数提供 returnType
参数来陷入困境。由于您只有一个 Execute() 方法,因此您不得不处理 return 类型可以是不同类型这一事实。
使用 dynamic
已经差不多了。您可以使 Execute() 成为通用的,但随后您将不得不处理其类型参数与属性的 ResponseType 之间的不匹配问题。不是编译器可以捕获的,这在运行时会失败。它不是通用的。
坦率地说,这听起来太灵活了。冒着错误解释 return 类型这一点的风险,"registration action" 的结果相当布尔值。它有效或无效。事实上,您的第一个插件片段就是这样实现的 return 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>
class 将 属性 设置为 return typeof(T)
按照惯例,您可以假设(或在 abstract StrategyResult<T> : IStrategyResult<T>
class 上强制使用继承)T
与 ReturnType
属性 相同非通用 IStrategyResult
接口。
您需要进行更全面的重构,而不仅仅是弄清楚如何调用您的插件。
[RegisterAction]
属性不需要保存 targetType 和 returnType,属性的这些参数很容易与代码不同步,使它们成为一个潜在的漏洞。
然后从您的设置的另一面思考:您如何使用数据,您如何处理您的 IStrategyResult<>
- 真的 必须是通用的还是有一种特定的方法可以封装结果类型?我无法想象一个 returns "anything" 到主机的插件系统。提示确实在您的 dynamic Execute(...)
中 - 您的参数和结果都失去了强类型,这表明强类型插件对任何事情都没有帮助。只需使用 object
或 - 更好 - 创建 StrategyResult
class 而不是当前界面并提供那里所需的任何属性(我添加了一些无聊的示例),例如:
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
.
的结果
澄清一下,我使用动态和 MakeGenericType 进行了这项工作。但我忍不住认为有更好的方法可以做到这一点。我想做的是使用 Unity 创建一个 "plug-in" 加载器。我将在 post 代码中对其进行解释,以便您了解我在做什么。
首先,我只是 post 插件本身:
[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; }
}
一切都相当简单。这里的目标只是在 class 加载时附加一些元数据。加载是通过统一使用包装器进行的,该包装器使用文件搜索模式简单地将程序集加载到 bin 目录中,并将其添加到具有 StrategyActions 集合的单例 class 中。我不需要在这里粘贴所有统一代码,因为我知道它可以工作并注册和解析程序集。
那么现在进入问题的核心。我在执行操作的单例上有一个函数。这些应用了 Unity.Interception HandlerAttributes 并像这样传递了一个字符串(我可以 post 代码但我认为它不相关):
[ExecuteAction("MyPlugin")]
处理程序在已注册(添加到集合中)的单例 class 到 "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);
}
此执行包含在一个枚举器调用中,该调用 return 是一个结果集合,用于管理依赖项和其他内容(见下文)。调用者使用 ISTrategyResult{T} 的值 属性 引用这些值来执行其他业务规则定义的各种操作。
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 属性指定的 return 类型。如您所见,我正在捕获插件的类型和 return 类型。我正在使用 "generic" 变量通过使用 MakeGenericType 统一解析类型,效果很好。我还根据集合中的类型创建了一个代表 return 类型的泛型。
我在这里不喜欢的是必须使用动态 return 这个值到一个函数。我想不出一种方法来 return 作为 IStrategyResult{T} 因为显然 "dynamic Execute(..." 的调用者不能在 运行 时暗示 return 类型的功能。我仔细考虑了通过 MakeGenericMethod 调用来调用 Execute,因为我实际上有预期的类型 StrategyAction。如果我能在调用期间确定 T 的类型时找出 return IStrategyResult{T} 的强类型结果,那就太好了。
我明白为什么我不能用我当前的实现来做到这一点我只是想找到一种方法来包装所有这些功能而不使用动态。并希望有人可以提供一些可能有用的建议。如果这意味着将其与对非通用 classes 或类似内容的其他调用包装起来,那么如果这是唯一的解决方案,那也很好。
您通过为 RegisterActionAttribute 构造函数提供 returnType
参数来陷入困境。由于您只有一个 Execute() 方法,因此您不得不处理 return 类型可以是不同类型这一事实。
使用 dynamic
已经差不多了。您可以使 Execute() 成为通用的,但随后您将不得不处理其类型参数与属性的 ResponseType 之间的不匹配问题。不是编译器可以捕获的,这在运行时会失败。它不是通用的。
坦率地说,这听起来太灵活了。冒着错误解释 return 类型这一点的风险,"registration action" 的结果相当布尔值。它有效或无效。事实上,您的第一个插件片段就是这样实现的 return 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>
class 将 属性 设置为 return typeof(T)
按照惯例,您可以假设(或在 abstract StrategyResult<T> : IStrategyResult<T>
class 上强制使用继承)T
与 ReturnType
属性 相同非通用 IStrategyResult
接口。
您需要进行更全面的重构,而不仅仅是弄清楚如何调用您的插件。
[RegisterAction]
属性不需要保存 targetType 和 returnType,属性的这些参数很容易与代码不同步,使它们成为一个潜在的漏洞。
然后从您的设置的另一面思考:您如何使用数据,您如何处理您的 IStrategyResult<>
- 真的 必须是通用的还是有一种特定的方法可以封装结果类型?我无法想象一个 returns "anything" 到主机的插件系统。提示确实在您的 dynamic Execute(...)
中 - 您的参数和结果都失去了强类型,这表明强类型插件对任何事情都没有帮助。只需使用 object
或 - 更好 - 创建 StrategyResult
class 而不是当前界面并提供那里所需的任何属性(我添加了一些无聊的示例),例如:
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
.