Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

Quoi de neuf dans C# 5 ?

[new:30/05/2012]Contre vents et marées, ce fantastique langage qu’est C# continue son éternelle mutation, comme un papillon qui n’en finirait pas de renaitre de son cocon, toujours plus beau à chaque fois. Dernièrement j’ai beaucoup parlé de WinRT et Windows 8, et j’en reparlerai tout l’été pour préparer la rentrée ! Mais lorsque tout cela sera enfin sur le marché la version 5 de C# le sera aussi et il serait bien dommage de l’oublier. Quelles nouvelles parures arbore notre papillon dans cette mouture ?

C#5 Une évolution logique

Selon comment vous regarderez C#5 vous le trouverez révolutionnaire ou bien simplement dans la suite des améliorations déjà immenses des versions précédentes.

C# 5 est “tout simplement” une suite logique en adéquation avec les besoins des développeurs.

D’un côté peu de nouveautés aussi faramineuses qu’à pu l’être Linq par exemple, et de l’autre des avancées absolument nécessaires pour être en phase avec les exigences des applications modernes.

Des petites choses bien utiles...

 

Informations de l’appelant

Parmi ces petites choses bien utiles on trouve les informations de la méthode appelante. C’est simple, c’est pas le truc qui scotche, mais cela permet par exemple d’écrire en quelques lignes son propre logger sans être dépendant d’une grosse librairie externe comme Log4Net ou d’autres.

On connaissait déjà les paramètres optionnels introduits par C# 4 et qui permettent d’écrire un code de ce type :

   1: public void MaMethode(int a = 123, string b = "Coucou") { ... }
   2: MaMethode(753);  // compilé en MaMethode(753, "Coucou")
   3: MaMethode();     // compilé en MaMethode(123, "Coucou")

Sous C# 4 la valeur des paramètres était forcément une constante. C# 5 introduit la possibilité d’utiliser un attribut qui ira chercher la valeur au runtime parmi les informations de la méthode appelante (appelante et non appelée ce qui fait toute la différence ici).

De fait, il devient possible d’écrire un code comme celui-ci :

La méthode de log

   1: public static void Trace(string message, 
   2:                        [CallerFilePath] string sourceFile = "", 
   3:                        [CallerMemberName] string memberName = "") 
   4: {
   5:     var msg = String.Format("{0}: {1}.{2}: {3}",
   6:                              DateTime.Now.ToString("yyyy-mm-dd HH:MM:ss.fff"), 
   7:                              Path.GetFileNameWithoutExtension(sourceFile),
   8:                              memberName, message);
   9:   MyLogger.Log(msg);
  10: }

Ici rien de très spécial, sauf la présence des attributs dans la déclaration des paramètres optionnels. Pour faire court le code suppose une infrastructure hypothétique “MyLogger” qui elle stocke le message (ou l’envoie sur le web ou ce que vous voulez).

Grâce à cette astuce il est très facile de logger des messages dans son code en utilisant son propre code “Trace” :

   1: // Fichier CheckUser.cs
   2: public void CheckUserAccount(string userName) 
   3: {
   4:   // compilé en Trace("Entrée dans la méthode", "CheckUser.cs", "CheckUserAccount")  
   5:   Trace("Entrée dans la méthode");
   6:   // ...
   7:   Trace("Sortie de la méthode");
   8: }

 

A première vue ce n’est pas révolutionnaire en effet. Pratique en revanche. A seconde vue ce n’est toujours pas révolutionnaire mais ça peut s’utiliser de façon plus pratique...

Prenons le cas de l’interface INotifyPropertyChanged qui demande à passer le nom de la propriété dans l’évènement. Il existe tout un tas de “ruses” dans certaines librairies pour soit contrôler le nom passé, soit tenter comme Jounce d’éviter de le taper. Toutes ces tentatives sont essentielles car une simple erreur d’orthographe ou tout bêtement un refactoring du nom d’une propriété peut casser toute la belle logique d’un databinding...

