C# 使用 Activator.CreateInstance
我昨天问了一个关于使用反射或策略模式动态调用方法的问题。
然而,从那时起,我决定将这些方法更改为实现公共接口的单独类。原因是,每个类虽然有一些相似之处,但也执行该类特有的某些方法。
我一直在使用这样的策略:
switch (method)
{
case "Pivot":
return new Pivot(originalData);
case "GroupBy":
return new GroupBy(originalData);
case "Standard deviation":
return new StandardDeviation(originalData);
case "% phospho PRAS Protein":
return new PhosphoPRASPercentage(originalData);
case "AveragePPPperTreatment":
return new AveragePPPperTreatment(originalData);
case "AvgPPPNControl":
return new AvgPPPNControl(originalData);
case "PercentageInhibition":
return new PercentageInhibition(originalData);
default:
throw new Exception("ERROR: Method " + method + " does not exist.");
}
但是,随着潜在类数量的增长,我将需要不断添加新类,从而打破了修改关闭规则。
相反,我使用了这样的解决方案:
var test = Activator.CreateInstance(null, "MBDDXDataViews."+ _class);
ICalculation instance = (ICalculation)test.Unwrap();
return instance;
实际上,_class 参数是运行时传入的类的名称。 这是执行此操作的常见方法吗?这会产生性能问题吗?
我对反思还很陌生,所以欢迎您的建议。
I asked a question yesterday regarding using either reflection or Strategy Pattern for dynamically calling methods.
However, since then I have decided to change the methods into individual classes that implement a common interface. The reason being, each class, whilst bearing some similarities also perform certain methods unique to that class.
I had been using a strategy as such:
switch (method)
{
case "Pivot":
return new Pivot(originalData);
case "GroupBy":
return new GroupBy(originalData);
case "Standard deviation":
return new StandardDeviation(originalData);
case "% phospho PRAS Protein":
return new PhosphoPRASPercentage(originalData);
case "AveragePPPperTreatment":
return new AveragePPPperTreatment(originalData);
case "AvgPPPNControl":
return new AvgPPPNControl(originalData);
case "PercentageInhibition":
return new PercentageInhibition(originalData);
default:
throw new Exception("ERROR: Method " + method + " does not exist.");
}
However, as the number of potential classes grow, I will need to keep adding new ones, thus breaking the closed for modification rule.
Instead, I have used a solution as such:
var test = Activator.CreateInstance(null, "MBDDXDataViews."+ _class);
ICalculation instance = (ICalculation)test.Unwrap();
return instance;
Effectively, the _class parameter is the name of the class passed in at runtime.
Is this a common way to do this, will there be any performance issues with this?
I am fairly new to reflection, so your advice would be welcome.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
使用反射时,您应该首先问自己几个问题,因为您可能最终会得到一个难以维护的过于复杂的解决方案:
动态
调用解决问题(仅限.NET 4.0及更高版本)吗?通用性/动态
根据您的描述,我假设您在编译时不知道类型,您只知道它们共享接口
ICalculation
。如果这是正确的,那么上面的数字 (1) 和 (2) 在您的场景中可能是不可能的。性能
这是一个重要的问题。使用反射的开销可能会带来超过 400 倍的损失:即使是中等数量的调用也会减慢速度。
解决方案相对简单:不使用 Activator.CreateInstance,而是使用工厂方法(您已经拥有该方法),查找 MethodInfo 创建委托,将其缓存并使用从那时起,代表。这只会对第一次调用产生惩罚,后续调用具有接近本机的性能。
结合技术
这里有很多可能,但我真的需要更多地了解您的情况才能在这个方向上提供帮助。通常,我最终会将
动态
与泛型和缓存反射结合起来。当使用信息隐藏时(这在 OOP 中很常见),您最终可能会得到一个快速、稳定且可扩展性良好的解决方案。失去编译时类型安全
在五个问题中,这可能是最需要担心的一个。创建自己的异常以提供有关反射错误的清晰信息非常重要。这意味着:基于输入字符串或其他未经检查的信息对方法、构造函数或属性的每次调用都必须包装在 try/catch 中。仅捕获特定异常(一如既往,我的意思是:永远不要捕获 Exception 本身)。
重点关注
TargetException
(方法不存在)、TargetInitationException
(方法存在,但在调用时出现异常)、TargetParameterCountException
、MethodAccessException
(不是正确的权限,在 ASP.NET 中经常发生)、InvalidOperationException
(发生于泛型类型)。您并不总是需要尝试捕获所有它们,这取决于预期的输入和预期的目标对象。总结一下,
摆脱
Activator.CreateInstance
并使用 MethodInfo 查找工厂创建方法,然后使用Delegate.CreateDelegate
创建并缓存委托。只需将其存储在静态字典中,其中键等于示例代码中的类字符串。下面是一种快速但不那么肮脏的方法,可以安全地完成此操作,并且不会失去太多类型安全性。示例代码
When using reflection you should ask yourself a couple of questions first, because you may end up in an over-the-top complex solution that's hard to maintain:
dynamic
invocations (only .NET 4.0 and above)?Genericity / dynamic
From your description I assume you do not know the types at compile time, you only know they share the interface
ICalculation
. If this is correct, then number (1) and (2) above are likely not possible in your scenario.Performance
This is an important question to ask. The overhead of using reflection can impede a more than 400-fold penalty: that slows down even a moderate amount of calls.
The resolution is relatively easy: instead of using
Activator.CreateInstance
, use a factory method (you already have that), look up theMethodInfo
create a delegate, cache it and use the delegate from then on. This yields only a penalty on the first invocation, subsequent invocations have near-native performance.Combine technologies
A lot is possible here, but I'd really need to know more of your situation to assist in this direction. Often, I end up combining
dynamic
with generics, with cached reflection. When using information hiding (as is normal in OOP), you may end up with a fast, stable and still well-extensible solution.Losing compile time type safety
Of the five questions, this is perhaps the most important one to worry about. It is very important to create your own exceptions that give clear information about reflection mistakes. That means: every call to a method, constructor or property based on an input string or otherwise unchecked information must be wrapped in a try/catch. Catch only specific exceptions (as always, I mean: never catch
Exception
itself).Focus on
TargetException
(method does not exist),TargetInvocationException
(method exists, but rose an exc. when invoked),TargetParameterCountException
,MethodAccessException
(not the right privileges, happens a lot in ASP.NET),InvalidOperationException
(happens with generic types). You don't always need to try to catch all of them, it depends on the expected input and expected target objects.To sum it up
Get rid of your
Activator.CreateInstance
and use MethodInfo to find the factory-create method, and useDelegate.CreateDelegate
to create and cache the delegate. Simply store it in a staticDictionary
where the key is equal to the class-string in your example code. Below is a quick but not-so-dirty way of doing this safely and without losing too much type safety.Sample code
我建议您为工厂实现提供一个方法
RegisterImplementation
。因此,每个新类只是对该方法的调用,并且您不会更改工厂代码。更新:
我的意思是这样的:
创建一个定义计算的接口。根据您的代码,您已经这样做了。为了完整起见,我将在答案的其余部分中使用以下接口:
您的工厂将如下所示:
出于简单性的原因,这个简单的工厂类缺乏错误检查。
更新2:
您可以在应用程序初始化例程中的某个地方像这样初始化它:
I suggest you give your factory implementation a method
RegisterImplementation
. So every new class is just a call to that method and you are not changing your factories code.UPDATE:
What I mean is something like this:
Create an interface that defines a calculation. According to your code, you already did this. For the sake of being complete, I am going to use the following interface in the rest of my answer:
Your factory will look something like this:
This simple factory class is lacking error checking for the reason of simplicity.
UPDATE 2:
You would initialize it like this somewhere in your applications initialization routine:
作为一个例子,如何在构造函数中添加初始化:
类似于:
but written with Linq Expression, part of code is taken here:
用法如下:
同样可以用DynamicMethods来实现。
此外,这些类不需要从同一接口或基类继承。
谢谢,维塔利
Just as an example how to add initialization in the constructor:
Something similar to:
but written with Linq Expression, part of code is taken here:
The usage is following:
The same can be implemented with DynamicMethods.
Also, the classes are not required to be inherited from the same interface or base class.
Thanks, Vitaliy
在这种情况下,我使用的一种策略是使用特殊属性来标记我的各种实现以指示其键,并扫描活动程序集以查找具有该键的类型:
这样,只需使用新的键字符串创建一个新类,您可以自动“插入”工厂,而无需修改工厂代码。
您还会注意到,我没有依赖每个实现来提供特定的构造函数,而是在我希望类实现的接口上创建了一个 Initialize 方法。只要他们实现了该接口,我就能够将“originalData”发送给他们,而不会出现任何反射怪异。
我还建议使用像 Ninject 这样的依赖注入框架,而不是使用 Activator.CreateInstance。这样,您的操作实现就可以使用构造函数注入来实现其各种依赖项。
One strategy that I use in cases like this is to flag my various implementations with a special attribute to indicate its key, and scan the active assemblies for types with that key:
That way, just by creating a new class with a new key string, you can automatically "plug in" to the factory, without having to modify the factory code at all.
You'll also notice that rather than depending on each implementation to provide a specific constructor, I've created an Initialize method on the interface I expect the classes to implement. As long as they implement the interface, I'll be able to send the "originalData" to them without any reflection weirdness.
I'd also suggest using a dependency injection framework like Ninject instead of using Activator.CreateInstance. That way, your operation implementations can use constructor injection for their various dependencies.
本质上,听起来您想要工厂模式。在这种情况下,您定义输入到输出类型的映射,然后像您正在做的那样在运行时实例化该类型。
示例:
您有 X 个类,它们都共享 IDoSomething 的公共接口。
Essentially, it sounds like you want the factory pattern. In this situation, you define a mapping of input to output types and then instantiate the type at runtime like you are doing.
Example:
You have X number of classes, and they all share a common interface of IDoSomething.