C# 使用 Activator.CreateInstance [英] C# Using Activator.CreateInstance

查看:28
本文介绍了C# 使用 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;

实际上,_class 参数是在运行时传入的类的名称.这是执行此操作的常用方法吗?这样做会有任何性能问题吗?

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.

推荐答案

在使用反射时,您应该先问自己几个问题,因为您最终可能会得到一个难以维护的过于复杂的解决方案:

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. 有没有办法使用泛型或类/接口继承来解决问题?
  2. 我可以使用 dynamic 调用来解决问题吗(仅限 .NET 4.0 及更高版本)?
  3. 性能是否重要,即我的反射方法或实例化调用会被调用一次、两次还是一百万次?
  4. 我可以结合技术来获得智能但可行/易于理解的解决方案吗?
  5. 失去编译时类型安全我可以吗?
  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)?
  3. Is performance important, i.e. 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?

通用性/动态

根据您的描述,我假设您在编译时不知道这些类型,您只知道它们共享接口 ICalculation.如果这是正确的,那么上面的数字 (1) 和 (2) 在您的场景中可能是不可能的.

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.

这是一个很重要的问题.使用反射的开销会阻碍超过 400 倍的惩罚:即使是中等数量的调用也会减慢速度.

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.

解决方法相对简单:不使用Activator.CreateInstance,使用工厂方法(你已经有了),查找MethodInfo 创建一个委托,缓存它并从那时起使用委托.这只会在第一次调用时产生惩罚,后续调用具有接近本机的性能.

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. This yields only a penalty on the first invocation, subsequent invocations have near-native performance.

这里有很多可能,但我真的需要更多地了解您的情况才能在这个方向上提供帮助.通常,我最终将 dynamic 与泛型和缓存反射结合起来.使用信息隐藏时(在 OOP 中很常见),您最终可能会得到一个快速、稳定且仍然具有良好扩展性的解决方案.

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.

在五个问题中,这可能是最需要担心的问题.创建您自己的异常以提供有关反射错误的清晰信息非常重要.这意味着:基于输入字符串或其他未经检查的信息对方法、构造函数或属性的每次调用都必须包含在 try/catch 中.只捕获特定的异常(一如既往,我的意思是:永远不要捕获 Exception 本身).

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).

关注TargetException(方法不存在),TargetInvocationException(方法存在,但在调用时增加了一个例外),TargetParameterCountExceptionMethodAccessException(不是正确的权限,在 ASP.NET 中经常发生),InvalidOperationException(发生在泛型类型中).您并不总是需要尝试捕获所有这些对象,这取决于预期的输入和预期的目标对象.

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.

去掉你的 Activator.CreateInstance 并使用 MethodInfo 找到工厂创建方法,并使用 Delegate.CreateDelegate 创建和缓存委托.只需将其存储在静态 Dictionary 中,其中键等于示例代码中的类字符串.下面是一种快速但不那么脏的方法,可以安全地执行此操作,并且不会失去太多的类型安全性.

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. Simply store it in a static Dictionary 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.

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

这篇关于C# 使用 Activator.CreateInstance的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