À quoi sert le type «dynamique» en C # 4.0?

236

C # 4.0 a introduit un nouveau type appelé «dynamique». Tout cela sonne bien, mais à quoi servirait un programmeur?

Y a-t-il une situation où cela peut sauver la situation?

Fahad
la source
4
doublon possible de stackoverflow.com/questions/2255982/…
Jörg W Mittag
Il est utile lorsque vous travaillez avec COM ou des langages typés dynamiquement. Par exemple, si vous utilisez lua ou python pour écrire votre langage, il est très pratique d'appeler simplement le code de script comme s'il s'agissait de code normal.
CodesInChaos
J'espère que cet article a une réponse complète à votre question visualstudiomagazine.com/Articles/2011/02/01/…
Développeur

Réponses:

196

Le mot-clé dynamique est nouveau dans C # 4.0 et est utilisé pour indiquer au compilateur que le type d'une variable peut changer ou qu'il n'est pas connu avant l'exécution. Considérez-le comme étant capable d'interagir avec un objet sans avoir à le lancer.

dynamic cust = GetCustomer();
cust.FirstName = "foo"; // works as expected
cust.Process(); // works as expected
cust.MissingMethod(); // No method found!

Notez que nous n'avons pas eu besoin de lancer ou de déclarer cust en tant que client de type. Parce que nous l'avons déclaré dynamique, le runtime prend le relais, puis recherche et définit la propriété FirstName pour nous. Maintenant, bien sûr, lorsque vous utilisez une variable dynamique, vous abandonnez la vérification du type de compilateur. Cela signifie que l'appel cust.MissingMethod () sera compilé et n'échouera pas jusqu'à l'exécution. Le résultat de cette opération est une RuntimeBinderException car MissingMethod n'est pas défini sur la classe Customer.

L'exemple ci-dessus montre comment la dynamique fonctionne lors de l'appel de méthodes et de propriétés. Une autre fonctionnalité puissante (et potentiellement dangereuse) est la possibilité de réutiliser des variables pour différents types de données. Je suis sûr que les programmeurs Python, Ruby et Perl peuvent penser à un million de façons de tirer parti de cela, mais j'utilise C # depuis si longtemps que cela me semble "mal".

dynamic foo = 123;
foo = "bar";

OK, donc vous n'écrirez probablement pas très souvent du code comme celui-ci. Il peut cependant arriver que la réutilisation de variables puisse être utile ou nettoyer un morceau de code hérité sale. Un cas simple que je rencontre souvent est de devoir constamment convertir entre décimal et double.

decimal foo = GetDecimalValue();
foo = foo / 2.5; // Does not compile
foo = Math.Sqrt(foo); // Does not compile
string bar = foo.ToString("c");

La deuxième ligne ne compile pas car 2.5 est tapé comme un double et la ligne 3 ne compile pas car Math.Sqrt attend un double. Évidemment, tout ce que vous avez à faire est de convertir et / ou de changer votre type de variable, mais il peut y avoir des situations où la dynamique a du sens à utiliser.

dynamic foo = GetDecimalValue(); // still returns a decimal
foo = foo / 2.5; // The runtime takes care of this for us
foo = Math.Sqrt(foo); // Again, the DLR works its magic
string bar = foo.ToString("c");

En savoir plus: http://www.codeproject.com/KB/cs/CSharp4Features.aspx

