web-dev-qa-db-fra.com

L'utilisation de la dynamique est-elle considérée comme une mauvaise pratique?

En C #, quelqu'un peut faire:

MyClass myInstance        = new MyClass();
dynamic mydynamicInstance = myInstance;

Et puis, invoquez une méthode, comme:

//This method takes a MyClass argument and does something.
Caller.InvokeMethod(myDynamicInstance);

Maintenant, cela conduira à la détermination du type myInstance au moment de l'exécution et, s'il est valide, le Caller.InvokeMethod sera appelé normalement.

Maintenant, ma question est de savoir si cela est considéré comme une mauvaise pratique d'utiliser dynamic, en particulier dans les cas suivants:

1) InvokeMethod instancie une autre instance de type myDynamicInstance, en utilisant la réflexion à l'intérieur.

2) Il existe une classe de base abstraite MyBaseClass et plusieurs sous-classes, dont MyBaseClass. Si nous avons un certain nombre de méthodes surchargées de InvokeMethod pour toutes ces classes dérivées, pourrions-nous l'utiliser afin de permettre au moment de l'exécution la détermination du type, puis l'invocation appropriée via la surcharge de méthode (ou liaison tardive sur l'appel) d'une méthode de cette classe)?:

public abstract class MyBaseClass         {/*...*/}
public class MyClass        : MyBaseClass {/*...*/}
public class MyAnotherClass : MyBaseClass {/*...*/}

MyBaseClass myBaseClassRef = new MyClass();
dynamic myDynamicInstance  = myBaseClassRef;

Caller.InvokeMethod(myDynamicInstance);
39
Nick Louloudakis

La réponse courte est OUI, c'est une mauvaise pratique d'utiliser dynamique.

Pourquoi?

Le mot clé dynamic fait référence à la liaison tardive de type, ce qui signifie que le système vérifie le type uniquement pendant l'exécution plutôt que pendant la compilation. Cela signifiera alors que l'utilisateur , au lieu du programmeur, est laissé pour découvrir l'erreur potentielle . L'erreur peut être une MissingMethodException, mais il peut également s'agir d'un appel non destiné à une méthode existante avec un mauvais comportement. Imaginez un appel à une méthode qui aboutit à calculer un mauvais prix ou à calculer un mauvais niveau d'oxygène.

De manière générale, la vérification de type permet d'obtenir un calcul déterministe, et donc, lorsque vous le pouvez, vous devez l'utiliser. Voici une question sur les défauts de dynamique .

Cependant, la dynamique peut être utile ...

  • Interopérez avec COM comme avec Office
  • Interopérez avec les langages où les types dynamiques font partie du langage (IronPython, IronRuby) car dynamic a été introduit pour aider à les porter sur .Net.
  • Peut remplacer le code complexe de réflexion par un code élégant et discret (cependant selon le cas, vous devez toujours profiler les deux approches pour vérifier laquelle est la plus appropriée) en termes de performances et de contrôles au moment de la compilation).

La base de code évolue tout au long du cycle de vie de l'application et même si la dynamique semble correcte maintenant, elle crée un précédent qui peut impliquer une augmentation de l'utilisation dynamique des mots clés par votre équipe. Cela peut entraîner une augmentation des coûts de maintenance (dans le cas où la signature susmentionnée évolue, vous pouvez la remarquer trop tard). Bien sûr, vous pouvez vous fier aux tests unitaires, aux tests humains de non régression, etc. Mais lorsque vous devez choisir entre la qualité liée à la discipline humaine et automatiquement vérifiée par la qualité liée à l'ordinateur, choisissez la dernière. C'est moins sujet aux erreurs.

Dans ton cas...

Dans votre cas, il semble que vous puissiez utiliser le schéma d'héritage commun (le premier ci-dessous et celui que vous mentionnez dans votre question), car dynamic ne vous en donnera pas avantage supplémentaire (cela vous coûtera juste plus de puissance de traitement et vous fera courir le risque de futurs bugs potentiels).

Cela dépend si vous pouvez changer le code de la hiérarchie MyClass et/ou Caller.InvokeMethod.

Énumérons les différentes alternatives possibles à la dynamique ...

  • Alternative compilée à vérification de type à l'appel de méthode par mot clé dynamique:

