Tutoriel pour apprendre les nouveautés de C# 6 et C# 7

Aymen TROUDI

Lors des TechDays 2015, Microsoft a présenté une nouvelle version 6 de son langage phare C#. À peine un an après, son successeur C# 7 prépare son arrivée. Chacune de ces versions apporte un ensemble de facilités aux développeurs.

À travers ce tutoriel, nous passons en revue l'ensemble des nouveautés apportées par C# 6 et celles attendues par C# 7. Chaque fonctionnalité sera décortiquée avec des exemples illustratifs.

Commentez

- Titre : Tutoriel pour apprendre les nouveautés de C# 6 et C# 7

- Auteur : Aymen TROUDI

- Parution : 3 août 2017

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Aymen TROUDI. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. C#6

- II-A. Propriétés automatiques

- II-B. Initialisation des indexeurs

- II-C. Initialisation des collections

- II-D. Membres sous forme d'expression

- II-E. Filtres d'exception

- II-F. Await dans les catch / finally

- II-G. Import statique

- II-H. Opérateur « nameof »

- II-I. Opérateur de nullité conditionnelle « ? »

- II-J. Chaînes interpolées

- II-J-1. Remarques

- III. C# 7

- III-A. Les littéraux binaires

- III-B. Les séparateurs digitaux

- III-C. Membres sous forme d'expression

- III-D. Les variables de sortie

- III-E. Les variables de référence

- III-F. Les fonctions locales

- III-G. Les tuples

- III-H. Le filtrage par motif

- III-I. Remarque

- IV. Conclusion

- V. Remerciements

I - Introduction

Avant d'aller dans le vif du sujet, rappelons les versions actuelles de C#, des frameworks .NET respectifs, des versions Visual Studio relatives, de leurs dates de sortie et des principales fonctionnalités apportées.

II - C#6

II-A - Propriétés automatiques

Les propriétés automatiques, introduites depuis C# 3, rendent la déclaration d'une propriété plus concise puisqu'aucune logique additionnelle n'est requise dans les accesseurs de la propriété. C# 6 leur apporte deux améliorations principales.

- Initialisation

C# 6 permet de joindre l'initialisation d'une propriété automatique à sa déclaration. Cela n'était pas possible dans les versions antérieures où l'initialisation se faisait forcément après la déclaration (i.e. via le constructeur).

1. // Avant C# 6

3. // déclaration d'une autopropriété

4. public string OldAutoProp { get; set; }

6. // initialisation dans le constructeur

7. public OldCSharp()

8. {

9. OldAutoProp = "INIT";

10. }

12. // Avec C# 6

14. // déclaration et initialisation d'une autopropriété

15. public string NewAutoProp { get; set; } = "INIT";

- Lecture seule

La déclaration d'une propriété automatique en lecture seule n'était pas possible jusqu'ici. Dans le meilleur des cas, un setter privé (ou protégé) est utilisé pour se rapprocher de cette notion. Nul besoin de le faire avec C# 6.

1. // Avant C# 6

3. // déclaration d'une autopropriété (presque !) en « lecture seule »

4. // on dit presque, car le setter privé peut être appelé n'importe

5. // où au sein de la classe (dans un constructeur ou dans une méthode)

6. public string OldReadOnlyAutoProp { get; private set; }

8. // initialisation dans le constructeur

9. public OldCSharp()

10. {

11. OldReadOnlyAutoProp = "INIT";

12. }

14. // Avec C# 6

16. // déclaration et initialisation d'une autopropriété en « lecture seule »

17. public string NewReadOnlyAutoProp { get; } = "INIT";

19. // déclaration et initialisation peuvent se faire distinctement

21. // déclaration d'une autopropriété en « lecture seule »

22. public string NewReadOnlyAutoProp { get; }

24. // initialisation dans le constructeur

25. public NewCSharp()

26. {

27. NewReadOnlyAutoProp = "INIT";

28. }

II-B - Initialisation des indexeurs

C# 6 introduit une nouvelle façon d'initialiser les objets indexés (i.e. les dictionnaires).

1. // Exemple 1 : Initialisation des dictionnaires

3. // Avant C# 6

4. var oldDic = new Dictionary<int, string>;

5. {

6. {1 , "a"},

7. {2 , "b"},

8. {3 , "c"},

9. };