Pranay Rana
la source
97
Personnellement, je n'aime pas l'idée d'utiliser l' dynamicin en c # pour résoudre des problèmes qui peuvent être résolus (peut-être même mieux) par des fonctionnalités standard de c # et du typage statique, ou tout au plus avec l'inférence de type ( var). dynamicdoit seulement être utilisé en ce qui concerne les problèmes avec le DLR Interopérabilité. Si vous écrivez du code dans un langage typé statique, comme c # est, faites-le et n'émulez pas un langage dynamique. C'est juste moche.
Philip Daubmeier
40
Si vous faites un usage dynamicintensif des variables dans votre code où vous n'en avez pas besoin (comme dans votre exemple avec le squareroot), vous abandonnez la vérification des erreurs de temps de compilation; au lieu de cela, vous obtenez maintenant des erreurs d'exécution possibles.
Philip Daubmeier
33
Généralement bien, mais quelques erreurs mineures. Premièrement, il n'est pas correct de dire que dynamique signifie que le type de la variable peut changer. La variable en question est de type "dynamique" (du point de vue du langage C #; du point de vue du CLR la variable est de type objet). Le type d'une variable ne change jamais . Le type d'exécution de la valeur d'une variable peut être n'importe quel type compatible avec le type de la variable. (Ou dans le cas des types de référence, il peut être nul.)
Eric Lippert
15
Concernant votre deuxième point: C # avait déjà la fonction de "créer une variable dans laquelle vous pouvez mettre n'importe quoi" - vous pouvez toujours créer une variable de type objet. La chose intéressante à propos de la dynamique est ce que vous signalez dans votre premier paragraphe: la dynamique est presque identique à l'objet, sauf que l'analyse sémantique est différée jusqu'à l'exécution, et l'analyse sémantique est effectuée sur le type d'exécution de l'expression. (Surtout. Il y a quelques exceptions.)
Eric Lippert
18
J'ai dépensé un point de vote négatif à ce sujet, principalement parce qu'il préconise implicitement l'utilisation du mot-clé pour un usage général. Il a un objectif spécifiquement ciblé (décrit parfaitement dans la réponse de Lasses) et bien que cette réponse soit -techniquement- correcte, elle est susceptible d'induire les développeurs en erreur.
Eight-Bit Guru
211

le dynamic mot-clé a été ajouté, ainsi que de nombreuses autres nouvelles fonctionnalités de C # 4.0, pour simplifier la conversation avec du code qui vit ou provient d'autres runtimes et qui possède différentes API.

Prenons un exemple.

Si vous avez un objet COM, comme le Word.Application objet, et que vous souhaitez ouvrir un document, la méthode pour le faire est fournie avec pas moins de 15 paramètres, dont la plupart sont facultatifs.

Pour appeler cette méthode, vous auriez besoin de quelque chose comme ça (je simplifie, ce n'est pas du code réel):

object missing = System.Reflection.Missing.Value;
object fileName = "C:\\test.docx";
object readOnly = true;
wordApplication.Documents.Open(ref fileName, ref missing, ref readOnly,
    ref missing, ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing);

Notez tous ces arguments? Vous devez passer ceux-ci car C # avant la version 4.0 n'avait pas de notion d'arguments optionnels. Dans C # 4.0, les API COM ont été rendues plus faciles à utiliser en introduisant:

  1. Arguments facultatifs
  2. Rendre reffacultatif pour les API COM
  3. Arguments nommés

La nouvelle syntaxe de l'appel ci-dessus serait:

wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);

Voyez à quel point il semble plus facile, à quel point il devient plus lisible?

Brisons cela:

                                    named argument, can skip the rest
                                                   |
                                                   v
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
                                 ^                         ^
                                 |                         |
                               notice no ref keyword, can pass
                               actual parameter values instead

La magie est que le compilateur C # va maintenant injecter le code nécessaire, et travailler avec de nouvelles classes dans le runtime, pour faire presque exactement la même chose que vous faisiez auparavant, mais la syntaxe vous a été cachée, maintenant vous pouvez vous concentrer sur le quoi , et pas tant sur le comment . Anders Hejlsberg aime dire que vous devez invoquer différentes "incantations", ce qui est une sorte de jeu de mots sur la magie du tout, où vous devez généralement agiter vos mains et dire des mots magiques dans le bon ordre. pour lancer un certain type de sort. L'ancienne façon API de parler aux objets COM était beaucoup de cela, vous aviez besoin de sauter à travers beaucoup de cerceaux afin d'amadouer le compilateur pour compiler le code pour vous.