La plus courante utilise l'appel virtuel d'interface comme cette instance.InvokeMethod () avec héritage appelant la bonne implémentation.

public interface IInvoker : { void InvokeMethod(); }
public abstract class MyBaseClass : IInvoker { public abstract void InvokeMethod(); }
public class MyAnotherClass : MyBaseClass { public override void InvokeMethod() { /* Do something */ } }
public class MyClass : MyBaseClass { public override void InvokeMethod() { /* Do something */ } }

Un autre un peu moins performant consiste à utiliser les méthodes d'extension

public static class InvokerEx:
{
    public static void Invoke(this MyAnotherClass c) { /* Do something */ } }
    public static void Invoke(this MyClass c) { /* Do something */ } }
}

S'il y a plusieurs "visiteurs" de la hiérarchie MyBaseClass, vous pouvez utiliser le modèle de visiteur :

public interface IVisitor 
{
    void Visit(this MyAnotherClass c);
    void Visit(this MyClass c);
}

public abstract class MyBaseClass : IInvoker { public abstract void Accept(IVisitor visitor); }
public class MyAnotherClass : MyBaseClass { public override void Accept(IVisitor visitor) { visitor.Visit(this); } }
public class MyClass : MyBaseClass { public override void Accept(IVisitor visitor) { visitor.Visit(this); } }

Autres variantes mais pas très utiles ici ( Méthode générique ) mais intéressantes pour la comparaison des performances:

public void InvokeMethod<T>(T instance) where T : IInvoker { return instance.InvokeMethod(); }
  • Alternative dynamique à l'appel de méthode par mot clé dynamique:

Si vous devez appeler une méthode inconnue au moment de la compilation, j'ai ajouté ci-dessous les différentes techniques que vous pourriez utiliser et mis à jour les résultats de performance:

MethodInfo.CreateDelegate

        _method = typeof (T).GetMethod("InvokeMethod");
        _func = (Func<T, int>)_method.CreateDelegate(typeof(Func<T, int>));

Remarque: Cast to Func est nécessaire pour éviter d'appeler DynamicInvoke (car il est généralement plus lent).

DynamicMethod et ILGenerator.Emit

Il crée en fait l'appel complet à partir de zéro, c'est le plus flexible mais vous devez avoir une certaine expérience en assembleur pour l'apprécier pleinement.

        _dynamicMethod = new DynamicMethod("InvokeMethod", typeof (int), new []{typeof(T)}, GetType().Module);
        ILGenerator il = _dynamicMethod.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, _method);
        il.Emit(OpCodes.Ret);
        _func = (Func<T, int>) _dynamicMethod.CreateDelegate(typeof (Func<T, int>));

Expression Linq

Il est similaire à DynamicMethod, mais vous ne contrôlez pas l'IL généré. Cependant, c'est vraiment plus lisible.

        _method = typeof (T).GetMethod("InvokeMethod");
        var instanceParameter = Expression.Parameter(typeof (T), "instance");
        var call = Expression.Call(instanceParameter, _method);
        _delegate = Expression.Lambda<Func<T, int>>(call, instanceParameter).Compile();
        _func = (Func<T, int>) _delegate;

MethodInfo.Invoke

Le dernier mais non le moindre, l'appel de réflexion standard connu. Cependant, même s'il est facile de jouer avec, ne l'utilisez pas car c'est vraiment un mauvais artiste (regardez les résultats de référence). Préférez CreateDelegate qui est vraiment plus rapide.

        _method = typeof (T).GetMethod("InvokeMethod");
        return (int)_method.Invoke(instance, _emptyParameters);

Le code du test de référence peut être trouvé sur GitHub .

Benchmark des différentes méthodes pour obtenir un ordre de grandeur (pour 10 millions d'appels) (. NET Framework 4.5) =:

For Class standard call:
Elapsed: 00:00:00.0532945
Call/ms: 188679
For MethodInfo.CreateDelegate call:
Elapsed: 00:00:00.1131495
Call/ms: 88495
For Keyword dynamic call:
Elapsed: 00:00:00.3805229
Call/ms: 26315
For DynamicMethod.Emit call:
Elapsed: 00:00:00.1152792
Call/ms: 86956
For Linq Expression call:
Elapsed: 00:00:00.3158967
Call/ms: 31746
For Extension Method call:
Elapsed: 00:00:00.0637817
Call/ms: 158730
For Generic Method call:
Elapsed: 00:00:00.0772658
Call/ms: 129870
For Interface virtual call:
Elapsed: 00:00:00.0778103
Call/ms: 129870
For MethodInfo Invoke call:
Elapsed: 00:00:05.3104416
Call/ms: 1883
For Visitor Accept/Visit call:
Elapsed: 00:00:00.1384779
Call/ms: 72463
 == SUMMARY ==
Class standard call: 1
Extension Method call : 1,19
Generic Method call : 1,45
Interface virtual call : 1,45
MethodInfo.CreateDelegate call : 2,13
DynamicMethod.Emit call : 2,17
Visitor Accept/Visit call : 2,60
Linq Expression call : 5,94
Keyword dynamic call : 7,17
MethodInfo Invoke call : 100,19

MODIFIER:

Donc, comparé au modèle de visiteur, la répartition dynamique est à peu près 3 fois plus lente . Il peut être acceptable pour certaines applications car il peut supprimer du code encombrant. C'est toujours à vous de choisir.
Gardez simplement à l'esprit tous les inconvénients.


EDIT: (comme réponse à plusieurs envois avantage)

Utiliser un nom de modèle à la mode comme ' envoi multiple' et simplement déclarer qu'il est plus propre car il utilise moins de code, cela n'en fait pas un avantage supplémentaire à mon humble avis. Si vous voulez écrire du code à la mode ou ne vous souciez pas de la sécurité des types et de la stabilité de la production, il existe déjà beaucoup de langage offrant une saisie dynamique complète. Je vois dynamic introduction de mots clés en C # comme un moyen de combler l'écart entre la famille de langages typés forts et les autres langages moins fortement typés. Cela ne signifie pas que vous devez changer la façon dont vous développez et mettre les vérifications de type à la corbeille.

MISE À JOUR: 08/11/2016 (. NET Framework 4.6.1)

Les ordres de grandeur restent les mêmes (même si certains se sont un peu améliorés):

Class standard call: 1
Extension Method call : 1,19
Interface virtual call : 1,46
Generic Method call : 1,54
DynamicMethod.Emit call : 2,07
MethodInfo.CreateDelegate call : 2,13
Visitor Accept/Visit call : 2,64
Linq Expression call : 5,55
Keyword dynamic call : 6,70
MethodInfo Invoke call : 102,96
58
Fab

Je ne suis pas entièrement d'accord avec Fabien sur le fait qu'il ne vous offre pas d'avantages supplémentaires. Ce qu'il résout avec le modèle de visiteur s'appelle Envoi multiple et dynamique peut également fournir une solution claire à cela. Bien sûr, vous devez connaître les implications mentionnées par Fabien comme les performances, la vérification de type statique ...

public abstract class MyBaseClass
{
}

public class MyClass : MyBaseClass
{
}
public class MyAnotherClass : MyBaseClass
{
}

public class ClassThatIsUsingBaseClass
{
    public static void PrintName(MyBaseClass baseClass)
    {
        Console.WriteLine("MyBaseClass");
    }

    public static void PrintName(MyClass baseClass)
    {
        Console.WriteLine("MyClass");
    }

    public static void PrintName(MyAnotherClass baseClass)
    {
        Console.WriteLine("MyAnotherClass");
    }

    public static void PrintNameMultiDispatch(MyBaseClass baseClass)
    {
        ClassThatIsUsingBaseClass.PrintName((dynamic)baseClass);
    }
}

Et l'utilisation est

static void Main(string[] args)
{
    MyBaseClass myClass = new MyClass();
    MyBaseClass myAnotherClass = new MyAnotherClass();
    ClassThatIsUsingBaseClass.PrintName(myClass);
    ClassThatIsUsingBaseClass.PrintName(myAnotherClass);
    ClassThatIsUsingBaseClass.PrintNameMultiDispatch(myClass);
    ClassThatIsUsingBaseClass.PrintNameMultiDispatch(myAnotherClass);

    Console.ReadLine();
}

La sortie est

MyBaseClass
MyBaseClass
MyClass
MyAnotherClass

Recherchez "Multiple dispatch" et "C # multiple dispatch" pour plus d'informations.

5
Muraad Nofal