使用反射时,您应该首先问自己几个问题,因为您最终可能会遇到难以维护的复杂解决方案:
- 有没有办法使用泛型或类/接口继承来解决问题?
- 我可以使用
dynamic
调用(仅限 .NET 4.0 及更高版本)解决问题吗?
- 性能是否重要,即我的反射方法或实例化调用会被调用一次、两次还是一百万次?
- 我可以结合技术来获得一个智能但可行/可理解的解决方案吗?
- 我可以失去编译时类型安全吗?
泛型/动态
根据您的描述,我假设您在编译时不知道类型,您只知道它们共享接口ICalculation
。如果这是正确的,那么上面的数字 (1) 和 (2) 在您的场景中可能是不可能的。
表现
这是一个重要的问题。使用反射的开销可以阻止超过 400 倍的惩罚:即使是中等数量的调用也会减慢。
解决方法相对简单:使用工厂方法(您已经拥有)代替使用Activator.CreateInstance
,查找MethodInfo
创建委托,缓存它并从那时起使用委托。这只会对第一次调用产生惩罚,随后的调用具有接近本机的性能。
结合技术
这里有很多可能,但我真的需要更多地了解你的情况才能在这个方向上提供帮助。通常,我最终会结合dynamic
泛型和缓存反射。使用信息隐藏(在 OOP 中很常见)时,您最终可能会得到一个快速、稳定且可扩展性良好的解决方案。
失去编译时类型安全
在这五个问题中,这可能是最需要担心的一个。创建自己的异常以提供有关反射错误的清晰信息非常重要。这意味着:基于输入字符串或其他未经检查的信息对方法、构造函数或属性的每次调用都必须包装在 try/catch 中。只捕获特定的异常(一如既往,我的意思是:永远不要捕获Exception
自己)。
关注TargetException
(方法不存在),TargetInvocationException
(方法存在,但在调用时增加一个 exc.)TargetParameterCountException
,,MethodAccessException
(不是正确的权限,在 ASP.NET 中经常发生),InvalidOperationException
(发生在泛型类型中)。您并不总是需要尝试捕获所有这些,这取决于预期的输入和预期的目标对象。
把它们加起来
摆脱你Activator.CreateInstance
并使用 MethodInfo 找到工厂创建方法,并使用Delegate.CreateDelegate
来创建和缓存委托。只需将其存储在静态Dictionary
中,其中键等于示例代码中的类字符串。下面是一种快速但不那么脏的方法,可以安全地执行此操作,并且不会失去太多的类型安全性。
示例代码
public class TestDynamicFactory
{
// static storage
private static Dictionary<string, Func<ICalculate>> InstanceCreateCache = new Dictionary<string, Func<ICalculate>>();
// how to invoke it
static int Main()
{
// invoke it, this is lightning fast and the first-time cache will be arranged
// also, no need to give the full method anymore, just the classname, as we
// use an interface for the rest. Almost full type safety!
ICalculate instanceOfCalculator = this.CreateCachableICalculate("RandomNumber");
int result = instanceOfCalculator.ExecuteCalculation();
}
// searches for the class, initiates it (calls factory method) and returns the instance
// TODO: add a lot of error handling!
ICalculate CreateCachableICalculate(string className)
{
if(!InstanceCreateCache.ContainsKey(className))
{
// get the type (several ways exist, this is an eays one)
Type type = TypeDelegator.GetType("TestDynamicFactory." + className);
// NOTE: this can be tempting, but do NOT use the following, because you cannot
// create a delegate from a ctor and will loose many performance benefits
//ConstructorInfo constructorInfo = type.GetConstructor(Type.EmptyTypes);
// works with public instance/static methods
MethodInfo mi = type.GetMethod("Create");
// the "magic", turn it into a delegate
var createInstanceDelegate = (Func<ICalculate>) Delegate.CreateDelegate(typeof (Func<ICalculate>), mi);
// store for future reference
InstanceCreateCache.Add(className, createInstanceDelegate);
}
return InstanceCreateCache[className].Invoke();
}
}
// example of your ICalculate interface
public interface ICalculate
{
void Initialize();
int ExecuteCalculation();
}
// example of an ICalculate class
public class RandomNumber : ICalculate
{
private static Random _random;
public static RandomNumber Create()
{
var random = new RandomNumber();
random.Initialize();
return random;
}
public void Initialize()
{
_random = new Random(DateTime.Now.Millisecond);
}
public int ExecuteCalculation()
{
return _random.Next();
}
}