Les choses se dégradent encore plus en C # avant la version 4.0 si vous essayez de parler à un objet COM pour lequel vous n'avez pas d'interface ou de classe, tout ce que vous avez est une IDispatchréférence.

Si vous ne savez pas ce que c'est, IDispatchc'est essentiellement une réflexion pour les objets COM. Avec une IDispatchinterface, vous pouvez demander à l'objet "quel est le numéro d'identification de la méthode connue sous le nom de Save", et créer des tableaux d'un certain type contenant les valeurs des arguments, et enfin appeler une Invokeméthode sur l' IDispatchinterface pour appeler la méthode, en passant tous les informations que vous avez réussi à graver ensemble.

La méthode Save ci-dessus pourrait ressembler à ceci (ce n'est certainement pas le bon code):

string[] methodNames = new[] { "Open" };
Guid IID = ...
int methodId = wordApplication.GetIDsOfNames(IID, methodNames, methodNames.Length, lcid, dispid);
SafeArray args = new SafeArray(new[] { fileName, missing, missing, .... });
wordApplication.Invoke(methodId, ... args, ...);

Tout cela pour simplement ouvrir un document.

VB avait des arguments et un support optionnels pour la plupart de ces éléments il y a longtemps, donc ce code C #:

wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);

est fondamentalement juste C # rattraper VB en termes d'expressivité, mais le faire de la bonne façon, en le rendant extensible, et pas seulement pour COM. Bien sûr, cela est également disponible pour VB.NET ou tout autre langage intégré au runtime .NET.

Vous pouvez trouver plus d'informations sur l' IDispatchinterface sur Wikipedia: IDispatch si vous voulez en savoir plus. C'est vraiment des trucs sanglants.

Cependant, que se passe-t-il si vous souhaitez parler à un objet Python? Il existe une API différente de celle utilisée pour les objets COM, et comme les objets Python sont également de nature dynamique, vous devez recourir à la magie de la réflexion pour trouver les bonnes méthodes à appeler, leurs paramètres, etc., mais pas le .NET réflexion, quelque chose écrit pour Python, à peu près comme le code IDispatch ci-dessus, tout à fait différent.

Et pour Ruby? Une API différente encore.

JavaScript? Même accord, API différente pour cela également.

Le mot-clé dynamique se compose de deux choses:

  1. Le nouveau mot-clé en C #, dynamic
  2. Un ensemble de classes d'exécution qui sait comment gérer les différents types d'objets, qui implémentent une API spécifique dont le dynamicmot clé a besoin et mappe les appels à la bonne façon de faire les choses. L'API est même documentée, donc si vous avez des objets provenant d'un runtime non couvert, vous pouvez l'ajouter.

Le dynamicmot clé n'est cependant pas destiné à remplacer tout code existant uniquement en .NET. Bien sûr, vous pouvez le faire, mais il n'a pas été ajouté pour cette raison, et les auteurs du langage de programmation C # avec Anders Hejlsberg à l'avant, ont été catégoriques: ils considèrent toujours le C # comme un langage fortement typé et ne sacrifieront pas ce principe.

Cela signifie que bien que vous puissiez écrire du code comme ceci:

dynamic x = 10;
dynamic y = 3.14;
dynamic z = "test";
dynamic k = true;
dynamic l = x + y * z - k;

et de le faire compiler, il ne s'agissait pas d'une sorte de système de type magic-lets-figure-out-what-you-mean-at-runtime.

L'objectif était de faciliter la conversation avec d'autres types d'objets.

Il y a beaucoup de matériel sur Internet sur le mot-clé, les partisans, les opposants, les discussions, les diatribes, les éloges, etc.

Je vous suggère de commencer avec les liens suivants, puis de google pour en savoir plus:

