C # Utilisant L'Activateur.Créatéinstance

j'ai posé une question hier concernant l'utilisation de la réflexion ou de la stratégie pour des méthodes d'appel dynamique.

cependant, depuis lors j'ai décidé de changer les méthodes en classes individuelles qui implémentent une interface commune. La raison étant, chaque classe, tout en portant quelques similitudes effectuer également certaines méthodes uniques à cette classe.

j'avais utilisé une stratégie en tant que telle:

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

Cependant, au fur et à mesure que le nombre de classes potentielles augmentera, je devrai continuer à en ajouter de nouvelles, violant ainsi la règle de la fermeture pour modification.

au lieu de cela, j'ai utilisé une solution comme telle:

var test = Activator.CreateInstance(null, "MBDDXDataViews."+ _class);
       ICalculation instance = (ICalculation)test.Unwrap();
       return instance;

effectivement, le paramètre _class est le nom de la classe transmise à l'exécution. Est-ce une façon courante de le faire, y aura-t-il des problèmes de rendement?

je suis assez nouveau à la réflexion, donc vos conseils seraient les bienvenus.

51
demandé sur Abel 2011-03-10 19:29:20

6 réponses

lorsque vous utilisez la réflexion, vous devriez d'abord vous poser quelques questions, parce que vous pourriez vous retrouver dans une solution complexe qui est difficile à maintenir:

  1. Existe-t-il un moyen de résoudre le problème en utilisant la généralité ou l'héritage de classe/interface?
  2. puis-je résoudre le problème en utilisant dynamic invocations (seulement .NET 4.0 et plus)?
  3. est une performance importante, c'est-à-dire ma méthode réfléchie ou appel d'instanciation appelé une fois, deux fois ou un million de fois?
  4. puis-je combiner des technologies pour obtenir une solution intelligente, mais réalisable/compréhensible?
  5. est-ce que je peux perdre la sécurité de la compilation?

Genericity / dynamique

de votre description je suppose que vous ne connaissez pas les types au moment de la compilation, vous savez seulement qu'ils partagent l'interface ICalculation . Si cela est correct, alors les numéros (1) et (2) ci-dessus ne sont probablement pas possibles dans votre scénario.

Performance

C'est une question importante à poser. La surabondance d'utilisation de la réflexion peut empêcher une pénalité de plus de 400 fois: cela ralentit même une quantité modérée d'appels.

la résolution est relativement facile: au lieu d'utiliser Activator.CreateInstance , utilisez une méthode d'usine( vous avez déjà cela), regardez le MethodInfo créer un délégué, le mettre en cache et utilisez le délégué à partir de là. Cela ne donne qu'une pénalité sur la première invocation, les invocations subséquentes ont une performance presque native.

combinent technologies

beaucoup de choses sont possibles ici, mais j'ai vraiment besoin d'en savoir plus sur votre situation pour vous aider dans cette direction. Souvent, je finis par combiner dynamic avec des génériques, avec une réflexion en cache. Lorsque vous utilisez la dissimulation d'informations (comme c'est normal dans OOP), vous pouvez finir avec un rapide, stable et immobile solution bien extensible.

perdre la sécurité de type de temps de compilation

des cinq questions, celle-ci est peut-être la plus importante. Il est très important de créer vos propres exceptions qui donnent des informations claires sur les erreurs de réflexion. Cela signifie: tout appel à une méthode, à un constructeur ou à une propriété basé sur une chaîne de saisie ou toute autre information non contrôlée doit être enveloppé dans un try/catch. Attraper des exceptions spécifiques (comme toujours, je signifie: ne jamais attraper Exception lui-même).

Focus sur TargetException (méthode n'existe pas), TargetInvocationException (méthode existe, mais a un exc. lorsqu'il est invoqué), TargetParameterCountException , MethodAccessException (pas les bons privilèges, se passe beaucoup dans ASP.NET), InvalidOperationException (se produit avec les types génériques). Vous n'avez pas toujours besoin d'essayer de les attraper tous, cela dépend des entrées attendues et des objets cibles attendus.

pour résumer

