50 votes

C # Utilisation de Activator.CreateInstance

J'ai posé une question hier concernant l'utilisation soit de réflexion ou de Stratégie de Modèle de dynamique de l'appel de méthodes.

Cependant, depuis lors, j'ai décidé de changer les méthodes dans les classes qui implémentent une interface commune. La raison d'être, chaque classe, tout en gardant quelques similitudes également effectuer certaines méthodes propres à la classe.

J'avais été en utilisant 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, comme le nombre potentiel d'expansion des classes, j'ai besoin de continuer à ajouter de nouveaux, et de briser ainsi le fermées pour la modification de la règle.

Au lieu de cela, j'ai utilisé une solution:

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

Effectivement, le _class paramètre est le nom de la classe à passer lors de l'exécution. Est-ce une façon courante pour ce faire, il y aura des problèmes de performances avec cela?

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

72voto

Abel Points 24335

Lors de l'utilisation de la réflexion que vous devez vous poser quelques questions, d'abord, parce que vous pouvez vous retrouver dans un au-dessus de la solution complexe qui est difficile à maintenir:

  1. Est-il un moyen de résoudre le problème en utilisant genericity ou de classe/l'héritage de l'interface?
  2. Puis-je résoudre le problème à l'aide d' dynamic invocations (seule .NET 4.0 et ci-dessus)?
  3. La performance est importante, c'est à dire mon reflète la méthode ou l'instanciation d'un appel d'être appelée une fois, deux fois ou un million de fois?
  4. Puis-je combiner des technologies pour arriver à une smart mais efficace/compréhensible solution?
  5. Je suis ok avec perte de temps de compilation de sécurité de type?

Genericity / dynamique

À partir de votre description, je suppose que vous ne connaissez pas le type au moment de la compilation, vous ne connaissez qu'ils partagent l'interface ICalculation. Si cela est correct, alors le nombre (1) et (2) ci-dessus sont susceptibles de ne pas possible dans votre scénario.

Performance

C'est une question importante à se poser. La surcharge de l'aide de la réflexion peut entraver une plus de 400 fois la pénalité: qui ralentit même une quantité modérée d'appels.

La résolution est relativement simple: au lieu d'utiliser Activator.CreateInstance, utilisez une méthode de fabrique (vous l'avez déjà), de rechercher l' MethodInfo créer un délégué, le cache et l'utilisation de la déléguer à partir de là. Cela donne seulement une pénalité sur le premier appel, à la suite d'invocations ont une performance quasi-native.

Combiner des technologies

Tout est possible ici, mais j'avais vraiment besoin d'en savoir plus sur votre situation afin de l'aider dans ce sens. Souvent, j'arrive à la fin combinant dynamic avec les génériques, avec mise en cache de la réflexion. Lors de l'utilisation de se cacher de l'information (comme il est normal, en programmation orientée objet), vous pouvez vous retrouver avec un rapide, stable et toujours le bien-solution évolutive.

Perdre de la compilation de sécurité de type

De ces cinq questions, c'est peut-être le plus important à s'inquiéter. Il est très important de créer vos propres exceptions que de donner de l'information claire sur la réflexion des erreurs. Cela signifie que chaque appel d'une méthode, constructeur ou bien sur la base d'une chaîne d'entrée ou autrement décoché l'information doit être enveloppé dans un try/catch. Attraper des exceptions spécifiques (comme toujours, je veux dire: 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 le droit de privilèges, il se passe beaucoup de choses dans ASP.NET), InvalidOperationException (c'est le cas avec les types génériques). Vous n'avez pas toujours besoin d'essayer toutes les attraper, il dépend de la contribution qui est attendue et prévue des objets cibles.

Pour résumer

Débarrassez-vous de vos Activator.CreateInstance et l'utilisation MethodInfo pour trouver l'usine de méthode de création et d'utilisation Delegate.CreateDelegate de créer et de mettre en cache le délégué. Il suffit de stocker dans un statique Dictionary où la clé est égale à la classe string dans votre exemple de code. 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é.

Exemple de 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();
    }
}

17voto

Daniel Hilgarth Points 90722

Je vous suggère de donner votre usine de mise en œuvre d'une méthode RegisterImplementation. Ainsi, chaque nouvelle classe est juste un appel à cette méthode et vous ne modifiez pas vos usines code.

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

Créer une interface qui définit un calcul. En fonction de votre code, vous l'avez déjà fait cela. 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 de ressembler à 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 simple usine de classe est le manque de vérification des erreurs pour la raison de la simplicité.

Mise à JOUR 2:
Vous l'initialiser comme ceci quelque part dans vos applications de routine d'initialisation:

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

7voto

Vitaliy Points 1466

Par exemple, comment ajouter de l'initialisation dans le constructeur:

Quelque chose de similaire à:

Activator.CreateInstance(Type.GetType("ConsoleApplication1.Operation1"), initializationData);

mais écrit avec une Expression Linq, une partie de code est pris 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'utilisation est la suivante:

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

La même chose peut être mis en œuvre avec DynamicMethods.

Aussi, les classes ne sont pas nécessaires pour être hérité depuis la même interface ou classe de base.

Merci, Vitaliy

6voto

StriplingWarrior Points 56276

Une stratégie que j'ai utiliser dans ce cas est de marquer mes différentes implémentations avec un attribut spécial pour indiquer sa clé, de numérisation et de l'actif des assemblages 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, seulement par la création d'une nouvelle classe avec une nouvelle chaîne de clé, vous pouvez automatiquement "plug in" à l'usine, sans avoir à modifier le code d'usine.

Vous remarquerez également que, plutôt qu'en fonction de chaque mise en œuvre d'offrir un constructeur, j'ai créé une méthode Initialize sur l'interface j'attends les classes à mettre en œuvre. Tant qu'ils implémentent l'interface, je vais être en mesure d'envoyer le "originalData" sans réflexion "bizarre".

Je voudrais aussi suggérer à l'aide d'une injection de dépendance cadre comme Ninject au lieu d'utiliser l'Activateur.CreateInstance. De cette façon, votre opération implémentations pouvez utiliser le constructeur pour l'injection de leurs diverses dépendances.

1voto

Tejs Points 23834

Essentiellement, on dirait que vous voulez le modèle de fabrique. Dans ce cas, vous définissez un mappage des entrées pour les types de sortie et puis instancier le type à l’exécution comme vous faites.

Exemple :

Vous avez X nombre de classes et ils tous partage une interface commune de IDoSomething.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X