Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 948
Darren Young Avatar asked Mar 10 '11 16:03

Darren Young


2 Answers

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

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

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

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();     } } 
like image 52
Abel Avatar answered Sep 19 '22 19:09

Abel


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:

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:
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"); } 
like image 37
Daniel Hilgarth Avatar answered Sep 20 '22 19:09

Daniel Hilgarth