Get débarrassez-vous de votre Activator.CreateInstance et utilisez MethodInfo pour trouver la méthode factory-create, et utilisez Delegate.CreateDelegate pour créer et mettre en cache le délégué. Il suffit de le stocker dans un Dictionary statique où la clé est égale à la corde de classe dans votre code d'exemple. Ci-dessous un rapide mais pas si sale façon de le faire en toute sécurité et sans perdre trop de type de sécurité.

code échantillon

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();
    }
}
69
répondu Abel 2011-03-11 18:23:43

je vous suggère de donner à votre mise en œuvre d'usine une méthode RegisterImplementation . Donc chaque nouvelle classe n'est qu'un appel à cette méthode et vous ne changerez pas votre code d'usine.

mise à jour:

Ce que je veux dire, c'est quelque chose comme ceci:

Créer une interface qui définit un calcul. Selon votre code, vous l'avez déjà fait. Par souci d'être complet, je vais utiliser l'interface suivante dans le reste de ma réponse:

public interface ICalculation
{
    void Initialize(string originalData);
    void DoWork();
}

votre usine ressemblera à quelque chose comme ceci:

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

cette classe d'usine simple manque de vérification d'erreur pour la raison de la simplicité.

mise à JOUR 2:

Vous l'initialiseriez comme ceci quelque part dans votre routine d'initialisation des applications:

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");
}
18
répondu Daniel Hilgarth 2011-03-11 11:06:02

comme exemple comment ajouter l'initialisation dans le constructeur:

quelque chose de similaire à:

Activator.CreateInstance(Type.GetType("ConsoleApplication1.Operation1"), initializationData);
mais écrit avec L'Expression Linq, une partie du code est prise ici :
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;
    }
}

l'usage est le suivant:

ActivatorsStorage ast = new ActivatorsStorage();
var a = ast.GetActivator("ConsoleApplication1.Operation1")(initializationData);
var b = ast.GetActivator("ConsoleApplication1.Operation2")(initializationData);

la même chose peut être implémentée avec des méthodes dynamiques.

en outre, les classes ne sont pas tenus d'être héritées de la même interface ou de la même base classe.

Merci, Vitaliy

7
répondu Vitaliy 2011-03-10 20:14:22

une stratégie que j'utilise dans des cas comme celui-ci est de signaler mes différentes implémentations avec un attribut spécial pour indiquer sa clé, et de scanner les assemblages actifs pour les types avec cette clé:

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

de cette façon, juste en créant une nouvelle classe avec une nouvelle chaîne de touches, vous pouvez automatiquement" brancher " à l'usine, sans avoir à modifier le code d'usine du tout.

vous noterez également que plutôt que de dépendre de chaque mise en œuvre à fournir un constructeur spécifique, j'ai créé une méthode D'initialisation sur l'interface que je m'attends à ce que les classes implémentent. Tant qu'ils mettent en place l'interface, je pourrai leur envoyer la "originalData" sans aucune bizarrerie de réflexion.

je suggère aussi d'utiliser un framework d'injection de dépendances comme Ninject au lieu d'utiliser L'activateur.CreateInstance. De cette façon, vos implémentations d'opération peuvent utiliser l'injection du constructeur pour leurs différentes dépendances.

6
répondu StriplingWarrior 2011-03-10 17:03:45

Essentiellement, il semble que vous voulez le modèle de fabrique. Dans cette situation, vous définissez un mappage des entrées vers les types de sortie et instanciez le type à l'exécution comme vous le faites.

exemple:

vous avez X nombre de classes, et ils partagent tous une interface commune D'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
répondu Tejs 2011-03-10 16:37:40
  1. il n'y a pas d'erreur ici. Êtes-vous absolument sûr que _class se résoudra à une classe valide? Est-ce que vous contrôlez toutes les valeurs possibles ou est-ce que cette chaîne est en quelque sorte générée par un utilisateur final?
  2. la réflexion est généralement plus coûteuse que son évitement. Les problèmes de performances sont proportionnelles au nombre d'objets que vous envisagez d'instancier cette façon.
  3. avant de vous enfuir et d'utiliser un framework d'injection de dépendances lire les critiques. = )
1
répondu SFun28 2011-03-10 16:39:20