C#使用Activator.CreateInstance
我昨天问了一个关于使用reflection或策略模式dynamic调用方法的问题。
然而,从那以后,我决定将这些方法改为实现一个通用接口的各个类。 原因在于,每个class级虽然具有一些相似之处,但也执行了该class级特有的某些方法。
我一直在使用这样的策略:
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."); }
然而,随着潜在类的数量的增加,我将需要不断增加新的类,从而打破修改规则的封闭性。
相反,我已经使用了一个解决scheme:
var test = Activator.CreateInstance(null, "MBDDXDataViews."+ _class); ICalculation instance = (ICalculation)test.Unwrap(); return instance;
实际上,_class参数是在运行时传入的类的名称。 这是一个常见的方式来做到这一点,会有任何性能问题呢?
我是相当新的反思,所以你的意见是值得欢迎的。
在使用反思时,首先应该问自己几个问题,因为最终可能会导致难以维护的复杂解决scheme:
- 有没有办法解决使用generics或类/接口inheritance的问题?
- 我可以使用
dynamic
调用(只有.NET 4.0及以上版本)解决问题吗? - 性能是否重要,即我的reflection方法或实例化调用会被调用一次,两次还是一百万次?
- 我可以结合技术来获得一个聪明但可行/可以理解的解决scheme吗?
- 我没有编译时间types安全吗?
generics/dynamic
从你的描述我假设你不知道在编译时的types,你只知道他们共享接口ICalculation
。 如果这是正确的,那么上面的数字(1)和(2)在您的情况下可能不可能。
性能
这是一个重要的问题。 使用reflection的开销可能阻碍400多倍的惩罚:即使是中等数量的呼叫也会减慢。
解决方法是相对容易的:而不是使用Activator.CreateInstance
,使用工厂方法(你已经有),查找MethodInfo
创build一个委托,caching它,并使用委托从那时起。 这只会在第一次调用时产生惩罚,后续的调用会接近本机的性能。
结合技术
这里有很多可能,但我真的需要了解更多的情况来协助这个方向。 通常,我最终将dynamic
与generics结合起来,并进行cachingreflection。 当使用信息隐藏(在OOP中是正常的)时,你可能会得到一个快速,稳定和仍然可以很好扩展的解决scheme。
失去编译时间types的安全性
在这五个问题中,这也许是最重要的问题。 创build自己的例外情况是非常重要的,这些例外会给出关于反思错误的明确信息 这意味着:基于inputstring或其他未经检查的信息对方法,构造函数或属性的每次调用都必须包装在try / catch中。 仅捕获特定的exception(像往常一样,我的意思是:从来没有捕获Exception
本身)。
重点在TargetException
(方法不存在), TargetInvocationException
(方法存在,但调用时增加了一个TargetInvocationException
), TargetParameterCountException
, MethodAccessException
(不正确的权限,在ASP.NET中发生很多), InvalidOperationException
(与genericstypes发生)。 您并不总是需要尝试去捕捉所有这些,这取决于预期的input和预期的目标对象。
把它们加起来
摆脱你的Activator.CreateInstance
并使用MethodInfo来查找工厂创build方法,并使用Delegate.CreateDelegate
来创build和caching委托。 只需将其存储在静态Dictionary
,其中的键等于示例代码中的类string。 下面是一个快速但不是太脏的方式来安全地做到这一点,而不会失去太多的types安全。
示例代码
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(); } }
我build议你给你的工厂实现一个方法RegisterImplementation
。 所以每一个新的类都只是对这个方法的调用,而不会改变你的工厂代码。
更新:
我的意思是这样的:
创build一个定义计算的接口。 根据你的代码,你已经做到了。 为了完整起见,我将在我的答案的其余部分使用以下界面:
public interface ICalculation { void Initialize(string originalData); void DoWork(); }
你的工厂看起来像这样:
public class CalculationFactory { private readonly Dictionary<string, Func<string, ICalculation>> _calculations = new Dictionary<string, Func<string, ICalculation>>(); public void RegisterCalculation<T>(string method) where T : ICalculation, new() { _calculations.Add(method, originalData => { var calculation = new T(); calculation.Initialize(originalData); return calculation; }); } public ICalculation CreateInstance(string method, string originalData) { return _calculations[method](originalData); } }
这个简单的工厂类为简单起见缺乏错误检查。
更新2:
你可以在你的应用程序初始化例程中的某个地方初始化它:
CalculationFactory _factory = new CalculationFactory(); public void RegisterCalculations() { _factory.RegisterCalculation<Pivot>("Pivot"); _factory.RegisterCalculation<GroupBy>("GroupBy"); _factory.RegisterCalculation<StandardDeviation>("Standard deviation"); _factory.RegisterCalculation<PhosphoPRASPercentage>("% phospho PRAS Protein"); _factory.RegisterCalculation<AveragePPPperTreatment>("AveragePPPperTreatment"); _factory.RegisterCalculation<AvgPPPNControl>("AvgPPPNControl"); _factory.RegisterCalculation<PercentageInhibition>("PercentageInhibition"); }
我在这种情况下使用的一种策略是使用特殊属性标记我的各种实现以指示其密钥,并使用该密钥扫描活动程序集的types:
[AttributeUsage(AttributeTargets.Class)] public class OperationAttribute : System.Attribute { public OperationAttribute(string opKey) { _opKey = opKey; } private string _opKey; public string OpKey {get {return _opKey;}} } [Operation("Standard deviation")] public class StandardDeviation : IOperation { public void Initialize(object originalData) { //... } } public interface IOperation { void Initialize(object originalData); } public class OperationFactory { static OperationFactory() { _opTypesByKey = (from a in AppDomain.CurrentDomain.GetAssemblies() from t in a.GetTypes() let att = t.GetCustomAttributes(typeof(OperationAttribute), false).FirstOrDefault() where att != null select new { ((OperationAttribute)att).OpKey, t}) .ToDictionary(e => e.OpKey, e => et); } private static IDictionary<string, Type> _opTypesByKey; public IOperation GetOperation(string opKey, object originalData) { var op = (IOperation)Activator.CreateInstance(_opTypesByKey[opKey]); op.Initialize(originalData); return op; } }
这样,只需用新的密钥string创build一个新的类,就可以自动“插入”到工厂,而不必修改工厂代码。
你也会注意到,我不是依靠每个实现来提供一个特定的构造函数,而是在接口上创build一个Initialize方法,我希望这些类可以实现。 只要他们实现了接口,我就可以发送“originalData”给他们,而不会有任何reflection的怪异。
我也build议使用像Ninject的dependency injection框架,而不是使用Activator.CreateInstance。 这样,你的操作实现可以使用构造器注入来实现各种依赖。
正如一个例子,如何在构造函数中添加初始化:
类似于:
Activator.CreateInstance(Type.GetType("ConsoleApplication1.Operation1"), initializationData);
但是用Linq Expression编写的,代码的一部分在这里 :
public class Operation1 { public Operation1(object data) { } } public class Operation2 { public Operation2(object data) { } } public class ActivatorsStorage { public delegate object ObjectActivator(params object[] args); private readonly Dictionary<string, ObjectActivator> activators = new Dictionary<string,ObjectActivator>(); private ObjectActivator CreateActivator(ConstructorInfo ctor) { Type type = ctor.DeclaringType; ParameterInfo[] paramsInfo = ctor.GetParameters(); ParameterExpression param = Expression.Parameter(typeof(object[]), "args"); Expression[] argsExp = new Expression[paramsInfo.Length]; for (int i = 0; i < paramsInfo.Length; i++) { Expression index = Expression.Constant(i); Type paramType = paramsInfo[i].ParameterType; Expression paramAccessorExp = Expression.ArrayIndex(param, index); Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType); argsExp[i] = paramCastExp; } NewExpression newExp = Expression.New(ctor, argsExp); LambdaExpression lambda = Expression.Lambda(typeof(ObjectActivator), newExp, param); return (ObjectActivator)lambda.Compile(); } private ObjectActivator CreateActivator(string className) { Type type = Type.GetType(className); if (type == null) throw new ArgumentException("Incorrect class name", "className"); // Get contructor with one parameter ConstructorInfo ctor = type.GetConstructors() .SingleOrDefault(w => w.GetParameters().Length == 1 && w.GetParameters()[0].ParameterType == typeof(object)); if (ctor == null) throw new Exception("There is no any constructor with 1 object parameter."); return CreateActivator(ctor); } public ObjectActivator GetActivator(string className) { ObjectActivator activator; if (activators.TryGetValue(className, out activator)) { return activator; } activator = CreateActivator(className); activators[className] = activator; return activator; } }
用法如下:
ActivatorsStorage ast = new ActivatorsStorage(); var a = ast.GetActivator("ConsoleApplication1.Operation1")(initializationData); var b = ast.GetActivator("ConsoleApplication1.Operation2")(initializationData);
DynamicMethods也可以实现同样的function。
另外,这些类不需要从相同的接口或基类inheritance。
谢谢,Vitaliy
基本上,这听起来像你想要的工厂模式。 在这种情况下,您可以定义input到输出types的映射,然后像运行时那样在运行时实例化types。
例:
你有X个类,他们都共享IDoSomething的通用接口。
public interface IDoSomething { void DoSomething(); } public class Foo : IDoSomething { public void DoSomething() { // Does Something specific to Foo } } public class Bar : IDoSomething { public void DoSomething() { // Does something specific to Bar } } public class MyClassFactory { private static Dictionary<string, Type> _mapping = new Dictionary<string, Type>(); static MyClassFactory() { _mapping.Add("Foo", typeof(Foo)); _mapping.Add("Bar", typeof(Bar)); } public static void AddMapping(string query, Type concreteType) { // Omitting key checking code, etc. Basically, you can register new types at runtime as well. _mapping.Add(query, concreteType); } public IDoSomething GetMySomething(string desiredThing) { if(!_mapping.ContainsKey(desiredThing)) throw new ApplicationException("No mapping is defined for: " + desiredThing); return Activator.CreateInstance(_mapping[desiredThing]) as IDoSomething; } }
- 这里没有错误检查。 你确定_class将解决到一个有效的类? 你是否控制了所有可能的值,或者这个string以某种方式被最终用户填充?
- 反思一般来说是最昂贵的,而不是避免它。 性能问题与您计划以此方式实例化的对象数量成比例。
- 在使用dependency injection框架之前,请阅读它的批评。 =)
- 你如何检查目录是否存在于Windows上的C?
- java.lang.NoClassDefFoundError:org / apache / commons / fileupload / FileItemFactory