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

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

6 ответов


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

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

генерики / динамический

из вашего описания я предполагаю, что вы не знаете типы во время компиляции, вы только знаете, что они разделяют интерфейс 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--> где ключ равен class-string в вашем примере кода. Ниже находится краткое, но не очень грязный способ сделать это безопасно и без потери слишком много безопасности типа.

пример кода

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

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

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