En y réfléchissant bien, les nouveaux attributs de paramètres optionnels peuvent être utilisés pour régler définitivement ce problème récurrent, de façon efficace, simple et uniquement en utilisant le langage et ses possibilités :

   1: public class ViewModelBase : INotifyPropertyChanged {
   2:   protected void Set<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
   3:  {
   4:     if (!Object.Equals(field, value))
   5:     {
   6:       field = value;
   7:       OnPropertyChanged(propertyName);
   8:     }
   9:   }
  10:   // ...
  11: }
  12:  
  13: public class Ecran1WM : ViewModelBase
  14: {
  15:   private int largeur;
  16:   public int Largeur
  17:   {
  18:     get { return largeur; }
  19:     set { Set(ref largeur, value); }  // Le compilateur remplira le nom de propriété avec "Largeur" 
  20:   }
  21: }

Pas si simpliste que ça donc cet ajout de C#5 !

Variables de boucle et expression Lambda

Ici aussi il ne s’agit pas forcément d’une révolution. Quoi que...

Vous le savez peut-être (je dis bien peut-être car le sujet est loin d’être compris par tout le monde, même des développeurs confirmés) il ne faut pas utiliser les variables de boucle dans des expressions Lambda par exemple.

En effet, le fameux problème de “closure” fait que la variable encapsulée est celle de la boucle et qu’en général cela ne correspond absolument pas à l’effet escompté.

Je ne referai un pas speech sur les closures puisque la bonne nouvelle c’est qu’en réalité C# 5 fonctionne comme on s’y attendait sans plus avoir à se poser de question bizarre...

Un petit exemple pour ceux qui ont du mal a situer le problème. Le code suivant ne fait pas ce qu’on attend de lui :

   1: var nombres = GetNombres(1, 2, 3, 4, 5);
   2: foreach (var n in nombres)
   3:  {
   4:   Console.WriteLine(n(10));
   5:  }
   6:  
   7: // Sortie réelle : 15 15 15 15 15
   8:  
   9:  public static List<Func<int, int>> GetNombres(params int[] addends)
  10:  {
  11:   var funcs = new List<Func<int, int>>();
  12:   foreach (int addend in addends)funcs.Add(i => i + addend);
  13:   return funcs;
  14:  }

Bref c’est pas très clair les closures pour plein de gens.

Au lieu d’avoir a créer une variable locale qui elle peut être capturée par la closure et éviter la catastrophe du code ci-dessus, C# 5 comprend la situation et fournira cette fois-ci le résultat attendu...

Cela supprimera des bugs bien sournois pas encore découverts et qui, au gré d’une recompilation en C# 5 disparaitront tous seuls sans que personne ne sache qu’ils ont pourtant été là !

 

... Aux grandes choses très utiles !

 

La programmation Asynchrone, l’épouvantail à développeur...

Ah, la programmation asynchrone... Il suffit d’en parler pour que le silence se fasse autour de la machine à café et que chacun trouve une excuse pour s’éclipser ! Il est vrai que le sujet à de quoi imposer le silence : soit on est un expert, soit il est préférable de ne rien dire de peur de dire une bêtise. D’ailleurs asynchrone c’est du multitâche ou ce n’en est pas ? Clac-clac font les dents dans le silence des vapeurs de café (ou les volutes de cigarettes en se caillant sur le trottoir, mais là c’est le froid plus que la peur qui fait jouer des castagnettes aux quenottes !).

.NET et C# proposent des tas de moyens de faire de la programmation asynchrone et du multitâche, mais ces méthodes ne passionnaient guère de monde jusqu’à ce que les progrès du hardware ne passent plus par les GHz mais par le nombre de cœurs du CPU et jusqu’à ce que les services Web (et me Cloud) se démocratisent Et là, panique ! Trop peu de compétence pour un sujet si délicat.
Tout le monde a eu, à un moment ou un autre, “peur” du multitâche et de l’asynchrone.

Mutex, Lock, Thread, ThreadPool, ThreadStart, WaitCallBack, Monitor.Enter, Monitor.Exit, TryEnter, Pulse et Wait, BeginGetResponse, EndGetResponse, objets immutables et j’en passe !!!

De quoi avoir le tournis je l’avoue.

Asynchrone ? Multitâche ?

Les deux choses sont très différentes et sont souvent confondues à tort.

Bref c’est un peu le bouillon. C# 5 s’intéresse à l’asynchrone, le multitâche avait plutôt été traité dans C# 4.

Multitâche

Le multitâche consiste à faire tourner plusieurs tâches en même temps. Ni plus ni moins. Il peut être utilisé en conjonction de la programmation asynchrone ou non, il n’y a pas de lien direct entre les deux techniques.

C# 5 ne propose rien de particulier concernant le multitâche proprement dit puisque cela a plutôt été l’une des avancées de C# 4 avec PLINQ et la classe Parallel. Alors passons à la suite...

Asynchrone

L’asynchrone est d’une autre nature. Il s’agit de faire exécuter une tâche (généralement sur une autre machine ou un périphérique) sans être sûr de quand arrivera la réponse (s’il y en a une) le tout sans bloquer le logiciel et son UI (ce sont ces considérations optionnelles qui peuvent amener à utiliser des techniques issu de la programmation multitâche, sans rapport direct avec l’asynchronisme ou faire penser que l’asynchronisme est du multitâche, vous suivez toujours ? !).

Le cas le plus fréquent aujourd’hui est la gestion des données. Qu’il s’agisse de véritables services Web ou d’équivalences techniques, les données sont de moins en moins accédées de façon directe.

L’écriture synchrone est facile. Le programme s’écrit au fil des lignes schématisant le temps qui coule de haut en bas dans le sens de lecture du code. Il est aisé d’entreprendre des actions, d’attendre leur réponse, de tester des valeurs, de passer à la suite. C’est la programmation “d’avant”.

Avec un service Web, une requête SQL, Entity framework, etc, le temps n’est plus linéaire au sein du programme puisqu’en réalité d’autres machines (d’autres cœurs, d’autres ordinateurs plus “loin”, d’autres périphériques) devront, chacun à leur rythme et en fonction de leur charge traiter un bout du problème et retourner une réponse.  Tout ce qui prend du temps peut être rendu asynchrone pour rendre la main le plus vite possible, clé de la réactivité des OS modernes.

L’asynchronisme pose ainsi de gros problèmes d’écriture. Comment faire en sorte que le programme ne soit pas bloqué sur la ligne x, en attende de la réponse à la question “envoyée ailleurs” sans pour autant passer à la ligne x+1 qui doit elle attendre que les résultats soient là pour avoir un sens ? Le propre de l’asynchronisme est de ne pas être bloquant, et c’est bien là que ça... bloque ! Car comment continuer à travailler sur des données qu’on a demandé si elles ne sont pas encore là...

Grâce aux méthodes anonymes de C# il était plus ou moins facile de résoudre le problème en programmant l’action a effectuer sur la réponse au sein d’un Callback. Charge au développeur de gérer les conséquences de tout cela : que faire pendant qu’on attend quelque chose ? rien ? passer à autre chose ? Que faire quand on sera interrompu, “plus tard”, par la réponse qui enfin arrivera ?

Tout cela peut se résoudre en appliquant des guides lines précises et en maitrisant, notamment sous Silverlight, WPF et demain WinRT, les notions de databinding, les méthodes de travail de type MVVM, et bien entendu les bases mêmes à la fois du multitâches et de la programmation asynchrone. Car dans la pratique c’est en faisant un savant mélange de toutes ces choses qu’on arrive à écrire un programme fluide et réactif.

Simplifions un peu