11. // Avec C# 6

12. var newDic = new Dictionary<int, string>;

13. {

14. [1] = "a",

15. [2] = "b",

16. [3] = "c"

17. };

19. // Exemple 2 : Initialisation des objets indexés

21. // classe indexée

22. public class IndexedClass

23. {

24. private readonly string[] _array = new string[10];

26. public string this[int i]

27. {

28. get

29. {

30. return _array[i];

31. }

32. set

33. {

34. _array[i] = value;

35. }

36. }

37. }

39. // Avant C# 6

40. var oldObj = new IndexedClass();

41. oldObj[1] = "a";

43. // Avec C# 6

44. var newObj = new IndexedClass {[1] = "a"};

II-C - Initialisation des collections

C# 6 prend en charge le support de l'extension « Add » ce qui permet d'avoir un code plus allégé et épuré lors de l'initialisation des collections.

1. // Avant C# 6

3. // initialisation d'une collection de personnes

4. var oldPers = new List<Person>()

5. {

6. new Person("Walter", "White"),

7. new Person("Jon", "Snow")

8. };

10. // Avec C# 6

12. // définition de méthodes d'extensions « Add »

14. // 1re méthode d'extension « Add » acceptant deux paramètres « string »

15. public static void Add(this List<Person> persons, string first, string last)

16. {

17. persons.Add(new Person(first, last));

18. }

20. // 2e méthode d'extension « Add » acceptant un paramètre « int »

21. public static void Add(this List<Person> persons, int nbr)

22. {

23. for (var index = 0; index < nbr; index++)

24. {

25. persons.Add(new Person("F_" + index, "L_" + index));

26. }

27. }

29. // initialisation d'une collection de personnes

30. var newPers = new List<Person>()

31. {

32. { "Walter", "White" },

33. { "Jon", "Snow" },

34. { 2 },

35. };

II-D - Membres sous forme d'expression

C# 6 simplifie la déclaration des propriétés, méthodes et index dont le corps est constitué d'une seule instruction. La syntaxe rappelle les expressions lambda.

1. // Avant C# 6

2. public string FullName

3. {

4. get { return FirstName + " " + LastName; }

5. }

7. // Avec C# 6

8. public string FullName =>FirstName + " " + LastName;

II-E - Filtres d'exception

C# permet jusqu'à maintenant d'intercepter les exceptions en se basant sur leur type et en allant du plus spécifique vers le plus général lors de la définition des blocs catch appropriés. C# 6 étend les blocs catch avec des conditions grâce à l'introduction du mot-clé « when ». Lorsque la condition n'est pas validée, le bloc catch n'est pas évalué. Lorsque plusieurs conditions sont valides à la fois, seul le premier bloc catch est évalué.

1. // Avant C# 6

2. try

3. {

4. // un traitement générant une exception

5. throw new ArgumentException("__ERR_2");

6. }

7. catch (Exception ex)

8. {

9. if (ex is FormatException && ex.Message == "__ERR_1")

10. {

11. Log("__MSG_1");

12. }

13. else if (ex is ArgumentException && ex.Message == "__ERR_2")

14. {

15. Log("__MSG_2");

16. }

17. else

18. {

19. Log("__MSG_3");

20. }

21. }

23. // Avec C# 6

24. try

25. {

26. // un traitement générant une exception

27. throw new ArgumentException("__ERR_2");

28. }

29. catch (FormatException ex) when (ex.Message == "__ERR_1")

30. {

31. Log"__MSG_1");

32. }

33. catch (ArgumentException ex) when (ex.Message == "__ERR_2")

34. {

35. Log("__MSG_2");

36. }

37. catch (Exception)

38. {

39. Log("__MSG_3");

40. }

II-F - Await dans les catch / finally

Avant C# 6, il n'était pas possible d'utiliser le mot-clé « await » au sein d'un bloc catch ou finally. Désormais, c'est possible de le faire. Cela est très pratique, par exemple, pour journaliser des erreurs dans un bloc catch ou libérer des ressources dans un bloc finally suite à une exception dans un code asynchrone.

1. // Avant C# 6

2. private static async Task AsyncJob()

3. {

4. Exception exception;

6. try

7. {

8. // un traitement générant une exception

9. throw new Exception("__ERR");

10. }

11. catch (Exception ex)

12. {

13. exception = ex;

14. }

16. if (exception != null)

17. {

18. await AsyncLog(exception.Message);

19. }

21. await AsyncDispose();

22. }

24. // Avec C# 6

25. private static async Task AsyncJob()

26. {

27. try

28. {

29. // un traitement générant une exception

30. throw new Exception("__ERR");

31. }

32. catch (Exception ex)

33. {

34. await AsyncLog(ex.Message);

35. }

36. finally

37. {

38. await AsyncDispose();

39. }

40. }

42. // journalisation des erreurs

43. private static Task AsyncLog(string msg)

44. {

45. return Task.Factory.StartNew(() =>

46. {

47. Thread.Sleep(1000);

48. Log(msg);

49. });

50. }

52. // libération des ressources

53. private static Task AsyncDispose()

54. {

55. return Task.Factory.StartNew(() =>

56. {

57. Thread.Sleep(1000);

58. Dispose();

59. });

60. }

II-G - Import statique

La directive using static permet d'importer les méthodes statiques d'une classe et de les utiliser sans préfixe comme si elles étaient déclarées dans la classe courante.

1. // Avec C# 6

2. namespace Csharp6Demo

3. {

4. using System;

5. using static System.Console;

6. using static System.DateTime;

8. public class StaticImport

9. {

10. // utiliser Now au lieu de DateTime.Now

11. public DateTime Date { get; } = Now;

13. public void Print()

14. {

15. // utiliser WriteLine au lieu de Console.WriteLine

16. WriteLine(Date.ToShortDateString());

17. }

18. }

19. }

II-H - Opérateur « nameof »

Il est parfois utile de manipuler le nom d'une méthode ou d'une variable dans le code. Cela se faisait jusqu'ici via une chaîne « en dur », difficile à maintenir en cas de refactoring. C# 6 répond à ce besoin avec l'opérateur « nameof » permettant de retrouver le nom d'un symbole donné.

1. private double _price;

2. public double Price

3. {

4. get { return _price; }

6. // Avant C# 6

7. set { _price = value; OnPropertyChanged("Price"); }

9. // Avec C# 6

10. set { _price = value; OnPropertyChanged(nameof(Price)); }

11. }

II-I - Opérateur de nullité conditionnelle « ? »

L'opérateur « ? » offre une façon élégante et concise pour accéder aux membres d'un objet sans générer d'exception de type NullReferenceException . On peut aussi chaîner cet opérateur pour naviguer au sein d'une arborescence de membres.

1. // Exemple 1 : accès aux membres d'un objet

3. // Avant C# 6

4. string country = "Undefined";

5. IEnumerable<Person> persons = FindPersons(take: 1);

6. if (persons != null)

7. {

8. Person person = persons.FirstOrDefault();

9. if (person != null && person.Address != null && person.Address.Country != null)

10. {

11. country = person.Address.Country;

12. }

13. }

15. // Avec C# 6

16. string country = FindPersons(take: 1)?.FirstOrDefault()?.Address?.Country ?? "Undefined";

18. // Exemple 2 : accès aux membres d'un tableau

20. // Avant C# 6

21. string firstname = null;

22. Person[] persons = GetPersons();

23. if (persons != null && persons.Length >= 2 && persons[1] != null)

24. {

25. firstname = persons[1].FirstName;

26. }

28. // Avec C# 6

29. string firstname = GetPersons()?[1]?.FirstName;

31. // Exemple 3 : invocation d'un delegate

33. // Avant C# 6

34. public void OnPropertyChanged(string propertyName)

35. {

36. if (this.PropertyChanged != null)

37. {

38. this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

39. }

40. }

42. // Avec C# 6

43. public void OnPropertyChanged(string propertyName)

44. {

45. this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

46. }

II-J - Chaînes interpolées

La construction des chaînes de caractères a été améliorée dans C# 6 grâce à une syntaxe plus facile à lire et écrire que le fameux String.Format. Il faut noter que les valeurs d'une chaîne interpolée sont formatées par défaut dans la culture courante. Pour changer ce comportement, C# 6 introduit la notion de FormattableString. Cela signifie qu'une chaîne interpolée est convertible et peut être donc formatée selon une culture différente de la culture courante.

1. // Exemple 1 : interpolation de chaînes

3. // Avant C# 6

4. public override string ToString()

5. {

6. return string.Format("My name is {0} {1}", FirstName, LastName);

7. }

9. // Avec C# 6

10. public override string ToString()

11. {

12. return $"My name is {FirstName} {LastName}";

13. }

15. // Exemple 2 : formatage selon une culture

17. // déclaration d'une valeur de type double

18. double dbl = 1.5;

20. // affichage de la chaîne interpolée selon la culture courante

21. Console.WriteLine($"double in french culture = {dbl}"); // 1,5

23. // affichage de la chaîne interpolée selon une culture invariante (V1)

24. Console.WriteLine(FormattableString.Invariant($"double in invariant culture = {dbl}")); // 1.5

26. // affichage de la chaîne interpolée selon une culture invariante (V2)

27. IFormattable str = $"double in invariant culture = {dbl}";

28. Console.WriteLine(str.ToString(null, CultureInfo.InvariantCulture)); // 1.5

II-J-1 - Remarques

- .NET 4.5 et VS 2013
Bien que C# 6 ait accompagné la sortie du .NET 4.6 et de VS 2015, il est possible d'utiliser la plupart de ses fonctionnalités (hormis FormattableString) sous .NET 4.5. Il est également possible d'utiliser du C# 6 sous VS 2013 à condition d'inclure le package nuget « Microsoft.Net.Compilers ». Cependant, VS 2013 ne considère pas les instructions C# 6 comme syntaxiquement valides et les remonte au niveau de l'onglet « Erreurs » même si la compilation se passe correctement.

- ASP.NET MVC 5
C# 6 est pris en charge nativement dans ASP.NET MVC 6 et VS 2015. Cependant, il n'est pas compatible avec ASP.NET MVC 5. Pour utiliser C# 6 dans ASP.NET MVC 5 sous VS 2013/2015, il suffit d'inclure le package nuget « Microsoft.CodeDom.Providers.DotNetCompilerPlatform ».

- ReSharper et VS 2013
ReSharper sous VS 2013 considère le code C# 6 comme erroné et le surligne en rouge. Pour résoudre le problème, il suffit d'activer la fonctionnalité C# 6 au niveau ReSharper (via le tooltip à gauche du code).

- VS 2013 et VS 2015
Pour de multiples raisons (migration des VM pas terminée, licences limitées, etc.), certaines équipes peuvent utiliser à la fois des versions VS 2013 et VS 2015. Au premier abord, cela n'a pas d'impact tant que le package nuget « Microsoft.Net.Compilers » est inclus dans la solution. Cependant, cela impacte de manière significative le temps de compilation sous VS 2015. Ce dernier ne compile plus de manière native et s'appuie sur le package nuget lors de la compilation, ce qui explique la lenteur. Une des astuces pour résoudre ce problème, consiste à éditer le(s) csproj pour rajouter une condition sur l'import du package nuget en question.

1. <!-- bypass de l'import si un fichier « vs15.flag » se trouve sous le dossier parent de la solution

2. si le fichier « vs15.flag » existe, la compilation se fera de manière native et donc rapide -->

3. <Import Project="..\packages\Microsoft.Net.Compilers.1.3.2\build\Microsoft.Net.Compilers.props"

4. Condition="Exists('..\packages\Microsoft.Net.Compilers.1.3.2\build\Microsoft.Net.Compilers.props')

5. AND !Exists('..\vs15.flag')" />

III - C# 7

Au moment de l'écriture de ce tutoriel, il n'y a toujours pas de date officielle de sortie pour C# 7 ni pour la liste définitive de ses fonctionnalités. Cependant, la Preview 5 de Visual Studio '15' nommé Visual Studio 2017 suite aux dernières nouvelles de la conférence Connect(), lève le voile sur les fonctionnalités potentielles de C# 7.

III-A - Les littéraux binaires

Les versions actuelles de C# permettent de représenter des littéraux dans une forme digitale ou hexadécimale. C# 7 introduit en plus la forme binaire. Le préfixe à utiliser pour définir un littéral binaire est 0b.

1. // Avec C# 7

2. int b = 0b1010;

III-B - Les séparateurs digitaux

C# 7 apporte une facilité syntaxique, existante en Java 7, permettant d'améliorer la lisibilité des littéraux binaires, digitaux ou hexadécimaux. Le caractère underscore « _ » est utilisé comme séparateur dans un littéral.

1. // Avant C# 7

2. int d = 123456789;

3. int x = 0xABCDEF;

5. // Avec C# 7

6. int d = 123_456_789;

7. int x = 0xAB_CD_EF;

8. int b = 0b1010_1011_1100_1101_1110_1111;

III-C - Membres sous forme d'expression

C# 6 a introduit cette fonctionnalité pour les propriétés, méthodes et index. C# 7 l'étend pour les constructeurs, les destructeurs et les exceptions.

1. // Avec C# 7

2. class Point

3. {

4. private static int Total;

6. public int x { get; set; }

7. public int y { get; set; }

9. // expression constructeur

10. public Point() => Total++;

12. // expression destructeur

13. ~Point() => Total--;

15. // expression throw

16. public Point Move() => throw new NotImplementedException();

17. }

III-D - Les variables de sortie

Actuellement, avant d'utiliser une variable de sortie « out », il faut la déclarer. Avec C# 7, cela n'est plus nécessaire. La variable de sortie est déclarée au moment de son utilisation.

1. // Exemple 1

3. // Avant C# 7

4. static int? OldConvert(string str)

5. {

6. int val;

7. if (int.TryParse(str, out val)) return val;

8. return null;

9. }

11. // Avec C# 7

12. static int? NewConvert(string str)

13. {

14. if (int.TryParse(str, out int val)) return val;

15. return null;

16. }

18. // Exemple 2

20. // méthode avec une variable de sortie « out »

21. static void Modify(out int val)

22. {

23. val = new Random().Next();

24. }

26. // Avant C# 7

27. int x;

28. Modify(out x);

29. Console.WriteLine(x);

31. // Avec C# 7

32. Modify(out int y);

33. Console.WriteLine(y);

III-E - Les variables de référence

Les variables de référence « ref » étaient jusqu'ici utilisées lors du passage des paramètres à une fonction. C# 7 étend leur utilisation aux variables locales et aux retours de fonctions. Ainsi, il est désormais possible qu'une fonction renvoie en retour une référence. Il est également possible de stocker une référence dans une variable locale.

1. // Avec C# 7

3. // une fonction qui renvoie une référence

4. public static ref int Find(int number, int[] numbers)

5. {

6. for (int i = 0; i < numbers.Length; i++)

7. {

8. if (numbers[i] == number)

9. {

10. return ref numbers[i];

11. }

12. }

14. throw new IndexOutOfRangeException($"{number} not found");

15. }

17. // déclaration d'un tableau d'entiers

18. int[] array = { 2, 14, -3, 0, 8, 9, -6 };

20. // une variable locale qui stocke une référence

21. ref int place = ref Find(8, array);

23. Console.WriteLine(array[4]); // affiche 8

24. place = 5;

25. Console.WriteLine(array[4]); // affiche 5

III-F - Les fonctions locales

Avec C# 7, on peut déclarer et appeler une fonction locale à l'intérieur d'un scope donné (constructeur, propriété ou méthode). La fonction locale a accès aux paramètres de l'appelant. Elle peut être récursive ou asynchrone. Le corps d'une fonction locale peut être défini sous forme d'expression lambda.

1. // Avec C# 7

2. public int MyProperty

3. {

4. get

5. {

6. // déclaration d'une fonction locale

7. int Func() => new Random().Next(10);

9. // utilisation d'une fonction locale

10. return Func() * Func() - Func();

11. }

12. }

III-G - Les tuples

Avec les versions précédentes de C#, avoir plusieurs valeurs de retour dans une fonction n'était pas possible sans le recours à des variables de sortie « ref » ou à des objets créés sur mesure. Désormais, c'est de l'histoire ancienne avec C# 7, car une fonction peut renvoyer plusieurs valeurs de retour à la fois. C# 7 apporte également des améliorations au niveau de la construction et la déconstruction d'un tuple.

1. // Avec C# 7

3. // Exemple 1 : fonction avec plusieurs valeurs de retour « nommées »

4. public static (int sum, int sub) SumSubOne(int a, int b)

5. {

6. return (a + b, a - b);

7. }

9. // construction d'un tuple

10. var one = SumSubOne(4,2);

12. // affichage d'un tuple

13. Console.WriteLine($"{one} {one.sum} {one.sub}");

15. // Exemple 2 : fonction avec plusieurs valeurs de retour « anonymes »

16. public static (int, int) SumSubTwo(int a, int b)

17. {

18. return (a + b, a - b);

19. }

21. // construction d'un tuple

22. var two = SumSubTwo(4,2);

24. // affichage d'un tuple

25. Console.WriteLine($"{two} {two.Item1} {two.Item2}");

27. // Exemple 3 : construction d'un tuple

28. var t1 = new Tuple<int, bool>(1, true); // Avant C# 7

29. var t2 = (foo: 1, bar: true); // Avec C# 7

30. Console.WriteLine($"{t2.foo} {t2.bar}");

32. // Exemple 4 : déconstruction d'un tuple

33. (var sum, var sub) = SumSubOne(4,2);

35. // traitement quelconque

36. var mul = sum * sub;

37. Console.WriteLine($"{sum} {sub} {mul}");

III-H - Le filtrage par motif

Le filtrage par motif, en anglais pattern matching, existe déjà dans les langages fonctionnels comme F#. Il consiste à vérifier si une valeur donnée correspond à un motif et si c'est le cas déclencher le traitement associé. C# 7 s'appuie sur les opérateurs is et when pour la définition des motifs dans les instructions conditionnelles if et switch.

1. // Avant C# 7

2. static void OldPrint(object o)

3. {

4. if (o is DateTime)

5. {

6. var d = (DateTime)o;

7. Console.WriteLine(d);

8. }

9. }

11. // Avec C# 7

12. static void NewPrint(object o)

13. {

14. if (o is DateTime d)

15. {

16. Console.WriteLine(d);

17. }

18. }

20. // Avec C# 7

21. static void NewSwitch(object o)

22. {

23. switch (o)

24. {

25. case string s:

26. Console.WriteLine($"object is a string of length {s.Length}");

27. break;

28. case int i when i % 2 == 0 :

29. Console.WriteLine($"object is an even int");

30. break;

31. case int i when i % 2 != 0:

32. Console.WriteLine($"object is an odd int");

33. break;

34. case Point p:

35. Console.WriteLine($"object is point(${p.x},${p.y})");

36. break;

37. case null:

38. Console.WriteLine($"object is null");

39. break;

40. default:

41. Console.WriteLine($"object is something else");

42. break;

43. }

44. }

III-I - Remarque

Les fonctionnalités C# 7 présentées ont été testées sous VS 2017 et .NET 4.5.2. Certaines, telles que les types « record », n'ont pas été présentées dans l'article vu qu'elles ont été introduites dans VS '15' Preview, mais retirées dans VS 2017. Enfin, il faut noter que certaines fonctionnalités sur les tuples nécessitent d'inclure le package nuget « System.ValueTuple ».

IV - Conclusion

À travers ce tutoriel, nous avons présenté une synthèse des fonctionnalités apportées par C# 6 et celles qui sont attendues par C# 7. La version 6 apporte des améliorations plutôt syntaxiques dans l'ensemble. La version 7 comble des fonctionnalités longtemps manquantes telles que les littéraux binaires et prend en charge d'autres fonctionnalités de langages fonctionnels (i.e. F#) telles que le filtrage par motif.
Le langage C# continue à évoluer assez constamment grâce aux efforts de Microsoft notamment via l'adoption d'un modèle open source pour .NET Core (compilateur Roslyn, etc.) et la création de la Fondation .NET (que Google a rejoint récemment).
La modernisation de C# se fait de manière agile, comme en témoignent les échanges sur github entre les développeurs et les concepteurs. Cela joue un rôle clé dans l'appréciation ou la dépréciation des fonctionnalités futures. Le délai (~ 1 an et demi) entre les deux versions 6 et 7 est l'un des plus courts comparé aux versions précédentes et peut être le fruit de cette conduite agile. C# devient de facto le langage de référence de Microsoft à la fois riche et évolutif pour faire du service (web api, wcf), du web (asp.net mvc) ou du mobile (xamarin).

V - Remerciements

Nous remercions la société Soat de nous avoir autorisés à publier ce tutoriel.

Nous tenons également à remercier Laethy pour la gabarisation et Claude Leloup pour la correction orthographique.