A quoi sert le type “dynamic” dans C # 4.0?

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

Y a-t-il une situation où il peut sauver la journée?

Le mot-clé dynamic est nouveau dans C # 4.0 et permet d’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 pouvant interagir avec un object 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 ni de déclarer cust comme type Customer. Comme nous l’avons déclaré dynamic, le runtime prend le relais, puis recherche et définit la propriété FirstName pour nous. Bien sûr, lorsque vous utilisez une variable dynamic, vous abandonnez la vérification du type de compilateur. Cela signifie que l’appel cust.MissingMethod () comstackra et n’échouera pas avant l’exécution. Le résultat de cette opération est une exception RuntimeBinderException car MissingMethod n’est pas défini sur la classe Customer.

L’exemple ci-dessus montre le fonctionnement dynamic lors de l’appel de méthodes et de propriétés. Une autre fonctionnalité puissante (et potentiellement dangereuse) consiste à pouvoir 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 d’en tirer parti, mais j’utilise C # depuis si longtemps que cela me semble tout simplement faux.

 dynamic foo = 123; foo = "bar"; 

OK, vous n’allez probablement pas écrire de code comme celui-ci très souvent. Cependant, il peut arriver que la réutilisation de variables soit utile ou que vous nettoyiez un élément du code hérité. Un cas simple que je rencontre souvent est de devoir constamment choisir entre décimal et double.

 decimal foo = GetDecimalValue(); foo = foo / 2.5; // Does not comstack foo = Math.Sqrt(foo); // Does not comstack ssortingng bar = foo.ToSsortingng("c"); 

La deuxième ligne ne comstack pas car 2.5 est tapé comme un double et la ligne 3 ne comstack pas car Math.Sqrt attend un double. Evidemment, tout ce que vous avez à faire est de lancer et / ou de changer votre type de variable, mais il peut y avoir des situations où la dynamic 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 ssortingng bar = foo.ToSsortingng("c"); 

Lire la suite: http://www.codeproject.com/KB/cs/CSharp4Features.aspx

Le mot-clé dynamic été ajouté, ainsi que de nombreuses autres nouvelles fonctionnalités de C # 4.0, pour simplifier la communication avec le code qui vit ou provient d’autres environnements d’exécution, avec des API différentes.

Prenons un exemple.

Si vous avez un object COM, tel que l’object Word.Application , et que vous souhaitez ouvrir un document, la méthode à suivre comporte au moins 15 parameters, 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 les transmettre car C # avant la version 4.0 n’avait pas de notion d’arguments optionnels. En C # 4.0, l’utilisation des API COM a été facilitée par l’introduction de:

  1. Arguments optionnels
  2. Rendre ref optionnel pour les API COM
  3. Arguments nommés

La nouvelle syntaxe pour l’appel ci-dessus serait:

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

Voyez à quel point cela a l’air plus facile, à quel point cela devient plus lisible?

Brisons cela à part:

  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, mais la syntaxe vous a été cachée, maintenant vous pouvez vous concentrer sur le quoi , et pas tellement sur le comment . Anders Hejlsberg aime dire que vous devez invoquer différentes “incantations”, une sorte de jeu de mots sur la magie du tout, où vous devez en général agiter votre main et dire des mots magiques dans le bon ordre. pour obtenir un certain type de sort en cours. L’ancienne façon de parler de l’API avec les objects COM était beaucoup de cela, vous deviez sauter à travers de nombreux obstacles afin d’amener le compilateur à comstackr le code pour vous.

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

Si vous ne savez pas ce que c’est, IDispatch est essentiellement une reflection pour les objects COM. Avec une interface IDispatch , vous pouvez demander à l’object “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, puis appeler une méthode Invoke sur l’interface IDispatch pour appeler la méthode , en passant toutes les informations que vous avez réussi à rassembler.

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

 ssortingng[] 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 ouvrir un document.

VB avait des arguments optionnels et la prise en charge de la plupart de ces éléments depuis 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 dans le bon sens, 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é à l’exécution .NET.

Vous pouvez trouver plus d’informations sur l’interface IDispatch sur Wikipedia: IDispatch si vous souhaitez en savoir plus à ce sujet. C’est vraiment gore.

Cependant, si vous vouliez parler à un object Python? Il existe une API différente de celle utilisée pour les objects COM, et comme les objects Python sont également de nature dynamic, vous devez avoir recours à la magie de la reflection pour trouver les bonnes méthodes à appeler, leurs parameters, etc. Réflexion, quelque chose d’écrit pour Python, un peu comme le code IDispatch ci-dessus, tout à fait différent.

Et pour Ruby? Une API différente

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

Le mot clé dynamic comprend deux choses:

  1. Le nouveau mot clé en C #, dynamic
  2. Un ensemble de classes d’exécution qui savent comment gérer les différents types d’objects, qui implémentent une API spécifique requirejse par le mot clé dynamic , et mappent les appels sur la bonne façon de procéder. L’API est même documentée, donc si vous avez des objects provenant d’un environnement d’exécution non couvert, vous pouvez l’append.

