簡體   English   中英

C#使用Activator.CreateInstance

[英]C# Using 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參數是在運行時傳入的類的名稱。 這是一種常見的方法,這會有任何性能問題嗎?

我很反思,所以歡迎你提出建議。

使用反射時,您首先應該問自己幾個問題,因為您最終可能會遇到難以維護的過度復雜的解決方案:

  1. 有沒有辦法使用泛型或類/接口繼承來解決問題?
  2. 我可以使用dynamic調用(僅限.NET 4.0及更高版本)解決問題嗎?
  3. 性能是否重要,即我的反射方法或實例化調用是否會被調用一次,兩次或一百萬次?
  4. 我可以結合技術來獲得智能但可行/可理解的解決方案嗎?
  5. 我沒有丟失編譯時類型安全嗎?

通用/動態

根據您的描述,我假設您在編譯時不知道類型,您只知道它們共享接口ICalculation 如果這是正確的,那么您的方案中可能無法使用上面的數字(1)和(2)。

性能

這是一個要問的重要問題。 使用反射的開銷可能會導致超過400倍的懲罰:即使是適量的呼叫也會減慢速度。

解決方案相對簡單:不使用Activator.CreateInstance ,而是使用工廠方法(您已經擁有它),查找MethodInfo創建委托,緩存它並從此使用委托。 這在第一次調用時僅產生一個懲罰,后續調用具有接近本機的性能。

結合技術

這里有很多可能,但我真的需要了解更多你的情況以協助這個方向。 通常,我最終將dynamic與泛型相結合,並使用緩存反射。 當使用信息隱藏(在OOP中是正常的)時,您可能最終得到一個快速,穩定且仍然可以很好擴展的解決方案。

丟失編譯時類型安全

在這五個問題中,這可能是最重要的一個問題。 創建自己的異常非常重要,可以提供有關反射錯誤的明確信息。 這意味着:基於輸入字符串或其他未經檢查的信息對方法,構造函數或屬性的每次調用都必須包裝在try / catch中。 只捕獲特定的異常(一如既往,我的意思是:永遠不會捕獲Exception本身)。

專注於TargetException (方法不存在), TargetInvocationException (方法存在,但在調用時上升為exc。), TargetParameterCountExceptionMethodAccessException (不是正確的權限,在ASP.NET中發生很多), InvalidOperationException (發生在泛型類型中)。 您並不總是需要嘗試捕獲所有這些,這取決於預期的輸入和預期的目標對象。

把它們加起來

刪除Activator.CreateInstance並使用MethodInfo查找factory-create方法,並使用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();
    }
}

我建議你給工廠實現一個方法RegisterImplementation 因此,每個新類只是對該方法的調用,而您不會更改工廠代碼。

更新:
我的意思是這樣的:

創建定義計算的接口。 根據你的代碼,你已經這樣做了。 為了完整起見,我將在其余的答案中使用以下界面:

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

僅作為示例如何在構造函數中添加初始化:

類似的東西:

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也可以實現相同的功能。

此外,不需要從同一接口或基類繼承類。

謝謝,Vitaliy

我在這種情況下使用的一種策略是使用特殊屬性標記我的各種實現以指示其鍵,並使用該鍵掃描活動程序集中的類型:

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

這樣,只需創建一個帶有新密鑰字符串的新類,就可以自動“插入”工廠,而無需修改工廠代碼。

您還會注意到,我不是依賴於每個實現來提供特定的構造函數,而是在接口上創建了一個我希望類實現的Initialize方法。 只要他們實現了界面,我就能夠將“originalData”發送給他們,而不會產生任何反射怪異。

我還建議使用像Ninject這樣的依賴注入框架,而不是使用Activator.CreateInstance。 這樣,您的操作實現可以使用構造函數注入來處理各種依賴項。

從本質上講,這聽起來像你想要的工廠模式。 在這種情況下,您可以定義輸入到輸出類型的映射,然后像運行那樣在運行時實例化類型。

例:

你有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. 這里沒有錯誤檢查。 你絕對確定_class會解析為有效的類嗎? 您是否正在控制所有可能的值,或者這個字符串是否以某種方式被最終用戶填充?
  2. 反思通常比避免反射成本最高。 性能問題與您計划以這種方式實例化的對象數量成比例。
  3. 在您運行並使用依賴注入框架之前,請閱讀對它的批評。 =)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM