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).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
// Avant C# 6
// déclaration d'une autopropriété
public
string
OldAutoProp {
get
;
set
;
}
// initialisation dans le constructeur
public
OldCSharp
(
)
{
OldAutoProp =
"INIT"
;
}
// Avec C# 6
// déclaration et initialisation d'une autopropriété
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
// Avant C# 6
// déclaration d'une autopropriété (presque !) en « lecture seule »
// on dit presque, car le setter privé peut être appelé n'importe
// où au sein de la classe (dans un constructeur ou dans une méthode)
public
string
OldReadOnlyAutoProp {
get
;
private
set
;
}
// initialisation dans le constructeur
public
OldCSharp
(
)
{
OldReadOnlyAutoProp =
"INIT"
;
}
// Avec C# 6
// déclaration et initialisation d'une autopropriété en « lecture seule »
public
string
NewReadOnlyAutoProp {
get
;
}
=
"INIT"
;
// déclaration et initialisation peuvent se faire distinctement
// déclaration d'une autopropriété en « lecture seule »
public
string
NewReadOnlyAutoProp {
get
;
}
// initialisation dans le constructeur
public
NewCSharp
(
)
{
NewReadOnlyAutoProp =
"INIT"
;
}
II-B. Initialisation des indexeurs▲
C# 6 introduit une nouvelle façon d'initialiser les objets indexés (i.e. les dictionnaires).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
// Exemple 1 : Initialisation des dictionnaires
// Avant C# 6
var
oldDic =
new
Dictionary<
int
,
string
>;
{
{
1
,
"a"
},
{
2
,
"b"
},
{
3
,
"c"
},
};
// Avec C# 6
var
newDic =
new
Dictionary<
int
,
string
>;
{
[
1
]
=
"a"
,
[
2
]
=
"b"
,
[
3
]
=
"c"
};
// Exemple 2 : Initialisation des objets indexés
// classe indexée
public
class
IndexedClass
{
private
readonly
string
[]
_array =
new
string
[
10
];
public
string
this
[
int
i]
{
get
{
return
_array[
i];
}
set
{
_array[
i]
=
value
;
}
}
}
// Avant C# 6
var
oldObj =
new
IndexedClass
(
);
oldObj[
1
]
=
"a"
;
// Avec C# 6
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
// Avant C# 6
// initialisation d'une collection de personnes
var
oldPers =
new
List<
Person>(
)
{
new
Person
(
"Walter"
,
"White"
),
new
Person
(
"Jon"
,
"Snow"
)
};
// Avec C# 6
// définition de méthodes d'extensions « Add »
// 1re méthode d'extension « Add » acceptant deux paramètres « string »
public
static
void
Add
(
this
List<
Person>
persons,
string
first,
string
last)
{
persons.
Add
(
new
Person
(
first,
last));
}
// 2e méthode d'extension « Add » acceptant un paramètre « int »
public
static
void
Add
(
this
List<
Person>
persons,
int
nbr)
{
for
(
var
index =
0
;
index <
nbr;
index++
)
{
persons.
Add
(
new
Person
(
"F_"
+
index,
"L_"
+
index));
}
}
// initialisation d'une collection de personnes
var
newPers =
new
List<
Person>(
)
{
{
"Walter"
,
"White"
},
{
"Jon"
,
"Snow"
},
{
2
},
};
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.
2.
3.
4.
5.
6.
7.
8.
// Avant C# 6
public
string
FullName
{
get
{
return
FirstName +
" "
+
LastName;
}
}
// Avec C# 6
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é.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
// Avant C# 6
try
{
// un traitement générant une exception
throw
new
ArgumentException
(
"__ERR_2"
);
}
catch
(
Exception ex)
{
if
(
ex is
FormatException &&
ex.
Message ==
"__ERR_1"
)
{
Log
(
"__MSG_1"
);
}
else
if
(
ex is
ArgumentException &&
ex.
Message ==
"__ERR_2"
)
{
Log
(
"__MSG_2"
);
}
else
{
Log
(
"__MSG_3"
);
}
}
// Avec C# 6
try
{
// un traitement générant une exception
throw
new
ArgumentException
(
"__ERR_2"
);
}
catch
(
FormatException ex) when
(
ex.
Message ==
"__ERR_1"
)
{
Log"__MSG_1"
);
}
catch
(
ArgumentException ex) when
(
ex.
Message ==
"__ERR_2"
)
{
Log
(
"__MSG_2"
);
}
catch
(
Exception)
{
Log
(
"__MSG_3"
);
}
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
// Avant C# 6
private
static
async
Task AsyncJob
(
)
{
Exception exception;
try
{
// un traitement générant une exception
throw
new
Exception
(
"__ERR"
);
}
catch
(
Exception ex)
{
exception =
ex;
}
if
(
exception !=
null
)
{
await
AsyncLog
(
exception.
Message);
}
await
AsyncDispose
(
);
}
// Avec C# 6
private
static
async
Task AsyncJob
(
)
{
try
{
// un traitement générant une exception
throw
new
Exception
(
"__ERR"
);
}
catch
(
Exception ex)
{
await
AsyncLog
(
ex.
Message);
}
finally
{
await
AsyncDispose
(
);
}
}
// journalisation des erreurs
private
static
Task AsyncLog
(
string
msg)
{
return
Task.
Factory.
StartNew
((
) =>
{
Thread.
Sleep
(
1000
);
Log
(
msg);
}
);
}
// libération des ressources
private
static
Task AsyncDispose
(
)
{
return
Task.
Factory.
StartNew
((
) =>
{
Thread.
Sleep
(
1000
);
Dispose
(
);
}
);
}
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
// Avec C# 6
namespace
Csharp6Demo
{
using
System;
using
static
System.
Console;
using
static
System.
DateTime;
public
class
StaticImport
{
// utiliser Now au lieu de DateTime.Now
public
DateTime Date {
get
;
}
=
Now;
public
void
Print
(
)
{
// utiliser WriteLine au lieu de Console.WriteLine
WriteLine
(
Date.
ToShortDateString
(
));
}
}
}
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é.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
private
double
_price;
public
double
Price
{
get
{
return
_price;
}
// Avant C# 6
set
{
_price =
value
;
OnPropertyChanged
(
"Price"
);
}
// Avec C# 6
set
{
_price =
value
;
OnPropertyChanged
(
nameof
(
Price));
}
}
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
// Exemple 1 : accès aux membres d'un objet
// Avant C# 6
string
country =
"Undefined"
;
IEnumerable<
Person>
persons =
FindPersons
(
take:
1
);
if
(
persons !=
null
)
{
Person person =
persons.
FirstOrDefault
(
);
if
(
person !=
null
&&
person.
Address !=
null
&&
person.
Address.
Country !=
null
)
{
country =
person.
Address.
Country;
}
}
// Avec C# 6
string
country =
FindPersons
(
take:
1
)?.
FirstOrDefault
(
)?.
Address?.
Country ??
"Undefined"
;
// Exemple 2 : accès aux membres d'un tableau
// Avant C# 6
string
firstname =
null
;
Person[]
persons =
GetPersons
(
);
if
(
persons !=
null
&&
persons.
Length >=
2
&&
persons[
1
]
!=
null
)
{
firstname =
persons[
1
].
FirstName;
}
// Avec C# 6
string
firstname =
GetPersons
(
)?[
1
]?.
FirstName;
// Exemple 3 : invocation d'un delegate
// Avant C# 6
public
void
OnPropertyChanged
(
string
propertyName)
{
if
(
this
.
PropertyChanged !=
null
)
{
this
.
PropertyChanged
(
this
,
new
PropertyChangedEventArgs
(
propertyName));
}
}
// Avec C# 6
public
void
OnPropertyChanged
(
string
propertyName)
{
this
.
PropertyChanged?.
Invoke
(
this
,
new
PropertyChangedEventArgs
(
propertyName));
}
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
// Exemple 1 : interpolation de chaînes
// Avant C# 6
public
override
string
ToString
(
)
{
return
string
.
Format
(
"My name is {0} {1}"
,
FirstName,
LastName);
}
// Avec C# 6
public
override
string
ToString
(
)
{
return
$"My name is
{FirstName}
{LastName}"
;
}
// Exemple 2 : formatage selon une culture
// déclaration d'une valeur de type double
double
dbl =
1
.
5
;
// affichage de la chaîne interpolée selon la culture courante
Console.
WriteLine
(
$"double in french culture =
{dbl}"
);
// 1,5
// affichage de la chaîne interpolée selon une culture invariante (V1)
Console.
WriteLine
(
FormattableString.
Invariant
(
$"double in invariant culture =
{dbl}"
));
// 1.5
// affichage de la chaîne interpolée selon une culture invariante (V2)
IFormattable str =
$"double in invariant culture =
{dbl}"
;
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.
2.
3.
4.
5.
<!-- bypass de l'import si un fichier « vs15.flag » se trouve sous le dossier parent de la solution
si le fichier « vs15.flag » existe, la compilation se fera de manière native et donc rapide -->
<Import
Project
=
"..\packages\Microsoft.Net.Compilers.1.3.2\build\Microsoft.Net.Compilers.props"
Condition
=
"Exists('..\packages\Microsoft.Net.Compilers.1.3.2\build\Microsoft.Net.Compilers.props')
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.
2.
// Avec C# 7
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.
2.
3.
4.
5.
6.
7.
8.
// Avant C# 7
int
d =
123456789
;
int
x =
0xABCDEF
;
// Avec C# 7
int
d =
123_456_789
;
int
x =
0xAB_CD_EF
;
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
// Avec C# 7
class
Point
{
private
static
int
Total;
public
int
x {
get
;
set
;
}
public
int
y {
get
;
set
;
}
// expression constructeur
public
Point
(
) =>
Total++;
// expression destructeur
~
Point
(
) =>
Total--;
// expression throw
public
Point Move
(
) =>
throw
new
NotImplementedException
(
);
}
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
// Exemple 1
// Avant C# 7
static
int
?
OldConvert
(
string
str)
{
int
val;
if
(
int
.
TryParse
(
str,
out
val)) return
val;
return
null
;
}
// Avec C# 7
static
int
?
NewConvert
(
string
str)
{
if
(
int
.
TryParse
(
str,
out
int
val)) return
val;
return
null
;
}
// Exemple 2
// méthode avec une variable de sortie « out »
static
void
Modify
(
out
int
val)
{
val =
new
Random
(
).
Next
(
);
}
// Avant C# 7
int
x;
Modify
(
out
x);
Console.
WriteLine
(
x);
// Avec C# 7
Modify
(
out
int
y);
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
// Avec C# 7
// une fonction qui renvoie une référence
public
static
ref
int
Find
(
int
number,
int
[]
numbers)
{
for
(
int
i =
0
;
i <
numbers.
Length;
i++
)
{
if
(
numbers[
i]
==
number)
{
return
ref
numbers[
i];
}
}
throw
new
IndexOutOfRangeException
(
$"
{number} not found"
);
}
// déclaration d'un tableau d'entiers
int
[]
array =
{
2
,
14
,
-
3
,
0
,
8
,
9
,
-
6
};
// une variable locale qui stocke une référence
ref
int
place =
ref
Find
(
8
,
array);
Console.
WriteLine
(
array[
4
]
);
// affiche 8
place =
5
;
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
// Avec C# 7
public
int
MyProperty
{
get
{
// déclaration d'une fonction locale
int
Func
(
) =>
new
Random
(
).
Next
(
10
);
// utilisation d'une fonction locale
return
Func
(
) *
Func
(
) -
Func
(
);
}
}
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
// Avec C# 7
// Exemple 1 : fonction avec plusieurs valeurs de retour « nommées »
public
static
(
int
sum,
int
sub) SumSubOne
(
int
a,
int
b)
{
return
(
a +
b,
a -
b);
}
// construction d'un tuple
var
one =
SumSubOne
(
4
,
2
);
// affichage d'un tuple
Console.
WriteLine
(
$"
{one}
{one.
sum}
{one.
sub}"
);
// Exemple 2 : fonction avec plusieurs valeurs de retour « anonymes »
public
static
(
int
,
int
) SumSubTwo
(
int
a,
int
b)
{
return
(
a +
b,
a -
b);
}
// construction d'un tuple
var
two =
SumSubTwo
(
4
,
2
);
// affichage d'un tuple
Console.
WriteLine
(
$"
{two}
{two.
Item1}
{two.
Item2}"
);
// Exemple 3 : construction d'un tuple
var
t1 =
new
Tuple<
int
,
bool
>(
1
,
true
);
// Avant C# 7
var
t2 =
(
foo:
1
,
bar:
true
);
// Avec C# 7
Console.
WriteLine
(
$"
{t2.
foo}
{t2.
bar}"
);
// Exemple 4 : déconstruction d'un tuple
(
var
sum,
var
sub) =
SumSubOne
(
4
,
2
);
// traitement quelconque
var
mul =
sum *
sub;
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
.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
// Avant C# 7
static
void
OldPrint
(
object
o)
{
if
(
o is
DateTime)
{
var
d =
(
DateTime)o;
Console.
WriteLine
(
d);
}
}
// Avec C# 7
static
void
NewPrint
(
object
o)
{
if
(
o is
DateTime d)
{
Console.
WriteLine
(
d);
}
}
// Avec C# 7
static
void
NewSwitch
(
object
o)
{
switch
(
o)
{
case
string
s:
Console.
WriteLine
(
$"object is a string of length
{s.
Length}"
);
break
;
case
int
i when
i %
2
==
0
:
Console.
WriteLine
(
$"object is an even int"
);
break
;
case
int
i when
i %
2
!=
0
:
Console.
WriteLine
(
$"object is an odd int"
);
break
;
case
Point p:
Console.
WriteLine
(
$"object is point($
{p.
x},$
{p.
y})"
);
break
;
case
null
:
Console.
WriteLine
(
$"object is null"
);
break
;
default
:
Console.
WriteLine
(
$"object is something else"
);
break
;
}
}
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.