Lasse V. Karlsen
la source
12
Il est également utile en dehors de COM pour les API JSON Web où la structure des objets JSON désérialisés n'est pas spécifiée en C #. Par exemple, la méthode Decode de System.Web.Helpers.Json renvoie un objet dynamique .
dumbledad
Un petit mot sur "ils considèrent toujours le C # comme un langage fortement typé": Eric Lippert n'est pas fan de "fortement typé" comme description.
Andrew Keeton
Je ne suis pas d'accord avec lui, mais c'est une question d'opinion, pas une question de fait. "Fortement typé" signifie pour moi que le compilateur sait, au moment de la compilation, quel type est utilisé, et applique ainsi les règles définies autour de ces types. Le fait que vous puissiez opter pour un type dynamique qui reporte la vérification des règles et la liaison à l'exécution ne signifie pas pour moi que la langue est faiblement typée. Je ne compare généralement pas fortement typé à faiblement typé, cependant, je le compare généralement à typé dynamiquement, comme des langages comme Python, où tout est un canard jusqu'à ce qu'il aboie.
Lasse V. Karlsen
Quel est l'intérêt de cette réponse? La moitié concerne les paramètres optionnels et l'interface IDispatch.
Xam
C'est pourquoi a dynamicété ajouté, pour soutenir d'autres écosystèmes sur la façon dont l'invocation de méthodes de réflexion peut être effectuée, ainsi que pour fournir une sorte d'approche de boîte noire aux structures de données avec un moyen documenté d'y parvenir.
Lasse V. Karlsen
29

Je suis surpris que personne n'ait mentionné l' envoi multiple . La façon habituelle de contourner cela est via le modèle de visiteur et ce n'est pas toujours possible, vous vous retrouvez donc avec des ischèques empilés .

Voici donc un exemple concret de ma propre application. Au lieu de faire:

public static MapDtoBase CreateDto(ChartItem item)
{
    if (item is ElevationPoint) return CreateDtoImpl((ElevationPoint)item);
    if (item is MapPoint) return CreateDtoImpl((MapPoint)item);
    if (item is MapPolyline) return CreateDtoImpl((MapPolyline)item);
    //other subtypes follow
    throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}

Tu fais:

public static MapDtoBase CreateDto(ChartItem item)
{
    return CreateDtoImpl(item as dynamic);
}

private static MapDtoBase CreateDtoImpl(ChartItem item)
{
    throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}

private static MapDtoBase CreateDtoImpl(MapPoint item)
{
    return new MapPointDto(item);
}

private static MapDtoBase CreateDtoImpl(ElevationPoint item)
{
    return new ElevationDto(item);
}

Notez que dans le premier cas ElevationPointest une sous-classe de MapPointet s'il n'est pas placé avant, MapPoint il ne sera jamais atteint. Ce n'est pas le cas avec dynamique, car la méthode de correspondance la plus proche sera appelée.

Comme vous pouvez le deviner à partir du code, cette fonctionnalité était utile pendant que j'effectuais la traduction des objets ChartItem vers leurs versions sérialisables. Je ne voulais pas polluer mon code avec les visiteurs et je ne voulais pas non plus polluer mes ChartItemobjets avec des attributs spécifiques de sérialisation inutiles.

Stelios Adamantidis
la source
Je ne connaissais pas ce cas d'utilisation. Un peu hacky au mieux, cependant. Cela détruira tout analyseur statique.
Kugel
2
@Kugel c'est vrai, mais je n'appellerais pas ça un hack . L'analyse statique est bonne, mais je ne laisserai pas cela m'empêcher d'une solution élégante, où les alternatives sont: violation du principe ouvert-fermé (modèle de visiteur) ou complexité cyclomatique accrue avec un redoutable isempilé les uns sur les autres.
Stelios Adamantidis
4
Eh bien, vous avez la possibilité de faire correspondre les motifs avec C # 7, non?
Kugel
2
Eh bien, les opérateurs sont beaucoup moins chers de cette façon (en évitant la double conversion) et vous obtenez une analyse statique ;-) et des performances.
Kugel
@idbrii s'il vous plaît ne changez pas mes réponses. N'hésitez pas à laisser un commentaire et je clarifierai (si nécessaire) car je suis toujours actif dans cette communauté. Aussi, veuillez ne pas utiliser magic; la magie n'existe pas.
Stelios Adamantidis
11

Il facilite l'interopérabilité des langages typés statiques (CLR) avec les langages dynamiques (python, ruby ​​...) fonctionnant sur le DLR (runtime de langage dynamique), voir MSDN :

Par exemple, vous pouvez utiliser le code suivant pour incrémenter un compteur en XML en C #.

Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1);

En utilisant le DLR, vous pouvez utiliser le code suivant à la place pour la même opération.

scriptobj.Count += 1;

MSDN répertorie ces avantages:

  • Simplifie le portage de langages dynamiques vers .NET Framework
  • Active les fonctionnalités dynamiques dans les langages typés statiquement
  • Fournit les avantages futurs du DLR et du .NET Framework
  • Permet le partage de bibliothèques et d'objets
  • Fournit une répartition et une invocation dynamiques rapides

Voir MSDN pour plus de détails.

Philip Daubmeier
la source
1
Et le changement de VM requis pour dynamique rend en fait les langues dynamiques plus faciles.
Dykam
2
@Dykam: Il n'y a aucun changement sur la VM. Le DLR fonctionne très bien jusqu'à .NET 2.0.
Jörg W Mittag
@ Jörg, oui il y a un changement. Le DLR est en partie réécrit parce que la machine virtuelle prend désormais en charge la résolution dynamique.
Dykam
J'étais un peu trop optimiste, les recherches ont montré que les changements n'étaient pas si importants.
Dykam
4

Un exemple d'utilisation:

Vous consommez de nombreuses classes qui ont une propriété commune 'CreationDate':

public class Contact
{
    // some properties

    public DateTime CreationDate { get; set; }        
}

public class Company
{
    // some properties

    public DateTime CreationDate { get; set; }

}

public class Opportunity
{
    // some properties

    public DateTime CreationDate { get; set; }

}

Si vous écrivez une méthode commun qui récupère la valeur de la propriété 'CreationDate', vous devrez utiliser la réflexion:

    static DateTime RetrieveValueOfCreationDate(Object item)
    {
        return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
    }

Avec le concept «dynamique», votre code est beaucoup plus élégant:

    static DateTime RetrieveValueOfCreationDate(dynamic item)
    {
        return item.CreationDate;
    }
Akli
la source
7
Duck tapant, sympa. Cependant, vous devez utiliser une interface pour cela si ce sont vos types.
Kugel
3

Interopérabilité COM. Surtout IUnknown. Il a été spécialement conçu pour cela.

Aen Sidhe
la source
2

Il sera principalement utilisé par les victimes RAD et Python pour détruire la qualité du code, IntelliSense et compiler la détection des bogues de temps.

stormianrootsolver
la source
Une réponse cynique mais facilement trop vraie. Je l'ai vu faire simplement pour éviter de déclarer des structures avec pour résultat que le code fonctionne si tout va bien, mais souffle sa pile de manière imprévisible dès que vous déplacez son fromage.
AnthonyVO
Oui, vous verrez cette coupe de coin classique avec de nombreuses autres fonctionnalités linguistiques. Il n'est pas surprenant que vous le voyiez également ici.
Hawkeye4040
1

Il évalue au moment de l'exécution, vous pouvez donc changer le type comme vous le pouvez en JavaScript vers ce que vous voulez. Cela est légitime:

dynamic i = 12;
i = "text";

Et vous pouvez donc changer le type selon vos besoins. Utilisez-le en dernier recours; c'est bénéfique, mais j'ai entendu beaucoup de choses se passer dans les coulisses en termes d'IL généré et cela peut avoir un prix de performance.