Le mot clé dynamic n’est toutefois pas destiné à remplacer un code existant uniquement .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 au premier plan ont été très catégoriques: ils considèrent toujours C # comme un langage fortement typé 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 le faire comstackr, il n’était pas conçu comme une sorte de système de type magie-permet-de-comprendre-ce que vous voulez dire à l’exécution.

L’objective était de faciliter la conversation avec d’autres types d’objects.

Il y a beaucoup de matériel sur Internet à propos du mot-clé, des promoteurs, des opposants, des discussions, des propos, des éloges, etc.

Je vous suggère de commencer par les liens suivants, puis sur Google pour en savoir plus:

  • DevDays 2010: Anders Hejlsberg – C # 4.0 et au-delà
  • Channel 9: Mads Torgersen – Inside C # 4.0: typage dynamic + +
  • DevX: COM Interop obtient beaucoup mieux en C # 4.0
  • Scott Hanselman – C # 4 et le mot-clé dynamic – Whirlwind Tour autour de .NET 4 (et Visual Studio 2010) Bêta 1

Je suis surpris que personne n’ait mentionné les envois multiples . La manière habituelle de contourner ce problème consiste à utiliser le modèle de visiteur (double envoi), ce qui n’est pas toujours possible, vous vous retrouvez donc avec des contrôles empilés.

Voici donc un exemple concret d’une application personnelle. 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()); } 

Vous utilisez la dynamic pour appeler votre fonction CreateDtoImpl avec le type d’exécution (dynamic) de l’object:

 public static MapDtoBase CreateDto(ChartItem item) { return CreateDtoImpl(item as dynamic); // magic here } 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, ElevationPoint est une sous-classe de MapPoint et si elle n’est pas placée avant MapPoint elle ne sera jamais atteinte. Ce n’est pas le cas avec dynamic, car la méthode de correspondance la plus proche sera appelée.

Comme vous pouvez le deviner à partir du code, cette fonctionnalité était pratique lorsque je traduisais des objects ChartItem vers leurs versions sérialisables. Je ne voulais pas polluer mon code avec les visiteurs et je ne voulais pas aussi polluer mes objects ChartItem avec des atsortingbuts spécifiques à la sérialisation inutiles.

Cela facilite l’interopérabilité des langages typés statiques (CLR) avec les langages dynamics (python, ruby ​​…) s’exécutant sur le DLR (Dynamic Language Runtime), 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 pour la même opération.

 scriptobj.Count += 1; 

MSDN répertorie ces avantages:

  • Simplifie le portage de langages dynamics sur le .NET Framework
  • Active les fonctionnalités dynamics dans les langages typés statiquement
  • Fournit les avantages futurs du DLR et du .NET Framework
  • Permet le partage de bibliothèques et d’objects
  • Fournit un envoi et une invocation dynamics rapides

Voir MSDN pour plus de détails.

Un exemple d’utilisation:

Vous consumz beaucoup de classes qui ont une propriété commun ‘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 devez utiliser la reflection:

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

Avec le concept “dynamic”, votre code est beaucoup plus élégant:

  static DateTime ResortingeveValueOfCreationDate(dynamic item) { return item.CreationDate; } 

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

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

Le meilleur cas d’utilisation des variables de type ‘dynamics’ pour moi était lorsque, récemment, j’écrivais une couche d’access aux données dans ADO.NET (en utilisant SQLDataReader ) et que le code appelait les procédures stockées héritées déjà écrites. Il existe des centaines de procédures stockées héritées contenant l’essentiel de la logique métier. Ma couche d’access aux données devait renvoyer une sorte de données structurées à la couche de 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 dataset 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 très élégant et net.

 public static dynamic GetSomeData(ParameterDTO dto) { dynamic result = null; ssortingng SPName = "a_legacy_stored_procedure"; using (SqlConnection connection = new SqlConnection(DataConnection.ConnectionSsortingng)) { 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"].ToSsortingng(); row.DeptName = reader["DeptName"].ToSsortingng(); row.AnotherColumn = reader["AnotherColumn"].ToSsortingng(); result = row; } } } return result; } 
  1. Vous pouvez appeler dans des langages dynamics tels que CPython en utilisant pythonnet:

dynamic np = Py.Import("numpy")

  1. Vous pouvez convertir les génériques en dynamic lorsque vous leur appliquez des opérateurs numériques. Cela fournit une sécurité de type et évite les limitations des génériques. Ceci est essentiellement * typage de canard:

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

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

 dynamic i = 12; i = "text"; 

Et ainsi vous pouvez changer le type selon vos besoins. Utilisez-le en dernier recours; c’est bénéfique, mais j’ai entendu beaucoup de choses en termes de IL généré et cela peut arriver à un prix de performance.