简体   繁体   English

C#使用Activator.CreateInstance

[英]C# Using Activator.CreateInstance

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. 实际上,_class参数是在运行时传入的类的名称。 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. 我很反思,所以欢迎你提出建议。

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: 使用反射时,您首先应该问自己几个问题,因为您最终可能会遇到难以维护的过度复杂的解决方案:

  1. Is there a way to solve the problem using genericity or class/interface inheritance? 有没有办法使用泛型或类/接口继承来解决问题?
  2. Can I solve the problem using dynamic invocations (only .NET 4.0 and above)? 我可以使用dynamic调用(仅限.NET 4.0及更高版本)解决问题吗?
  3. Is performance important, ie will my reflected method or instantiation call be called once, twice or a million times? 性能是否重要,即我的反射方法或实例化调用是否会被调用一次,两次或一百万次?
  4. Can I combine technologies to get to a smart but workable/understandable solution? 我可以结合技术来获得智能但可行/可理解的解决方案吗?
  5. Am I ok with losing compile time type safety? 我没有丢失编译时类型安全吗?

Genericity / dynamic 通用/动态

From your description I assume you do not know the types at compile time, you only know they share the interface ICalculation . 根据您的描述,我假设您在编译时不知道类型,您只知道它们共享接口ICalculation If this is correct, then number (1) and (2) above are likely not possible in your scenario. 如果这是正确的,那么您的方案中可能无法使用上面的数字(1)和(2)。

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. 使用反射的开销可能会导致超过400倍的惩罚:即使是适量的呼叫也会减慢速度。

The resolution is relatively easy: instead of using Activator.CreateInstance , use a factory method (you already have that), look up the MethodInfo create a delegate, cache it and use the delegate from then on. 解决方案相对简单:不使用Activator.CreateInstance ,而是使用工厂方法(您已经拥有它),查找MethodInfo创建委托,缓存它并从此使用委托。 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. 通常,我最终将dynamic与泛型相结合,并使用缓存反射。 When using information hiding (as is normal in OOP), you may end up with a fast, stable and still well-extensible solution. 当使用信息隐藏(在OOP中是正常的)时,您可能最终得到一个快速,稳定且仍然可以很好扩展的解决方案。

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. 这意味着:基于输入字符串或其他未经检查的信息对方法,构造函数或属性的每次调用都必须包装在try / catch中。 Catch only specific exceptions (as always, I mean: never catch Exception itself). 只捕获特定的异常(一如既往,我的意思是:永远不会捕获Exception本身)。

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). 专注于TargetException (方法不存在), TargetInvocationException (方法存在,但在调用时上升为exc。), TargetParameterCountExceptionMethodAccessException (不是正确的权限,在ASP.NET中发生很多), InvalidOperationException (发生在泛型类型中)。 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 use Delegate.CreateDelegate to create and cache the delegate. 删除Activator.CreateInstance并使用MethodInfo查找factory-create方法,并使用Delegate.CreateDelegate创建和缓存委托。 Simply store it in a static Dictionary where the key is equal to the class-string in your example code. 只需将其存储在静态Dictionary ,其中键等于示例代码中的类字符串。 Below is a quick but not-so-dirty way of doing this safely and without losing too much type safety. 下面是一种快速但不那么脏的方法,可以安全地进行此操作,而不会丢失太多的类型安全性。

Sample code 示例代码

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();
    }
}

I suggest you give your factory implementation a method RegisterImplementation . 我建议你给工厂实现一个方法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: 为了完整起见,我将在其余的答案中使用以下界面:

public interface ICalculation
{
    void Initialize(string originalData);
    void DoWork();
}

Your factory will look something like this: 你的工厂看起来像这样:

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);
    }
}

This simple factory class is lacking error checking for the reason of simplicity. 出于简单的原因,这个简单的工厂类缺少错误检查。

UPDATE 2: 更新2:
You would initialize it like this somewhere in your applications initialization routine: 您可以在应用程序初始化例程中的某处将其初始化为:

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");
}

Just as an example how to add initialization in the constructor: 仅作为示例如何在构造函数中添加初始化:

Something similar to: 类似的东西:

Activator.CreateInstance(Type.GetType("ConsoleApplication1.Operation1"), initializationData);
but written with Linq Expression, part of code is taken here : 但是用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; } } 

The usage is following: 用法如下:

 ActivatorsStorage ast = new ActivatorsStorage(); var a = ast.GetActivator("ConsoleApplication1.Operation1")(initializationData); var b = ast.GetActivator("ConsoleApplication1.Operation2")(initializationData); 

The same can be implemented with DynamicMethods. DynamicMethods也可以实现相同的功能。

Also, the classes are not required to be inherited from the same interface or base class. 此外,不需要从同一接口或基类继承类。

Thanks, Vitaliy 谢谢,Vitaliy

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: 我在这种情况下使用的一种策略是使用特殊属性标记我的各种实现以指示其键,并使用该键扫描活动程序集中的类型:

[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 => e.t);
    }
    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;
    }
}

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. 您还会注意到,我不是依赖于每个实现来提供特定的构造函数,而是在接口上创建了一个我希望类实现的Initialize方法。 As long as they implement the interface, I'll be able to send the "originalData" to them without any reflection weirdness. 只要他们实现了界面,我就能够将“originalData”发送给他们,而不会产生任何反射怪异。

I'd also suggest using a dependency injection framework like Ninject instead of using Activator.CreateInstance. 我还建议使用像Ninject这样的依赖注入框架,而不是使用Activator.CreateInstance。 That way, your operation implementations can use constructor injection for their various dependencies. 这样,您的操作实现可以使用构造函数注入来处理各种依赖项。

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. 你有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;
     }
}
  1. There's no error checking here. 这里没有错误检查。 Are you absolutely sure that _class will resolve to a valid class? 你绝对确定_class会解析为有效的类吗? Are you controlling all the possible values or does this string somehow get populated by an end-user? 您是否正在控制所有可能的值,或者这个字符串是否以某种方式被最终用户填充?
  2. Reflection is generally most costly than avoiding it. 反思通常比避免反射成本最高。 Performance issues are proportionate to the number of objects you plan to instantiate this way. 性能问题与您计划以这种方式实例化的对象数量成比例。
  3. Before you run off and use a dependency injection framework read the criticisms of it. 在您运行并使用依赖注入框架之前,请阅读对它的批评。 =) =)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM