C# С Помощью Активатора.Метод 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 - это имя класса, переданного во время выполнения.
Это распространенный способ сделать это, будут ли какие-либо проблемы с производительностью?



Я довольно новичок в размышлениях, поэтому ваш совет будет приветствоваться.

913   6  

6 ответов:

при использовании рефлексии вы должны сначала задать себе пару вопросов, потому что вы можете оказаться в более сложном решении, которое трудно поддерживать:

  1. есть ли способ решить проблему с помощью универсальности или наследования класса / интерфейса?
  2. могу ли я решить проблему с помощью dynamic вызовы (только .NET 4.0 и выше)?
  3. важна ли производительность, т. е. будет ли мой отраженный метод или вызов экземпляра вызываться один раз, дважды или миллион раз?
  4. могу ли я объединить технологии, чтобы получить умное, но работоспособное/понятное решение?
  5. я в порядке с потерей безопасности типа времени компиляции?

Genericity / dynamic

из вашего описания я предполагаю, что вы не знаете типы во время компиляции, вы только знаете, что они разделяют интерфейс ICalculation. Если это правильно, то число (1) и (2) выше, вероятно, не возможно в вашем сценарий.

производительность

это важный вопрос, чтобы спросить. Накладные расходы на использование отражения могут препятствовать более чем 400-кратному штрафу: это замедляет даже умеренное количество вызовов.

разрешение относительно легко: вместо использования Activator.CreateInstance, используйте заводской метод (у вас уже есть это), посмотрите на MethodInfo создайте делегат, кэшируйте его и используйте делегат с этого момента. Это дает только штраф при первом вызове, последующем вызовов практически без снижения производительности.

объединить технологии

здесь многое возможно, но мне действительно нужно знать больше о вашей ситуации, чтобы помочь в этом направлении. Часто я в конечном итоге комбинирую dynamic с дженериками, с кэшированным отражением. При использовании скрытия информации (как обычно в ООП), вы можете получить быстрое, стабильное и все еще хорошо расширяемое решение.

потеря времени компиляции тип безопасности

из пяти вопросов, это, пожалуй, самое важное, о чем стоит беспокоиться. Очень важно создавать свои собственные исключения, которые дают четкую информацию об ошибках отражения. Это означает: каждый вызов метода, конструктора или свойства на основе входной строки или другой непроверенной информации должен быть обернут в try/catch. Ловите только конкретные исключения (как всегда, я имею в виду: никогда не ловите ).

фокус на TargetException (способ не существует), TargetInvocationException (метод существует, но поднялся искл. при вызове), TargetParameterCountException,MethodAccessException (не правильные привилегии, происходит много в ASP.NET),InvalidOperationException (бывает с универсальными типами). Вам не всегда нужно пытаться поймать их все, это зависит от ожидаемого ввода и ожидаемых целевых объектов.

в итоге

избавься от своего Activator.CreateInstance и использовать MethodInfo, чтобы найти метод factory-create, и использовать Delegate.CreateDelegate для создания и кэширования делегата. Просто храните его в статический!--14--> где ключ равен класса-строка в вашем примере кода. Ниже находится краткое, но не очень грязный способ сделать это безопасно и без потери слишком много безопасности типа.

пример кода

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, часть кода берется здесь:
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.

кроме того, классы не должны наследоваться от того же интерфейса или базового класса.

Спасибо, Виталий

одна стратегия, которую я использую в таких случаях, заключается в том, чтобы помечать мои различные реализации специальным атрибутом, чтобы указать его ключ, и сканировать активные сборки для типов с этим ключом:

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

таким образом, просто создав новый класс с новой ключевой строкой, вы можете автоматически "подключаться" к фабрике, не изменяя заводской код вообще.

вы также заметите, что вместо того, чтобы зависеть от каждой реализации, чтобы обеспечить определенный конструктор, я создал метод инициализации на интерфейсе, который я ожидаю, что классы будут реализованы. Пока они реализуют интерфейс, я смогу отправить им "originalData" без каких-либо странностей отражения.

Я бы также предложил использовать структуру инъекции зависимостей, такую как Ninject, вместо использования активатора.Метод 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. перед тем, как запустить и использовать фреймворк инъекции зависимостей, прочитайте его критику. =)

Comments

    Ничего не найдено.