Brian Mains
la source
7
J'hésiterais à dire que c'est "légitime". Il compilera sûrement, donc en tant que tel, c'est du "code légitime" dans le sens où le compilateur le compilera maintenant, et le runtime l'exécutera. Mais je ne voudrais jamais voir ce morceau de code particulier (ou quelque chose qui lui ressemble) dans l'un des codes que je maintiens, ou ce serait une infraction de tir imminent.
Lasse V. Karlsen du
6
Bien sûr, mais cela aurait été "légitime" avec "objet" au lieu de "dynamique". Vous n'avez rien montré d'intéressant sur la dynamique ici.
Eric Lippert
Pour l'objet, vous devez le convertir dans le type approprié, afin d'invoquer réellement l'une de ses méthodes ... vous perdez la signature; vous pouvez demander à votre code d'appeler n'importe quelle méthode sans erreur de compilation, et il génère des erreurs lors de l'exécution. J'étais pressé de taper, désolé de ne pas l'avoir précisé. Et @Lasse, je serais d'accord et je n'utiliserai probablement pas beaucoup la dynamique.
Brian Mains
1
Le cas d'utilisation de dernier recours n'est pas expliqué
denfromufa
1

Le meilleur cas d'utilisation de variables de type «dynamique» pour moi était lorsque, récemment, j'écrivais une couche d'accès aux données dans ADO.NET (à l' aide de SQLDataReader ) et que le code invoquait les procédures stockées héritées déjà écrites. Il existe des centaines de ces procédures stockées héritées contenant la majeure partie de la logique métier. Ma couche d'accès aux données devait retourner une sorte de données structurées à la couche logique métier, basée sur C #, pour effectuer certaines manipulations ( bien qu'il n'y en ait presque pas ). Chaque procédure stockée renvoie un ensemble de données différent ( colonnes de table ). Donc, au lieu de créer des dizaines de classes ou de structures pour contenir les données renvoyées et les transmettre au BLL, j'ai écrit le code ci-dessous qui semble assez élégant et soigné.

public static dynamic GetSomeData(ParameterDTO dto)
        {
            dynamic result = null;
            string SPName = "a_legacy_stored_procedure";
            using (SqlConnection connection = new SqlConnection(DataConnection.ConnectionString))
            {
                SqlCommand command = new SqlCommand(SPName, connection);
                command.CommandType = System.Data.CommandType.StoredProcedure;                
                command.Parameters.Add(new SqlParameter("@empid", dto.EmpID));
                command.Parameters.Add(new SqlParameter("@deptid", dto.DeptID));
                connection.Open();
                using (SqlDataReader reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        dynamic row = new ExpandoObject();
                        row.EmpName = reader["EmpFullName"].ToString();
                        row.DeptName = reader["DeptName"].ToString();
                        row.AnotherColumn = reader["AnotherColumn"].ToString();                        
                        result = row;
                    }
                }
            }
            return result;
        }
user1451111
la source
0
  1. Vous pouvez appeler des langages dynamiques tels que CPython en utilisant pythonnet:

dynamic np = Py.Import("numpy")

  1. Vous pouvez convertir des génériques en leur dynamicappliquant des opérateurs numériques. Cela offre une sécurité de type et évite les limitations des génériques. Il s'agit essentiellement de la saisie de canard:

T y = x * (dynamic)x, où typeof(x) is T

denfromufa
la source
0

Un autre cas d'utilisation pour la dynamicsaisie concerne les méthodes virtuelles qui rencontrent un problème de covariance ou de contravariance. Un tel exemple est la fameuse Cloneméthode qui renvoie un objet du même type que l'objet auquel il est appelé. Ce problème n'est pas complètement résolu avec un retour dynamique car il contourne la vérification de type statique, mais au moins vous n'avez pas besoin d'utiliser des conversions laides tout le temps comme lors de l'utilisation de plain object. Autrement dit, les moulages deviennent implicites.

public class A
{
    // attributes and constructor here
    public virtual dynamic Clone()
    {
        var clone = new A();
        // Do more cloning stuff here
        return clone;
    }
}

public class B : A
{
    // more attributes and constructor here
    public override dynamic Clone()
    {
        var clone = new B();    
        // Do more cloning stuff here
        return clone;
    }
}    

public class Program
{
    public static void Main()
    {
        A a = new A().Clone();  // No cast needed here
        B b = new B().Clone();  // and here
        // do more stuff with a and b
    }
}
Frédéric
la source