Toutefois, si l’utilisation des méthodes anonymes a rendu les traitements asynchrones plus facile à orchestrer, elles n’ont pas résolu tous les problèmes. Loin s’en faut.

Lorsqu’une application Silverlight doit par exemple demander une liste d’items sur laquelle elle doit effectuer un autre traitement asynchrone (le tout via RIA Services par exemple), les callbacks s’imbriquent les uns dans les autres pour devenir illisibles. S’il faut en même temps prendre en charge la gestion d’éventuelles erreurs, leur log, etc, le code peut devenir rapidement imbuvable, donc in-maintenable.

La programmation asynchrone se généralisant il fallait trouver un moyen de simplifier tout ça.

Async et Await

C# 5 vient à la rescousse avec deux nouvelles instructions. Async et Await.

Et c’est plus simple encore que cela en a l’air au regard de la complexité du sujet.

Async est utilisé pour marquer une méthode. Cette marque est faite à l’intention du compilateur pour lui dire “à l’intérieur de cette méthode je veux écrire mon code comme si tout était synchrone”. C’est le compilateur (et la plateforme) qui vont se charger du reste.

Pour la petite histoire, cette bonne idée à été reprise de F# d’ailleurs.

Il est important de marquer une méthode avec Async car cela est utilisé par les appelants qui doivent savoir qu’ils auront certainement la main avant que le travail de la méthode appelée ne soit terminé. L’impact dépasse donc la méthode marquée pour atteindre tout code qui en fera l’usage.

Await est simplement utilisé comme une sorte de préfixe devant une instruction asynchrone pour dire au compilateur de se débrouiller pour que tout ce passe “comme si” l’appel était bloquant. On revient à une écriture synchrone du code. C’est donc une grande simplification. Mais attention l’astuce est plus complexe, j’y reviendrai certainement, car les appels ne sont pas réellement bloquants... la méthode prendra souvent fin et reviendra à l’appelant avant que le job des tâches asynchrones ne soit terminé. Ce sont bien des callbacks et non des points bloquants...

En fait, C# 5 va bien écrire les callbacks à notre place. Mais comme il le fait pour nous le code qu’on écrit redevient clair et limpide. Il ne faut pas se laisser abuser par cette apparence donc.

Voici un exemple de code utilisant Async et Await :

   1: public async void ShowReferencedContent(string filename)
   2: {
   3:   var url = await BeginReadFromFile(filename);
   4:   var contentOfUrl = await BeginHttpGetFromUrl(url);
   5:   MessageBox.Show(contentOfUrl);
   6: }

Dans la méthode ci-dessus on remarque deux choses : la méthode elle-même est marquée avec le mot clé “async” comme expliqué plus haut, et les lignes 3 et 4 utilise “await” en préfixe du code exécuté. Ces deux lignes de code font des appels à des méthodes asynchrones. Normalement le programme passerait à la ligne 4 avant que le résultat de la ligne 3 ne soit connu. Et forcément ça marcherait moins bien...

Mais ici, inutile d’écrire des méthodes anonymes passées en paramètres de méthodes asynchrones acceptant des callbacks (rien que l’écrire c’est compliqué !). C#5 se chargera de tout. Le code “semble” synchrone, mais il reste asynchrone seule l’imbrication des callbacks est écrite à notre place.

Vous allez penser que c’est très bien, mais que se passe-t-il si on souhaite que la méthode retourne une réponse à ses appelants ?

Comme une méthode “async” pourra se terminer avant que son travail ne soit réellement fini, il va falloir trouver un moyen d’indiquer à l’appelant qu’en plus elle retourne une valeur qui ne viendra que “plus tard”. On utilise alors une notation un peu différente pour son entête.

Par exemple, si la méthode doit retourner un “string”, son entête ne sera pas “public async string maméthode()” mais elle utilisera la classe générique Task pour retourner un Task<string>.

Une instance de Task représente un “bout de travail” qui peut éventuellement retourner une valeur. L’appelant peut examiner l’objet Task pour connaître son état et sa valeur de retour.

Un tel code ressemblera à cela :

   1: public static async Task<string> GetReferencedContent(string filename)
   2: {
   3:   string url = await BeginReadFromFile(filename);
   4:   string contentOfUrl = await BeginHttpGetFromUrl(url);
   5:   return contentOfUrl;
   6: }

On note la particularité suivante : la méthode retourne un string alors que le type est Task<string>. Ici aussi c’est le compilateur qui prend en charge la transformation du string en Task<string>.

Désormais un appelant peut utiliser la méthode comme bon lui semble : dans un mode “à la synchrone” avec await, ou bien en attendant “manuellement” le résultat, en sondant régulièrement le Task pour connaître son état...

Ceux qui ont déjà utilisé les méthodes asynchrones de .NET 4 noteront que les paires de méthodes "Begin / End” (comme WebRequest.BeginGetResponse / WebRequest.EndGetResponse), si elles existent toujours sous .NET 4.5 ne sont pas utilisables avec “await” (les Beginxxx nécessitent un appel de méthode explicite à l’intérieur du callback pour obtenir la réponse notamment). A la place, .NET 4.5 fournit de nouvelles méthodes qui retournent un Task. Ainsi, au lieu par exemple d’appeler WebRequest.BeginGetResponse, on utilisera WebRequest.GetResponseAsync.

Un petit exemple pour clarifier :

   1: private static async Task<string> GetContent(string url)
   2: {
   3:   WebRequest wr = WebRequest.Create(url);
   4:   var response = await wr.GetResponseAsync();
   5:   using (var stm = response.GetResponseStream())
   6:   {
   7:     using (var reader = new StreamReader(stm))
   8:     {
   9:       var content = await reader.ReadToEndAsync();
  10:       return content;
  11:     }
  12:   }
  13: }

 

Conclusion

C# a tellement évolué depuis sa première version qu’on se demande comment il est possible de trouver encore matière à faire de nouvelles versions... Mais c’est sans compter sur les besoins mêmes du développement qui évoluent.

Jusqu’à C# la plupart des langages disparaissaient lorsqu’ils devenaient inadaptés. C# a connu des modifications d’une profondeur rarement atteinte par aucun langage professionnel.

C# était à sa sortie une sorte de Java avec des éléments de syntaxe de Delphi (comme les propriétés). Delphi et C# ayant le même père on sentait la proximité tout comme l’influence de Java qui avait valu quelques soucis à Microsoft à l’époque avant d’être abandonné. Puis, en une dizaine d’années, au fil des versions, C# est devenu un langage totalement différent de ses sources d’inspiration. Intégrant rapidement des techniques empruntées à d’autres langages, ajoutant ses propres bonnes idées ou piochant dans des langages essayistes tels que F#.

Avec C# 5, fonctionnant avec WinRT, WPF et Silverlight, nous allons disposer d’un langage encore plus proche de nos besoins quotidiens pour produire des logiciels de plus en plus sophistiqués, réactifs, fluides, designés, s’adaptant à différents form factors.

Loin de se spécialiser (et perdre de sa souplesse) ou de trop se généraliser (et de se noyer dans trop d’options), C# impose son style unique qui en fait un allié de premier plan pour programmer des logiciels modernes tout en nous permettant de maitriser la complexité croissante du code à produire.

Certaines modes voudrait faire venir au premier plan pour développer de vrais applications de vieux langages utilitaires interprétés dont l’indigence laisse pantois. Ne vous laissez pas convaincre par ces leurres, servez-vous de vos connaissances, rentabilisez vos diplômes et demandez-vous si un informaticien professionnel qui ne sait pas faire la différence en javaScript et C# 5 a encore le droit de se proclamer professionnel...

Faites des heureux, partagez l'article !
blog comments powered by Disqus