C# Language
Fonctions C # 6.0
Recherche…
Introduction
Cette sixième itération du langage C # est fournie par le compilateur Roslyn. Ce compilateur est sorti avec la version 4.6 du .NET Framework, mais il peut générer du code de manière rétrocompatible pour permettre de cibler des versions antérieures du framework. Le code C # version 6 peut être compilé de manière totalement compatible avec .NET 4.0. Il peut également être utilisé pour des frameworks antérieurs, mais certaines fonctionnalités nécessitant un support de framework supplémentaire peuvent ne pas fonctionner correctement.
Remarques
La sixième version de C # a été publiée en juillet 2015 aux côtés de Visual Studio 2015 et .NET 4.6.
Outre l'ajout de nouvelles fonctionnalités linguistiques, il inclut une réécriture complète du compilateur. Auparavant, csc.exe
était une application Win32 native écrite en C ++, avec C # 6, il s'agit désormais d'une application gérée .NET écrite en C #. Cette réécriture était connue sous le nom de projet "Roslyn" et le code est maintenant open source et disponible sur GitHub .
Nom de l'opérateurde
L'opérateur nameof
renvoie le nom d'un élément de code sous la forme d'une string
. Ceci est utile lors de la levée des exceptions liées aux arguments de méthode et lors de l'implémentation d' INotifyPropertyChanged
.
public string SayHello(string greeted)
{
if (greeted == null)
throw new ArgumentNullException(nameof(greeted));
Console.WriteLine("Hello, " + greeted);
}
L'opérateur nameof
est évalué au moment de la compilation et change l'expression en un littéral de chaîne. Ceci est également utile pour les chaînes nommées d'après leur membre qui les expose. Considérer ce qui suit:
public static class Strings
{
public const string Foo = nameof(Foo); // Rather than Foo = "Foo"
public const string Bar = nameof(Bar); // Rather than Bar = "Bar"
}
Comme les expressions nameof
sont des constantes à la compilation, elles peuvent être utilisées dans des attributs, des étiquettes de case
, des instructions de switch
, etc.
Il est pratique d'utiliser nameof
avec Enum
s. Au lieu de:
Console.WriteLine(Enum.One.ToString());
il est possible d'utiliser:
Console.WriteLine(nameof(Enum.One))
La sortie sera One
dans les deux cas.
L'opérateur nameof
peut accéder aux membres non statiques en utilisant une syntaxe de type statique. Au lieu de faire:
string foo = "Foo";
string lengthName = nameof(foo.Length);
Peut être remplacé par:
string lengthName = nameof(string.Length);
La sortie sera Length
dans les deux exemples. Cependant, ce dernier empêche la création d'instances inutiles.
Bien que l'opérateur nameof
fonctionne avec la plupart des constructions de langage, il existe certaines limitations. Par exemple, vous ne pouvez pas utiliser l'opérateur nameof
sur les types génériques ouverts ou les valeurs de retour de méthode:
public static int Main()
{
Console.WriteLine(nameof(List<>)); // Compile-time error
Console.WriteLine(nameof(Main())); // Compile-time error
}
De plus, si vous l'appliquez à un type générique, le paramètre de type générique sera ignoré:
Console.WriteLine(nameof(List<int>)); // "List"
Console.WriteLine(nameof(List<bool>)); // "List"
Pour plus d'exemples, consultez cette rubrique dédiée à nameof
.
Solution de contournement pour les versions précédentes ( plus de détails )
Bien que l'opérateur nameof
n'existe pas dans C # pour les versions antérieures à 6.0, il est possible d'avoir des fonctionnalités similaires en utilisant MemberExpression
comme suit:
Expression:
public static string NameOf<T>(Expression<Func<T>> propExp)
{
var memberExpression = propExp.Body as MemberExpression;
return memberExpression != null ? memberExpression.Member.Name : null;
}
public static string NameOf<TObj, T>(Expression<Func<TObj, T>> propExp)
{
var memberExpression = propExp.Body as MemberExpression;
return memberExpression != null ? memberExpression.Member.Name : null;
}
Usage:
string variableName = NameOf(() => variable);
string propertyName = NameOf((Foo o) => o.Bar);
Notez que cette approche entraîne la création d'une arborescence d'expression à chaque appel. Les performances sont donc nettement inférieures à nameof
opérateur nameof
qui est évalué au moment de la compilation et dont le temps d'exécution est nul.
Membres de la fonction avec expression
Les membres de fonctions à corps d'expression permettent l'utilisation d'expressions lambda en tant que corps membres. Pour les membres simples, le code peut être plus propre et plus lisible.
Les fonctions avec expression peuvent être utilisées pour les propriétés, les indexeurs, les méthodes et les opérateurs.
Propriétés
public decimal TotalPrice => BasePrice + Taxes;
Est équivalent à:
public decimal TotalPrice
{
get
{
return BasePrice + Taxes;
}
}
Lorsqu'une fonction contenant une expression est utilisée avec une propriété, la propriété est implémentée en tant que propriété de lecture seule.
Indexeurs
public object this[string key] => dictionary[key];
Est équivalent à:
public object this[string key]
{
get
{
return dictionary[key];
}
}
Les méthodes
static int Multiply(int a, int b) => a * b;
Est équivalent à:
static int Multiply(int a, int b)
{
return a * b;
}
Qui peut également être utilisé avec des méthodes void
:
public void Dispose() => resource?.Dispose();
Un remplacement de ToString
pourrait être ajouté à la classe Pair<T>
:
public override string ToString() => $"{First}, {Second}";
En outre, cette approche simpliste fonctionne avec le mot-clé override
:
public class Foo
{
public int Bar { get; }
public string override ToString() => $"Bar: {Bar}";
}
Les opérateurs
Cela peut également être utilisé par les opérateurs:
public class Land
{
public double Area { get; set; }
public static Land operator +(Land first, Land second) =>
new Land { Area = first.Area + second.Area };
}
Limites
Les membres de la fonction avec expression ont certaines limitations. Ils ne peuvent contenir aucune instruction de bloc ni aucune autre instruction contenant des blocs: if
, switch
, for
, foreach
, while
, do
, try
, etc.
Certains if
les déclarations peuvent être remplacées par des opérateurs ternaires. Certaines instructions for
et foreach
peuvent être converties en requêtes LINQ, par exemple:
IEnumerable<string> Digits
{
get
{
for (int i = 0; i < 10; i++)
yield return i.ToString();
}
}
IEnumerable<string> Digits => Enumerable.Range(0, 10).Select(i => i.ToString());
Dans tous les autres cas, l'ancienne syntaxe des membres de la fonction peut être utilisée.
Les membres de la fonction avec expression peuvent contenir async
/ await
, mais ils sont souvent redondants:
async Task<int> Foo() => await Bar();
Peut être remplacé par:
Task<int> Foo() => Bar();
Filtres d'exception
Les filtres d'exception permettent aux développeurs d'ajouter une condition (sous la forme d'une expression boolean
) à un bloc catch , ce qui permet à catch
d'exécuter uniquement si la condition est évaluée à true
.
Les filtres d'exception permettent la propagation des informations de débogage dans l'exception d'origine, car en utilisant une instruction if
dans un bloc catch
et en relançant l'exception, la propagation des informations de débogage dans l'exception d'origine est interrompue. Avec les filtres d'exception, l'exception continue à se propager vers le haut dans la pile d'appels, sauf si la condition est remplie. Par conséquent, les filtres d'exception facilitent considérablement le débogage. Au lieu de s'arrêter sur l'instruction throw
, le débogueur s'arrête sur l'instruction générant l'exception, avec l'état actuel et toutes les variables locales préservées. Les décharges accidentelles sont affectées de la même manière.
Les filtres d'exception sont pris en charge par le CLR depuis le début et ils sont accessibles depuis plus de dix ans depuis VB.NET et F # en exposant une partie du modèle de gestion des exceptions du CLR. Ce n'est qu'après la sortie de C # 6.0 que la fonctionnalité était également disponible pour les développeurs C #.
Utilisation de filtres d'exception
Les filtres d'exception sont utilisés en ajoutant une clause when
à l'expression catch
. Il est possible d'utiliser n'importe quelle expression renvoyant un bool
dans une clause when
(sauf wait ). La variable d'exception déclarée ex
est accessible depuis la clause when
:
var SqlErrorToIgnore = 123;
try
{
DoSQLOperations();
}
catch (SqlException ex) when (ex.Number != SqlErrorToIgnore)
{
throw new Exception("An error occurred accessing the database", ex);
}
Plusieurs blocs d' catch
avec when
clauses peuvent être combinées. La première when
la clause de retour true
entraînera l'exception à prendre. Son bloc catch
sera entré, tandis que les autres clauses catch
seront ignorées (leurs clauses when
ne seront pas évaluées). Par exemple:
try
{ ... }
catch (Exception ex) when (someCondition) //If someCondition evaluates to true,
//the rest of the catches are ignored.
{ ... }
catch (NotImplementedException ex) when (someMethod()) //someMethod() will only run if
//someCondition evaluates to false
{ ... }
catch(Exception ex) // If both when clauses evaluate to false
{ ... }
Article risqué quand
Mise en garde
Il peut être risqué d'utiliser des filtres d'exception: lorsqu'une
Exception
est générée à partir de la clausewhen
, la clauseException
from thewhen
est ignorée et traitée commefalse
. Cette approche permet aux développeurs d'écrirewhen
la clause sans prendre en charge des cas invalides.
L'exemple suivant illustre un tel scénario:
public static void Main()
{
int a = 7;
int b = 0;
try
{
DoSomethingThatMightFail();
}
catch (Exception ex) when (a / b == 0)
{
// This block is never reached because a / b throws an ignored
// DivideByZeroException which is treated as false.
}
catch (Exception ex)
{
// This block is reached since the DivideByZeroException in the
// previous when clause is ignored.
}
}
public static void DoSomethingThatMightFail()
{
// This will always throw an ArgumentNullException.
Type.GetType(null);
}
Notez que les filtres d'exception évitent les problèmes de numéro de ligne déroutants associés à l'utilisation de la méthode throw
lorsque le code défaillant fait partie de la même fonction. Par exemple, dans ce cas, le numéro de ligne est indiqué par 6 au lieu de 3:
1. int a = 0, b = 0;
2. try {
3. int c = a / b;
4. }
5. catch (DivideByZeroException) {
6. throw;
7. }
Le numéro de ligne d'exception est signalé par 6 car l'erreur a été interceptée et renvoyée avec l'instruction throw
sur la ligne 6.
La même chose ne se produit pas avec les filtres d'exception:
1. int a = 0, b = 0;
2. try {
3. int c = a / b;
4. }
5. catch (DivideByZeroException) when (a != 0) {
6. throw;
7. }
Dans cet exemple, a
est 0, alors la clause catch
est ignorée mais 3 est signalée comme numéro de ligne. C'est parce qu'ils ne déroulent pas la pile . Plus précisément, l'exception n'est pas pris en ligne 5 car a
fait effectivement égal 0
et donc il n'y a pas possibilité de l'exception à relancées sur la ligne 6 , car la ligne 6 n'exécute pas.
Enregistrement comme effet secondaire
Les appels de méthode dans la condition peuvent entraîner des effets secondaires, donc les filtres d'exception peuvent être utilisés pour exécuter du code sur des exceptions sans les intercepter. Un exemple courant qui en profite est une méthode Log
qui renvoie toujours false
. Cela permet de tracer les informations du journal lors du débogage sans avoir à relancer l'exception.
Sachez que même si cela semble être un moyen confortable de journalisation, cela peut être risqué, surtout si des assemblages de journalisation tiers sont utilisés. Celles-ci peuvent générer des exceptions lors de la connexion à des situations non évidentes qui peuvent ne pas être détectées facilement (voir la section Risque
when(...)
ci-dessus).
try
{
DoSomethingThatMightFail(s);
}
catch (Exception ex) when (Log(ex, "An error occurred"))
{
// This catch block will never be reached
}
// ...
static bool Log(Exception ex, string message, params object[] args)
{
Debug.Print(message, args);
return false;
}
L'approche courante dans les versions précédentes de C # consistait à enregistrer et à renvoyer l'exception.
try
{
DoSomethingThatMightFail(s);
}
catch (Exception ex)
{
Log(ex, "An error occurred");
throw;
}
// ...
static void Log(Exception ex, string message, params object[] args)
{
Debug.Print(message, args);
}
Le bloc finally
Le bloc finally
s'exécute chaque fois que l'exception soit lancée ou non. Une subtilité avec des expressions dans when
les filtres d'exception sont exécutés plus haut dans la pile avant d' entrer dans les blocs finally
internes. Cela peut entraîner des résultats et des comportements inattendus lorsque le code tente de modifier l'état global (comme l'utilisateur ou la culture du thread en cours) et le restaurer dans un bloc finally
.
Exemple: bloc finally
private static bool Flag = false;
static void Main(string[] args)
{
Console.WriteLine("Start");
try
{
SomeOperation();
}
catch (Exception) when (EvaluatesTo())
{
Console.WriteLine("Catch");
}
finally
{
Console.WriteLine("Outer Finally");
}
}
private static bool EvaluatesTo()
{
Console.WriteLine($"EvaluatesTo: {Flag}");
return true;
}
private static void SomeOperation()
{
try
{
Flag = true;
throw new Exception("Boom");
}
finally
{
Flag = false;
Console.WriteLine("Inner Finally");
}
}
Sortie produite:
Début
Evaluates à: True
Intérieur enfin
Capture
Extérieur
Dans l'exemple ci-dessus, si la méthode SomeOperation
ne souhaite pas "modifier" l'état global des modifications apportées aux clauses when
l'appelant, elle devrait également contenir un bloc catch
pour modifier l'état. Par exemple:
private static void SomeOperation()
{
try
{
Flag = true;
throw new Exception("Boom");
}
catch
{
Flag = false;
throw;
}
finally
{
Flag = false;
Console.WriteLine("Inner Finally");
}
}
Il est également courant de voir les classes d’aide IDisposable
tirer parti de la sémantique de l’ utilisation de blocs pour atteindre le même objectif, car IDisposable.Dispose
sera toujours appelé avant qu’une exception appelée dans un bloc using
commence à se former dans la pile.
Initialiseurs de propriétés automatiques
introduction
Les propriétés peuvent être initialisées avec l'opérateur =
après la fermeture }
. La classe de Coordinate
ci-dessous montre les options disponibles pour initialiser une propriété:
public class Coordinate
{
public int X { get; set; } = 34; // get or set auto-property with initializer
public int Y { get; } = 89; // read-only auto-property with initializer
}
Accesseurs avec une visibilité différente
Vous pouvez initialiser les propriétés automatiques ayant une visibilité différente sur leurs accesseurs. Voici un exemple avec un setter protégé:
public string Name { get; protected set; } = "Cheeze";
L'accesseur peut également être internal
, internal protected
ou private
.
Propriétés en lecture seule
Outre la flexibilité de la visibilité, vous pouvez également initialiser les propriétés automatiques en lecture seule. Voici un exemple:
public List<string> Ingredients { get; } =
new List<string> { "dough", "sauce", "cheese" };
Cet exemple montre également comment initialiser une propriété avec un type complexe. En outre, les propriétés automatiques ne peuvent pas être en écriture seule, ce qui empêche également l'initialisation en écriture seule.
Vieux style (pre C # 6.0)
Avant C # 6, cela nécessitait un code beaucoup plus détaillé. Nous utilisions une variable supplémentaire appelée propriété de sauvegarde pour que la propriété donne une valeur par défaut ou pour initialiser la propriété publique comme ci-dessous,
public class Coordinate
{
private int _x = 34;
public int X { get { return _x; } set { _x = value; } }
private readonly int _y = 89;
public int Y { get { return _y; } }
private readonly int _z;
public int Z { get { return _z; } }
public Coordinate()
{
_z = 42;
}
}
Remarque: Avant C # 6.0, vous pouviez toujours initialiser les propriétés implémentées en lecture et en écriture (propriétés avec un getter et un setter) à partir du constructeur, mais vous ne pouviez pas initialiser la propriété avec sa déclaration
Usage
Les initialiseurs doivent évaluer les expressions statiques, tout comme les initialiseurs de champs. Si vous devez référencer des membres non statiques, vous pouvez initialiser des propriétés dans des constructeurs comme auparavant ou utiliser des propriétés avec des expressions. Les expressions non statiques, comme celle ci-dessous (commentée), génèrent une erreur de compilation:
// public decimal X { get; set; } = InitMe(); // generates compiler error
decimal InitMe() { return 4m; }
Mais les méthodes statiques peuvent être utilisées pour initialiser les propriétés automatiques:
public class Rectangle
{
public double Length { get; set; } = 1;
public double Width { get; set; } = 1;
public double Area { get; set; } = CalculateArea(1, 1);
public static double CalculateArea(double length, double width)
{
return length * width;
}
}
Cette méthode peut également être appliquée aux propriétés ayant un niveau d'accesseur différent:
public short Type { get; private set; } = 15;
L'initialiseur de propriété automatique permet d'affecter des propriétés directement dans leur déclaration. Pour les propriétés en lecture seule, il prend en charge toutes les exigences requises pour que la propriété soit immuable. Prenons l'exemple de la classe FingerPrint
dans l'exemple suivant:
public class FingerPrint
{
public DateTime TimeStamp { get; } = DateTime.UtcNow;
public string User { get; } =
System.Security.Principal.WindowsPrincipal.Current.Identity.Name;
public string Process { get; } =
System.Diagnostics.Process.GetCurrentProcess().ProcessName;
}
Notes de mise en garde
Veillez à ne pas confondre les initialiseurs de propriétés automatiques et de champs avec les méthodes de corps d'expression similaires qui utilisent =>
par opposition à =
et les champs qui n'incluent pas { get; }
.
Par exemple, chacune des déclarations suivantes sont différentes.
public class UserGroupDto
{
// Read-only auto-property with initializer:
public ICollection<UserDto> Users1 { get; } = new HashSet<UserDto>();
// Read-write field with initializer:
public ICollection<UserDto> Users2 = new HashSet<UserDto>();
// Read-only auto-property with expression body:
public ICollection<UserDto> Users3 => new HashSet<UserDto>();
}
Missing { get; }
dans la déclaration de propriété résulte dans un champ public. Les utilisateurs en lecture seule en lecture seule Users1
et en lecture-écriture Users2
sont initialisés qu’une seule fois, mais un champ public permet de modifier l’instance de collecte en dehors de la classe, ce qui est généralement indésirable. Changer une propriété automatique en lecture seule avec un corps d'expression en propriété en lecture seule avec initialiseur nécessite non seulement de supprimer >
de =>
, mais d'ajouter { get; }
.
Le symbole différent ( =>
au lieu de =
) dans Users3
résultat que chaque accès à la propriété retourne une nouvelle instance du HashSet<UserDto>
qui, alors que C # valide (du point de vue du compilateur) est probablement le comportement souhaité utilisé pour un membre de la collection.
Le code ci-dessus est équivalent à:
public class UserGroupDto
{
// This is a property returning the same instance
// which was created when the UserGroupDto was instantiated.
private ICollection<UserDto> _users1 = new HashSet<UserDto>();
public ICollection<UserDto> Users1 { get { return _users1; } }
// This is a field returning the same instance
// which was created when the UserGroupDto was instantiated.
public virtual ICollection<UserDto> Users2 = new HashSet<UserDto>();
// This is a property which returns a new HashSet<UserDto> as
// an ICollection<UserDto> on each call to it.
public ICollection<UserDto> Users3 { get { return new HashSet<UserDto>(); } }
}
Initialiseurs d'index
Les initialiseurs d'index permettent de créer et d'initialiser des objets avec des index en même temps.
Cela rend l'initialisation des dictionnaires très facile:
var dict = new Dictionary<string, int>()
{
["foo"] = 34,
["bar"] = 42
};
Tout objet ayant un getter ou un setter indexé peut être utilisé avec cette syntaxe:
class Program
{
public class MyClassWithIndexer
{
public int this[string index]
{
set
{
Console.WriteLine($"Index: {index}, value: {value}");
}
}
}
public static void Main()
{
var x = new MyClassWithIndexer()
{
["foo"] = 34,
["bar"] = 42
};
Console.ReadKey();
}
}
Sortie:
Indice: toto, valeur: 34
Indice: bar, valeur: 42
Si la classe a plusieurs indexeurs, il est possible de les affecter tous dans un seul groupe d'instructions:
class Program
{
public class MyClassWithIndexer
{
public int this[string index]
{
set
{
Console.WriteLine($"Index: {index}, value: {value}");
}
}
public string this[int index]
{
set
{
Console.WriteLine($"Index: {index}, value: {value}");
}
}
}
public static void Main()
{
var x = new MyClassWithIndexer()
{
["foo"] = 34,
["bar"] = 42,
[10] = "Ten",
[42] = "Meaning of life"
};
}
}
Sortie:
Indice: toto, valeur: 34
Indice: bar, valeur: 42
Index: 10, valeur: Dix
Index: 42, valeur: Sens de la vie
Il convient de noter que l’accesseur de set
indexeurs peut se comporter différemment par rapport à une méthode Add
(utilisée dans les initialiseurs de collection).
Par exemple:
var d = new Dictionary<string, int>
{
["foo"] = 34,
["foo"] = 42,
}; // does not throw, second value overwrites the first one
contre:
var d = new Dictionary<string, int>
{
{ "foo", 34 },
{ "foo", 42 },
}; // run-time ArgumentException: An item with the same key has already been added.
Interpolation de chaîne
L'interpolation de chaînes permet au développeur de combiner des variables
et du texte pour former une chaîne.
Exemple de base
Deux variables int
sont créées: foo
et bar
.
int foo = 34;
int bar = 42;
string resultString = $"The foo is {foo}, and the bar is {bar}.";
Console.WriteLine(resultString);
Sortie :
Le foo est 34 et le bar 42.
Les accolades dans les chaînes peuvent toujours être utilisées, comme ceci:
var foo = 34;
var bar = 42;
// String interpolation notation (new style)
Console.WriteLine($"The foo is {{foo}}, and the bar is {{bar}}.");
Cela produit la sortie suivante:
Le foo est {foo}, et la barre est {bar}.
Utilisation de l'interpolation avec des littéraux de chaîne textuels
L'utilisation de @
avant la chaîne entraînera l'interprétation de la chaîne textuellement. Par exemple, les caractères Unicode ou les sauts de ligne resteront exactement tels qu'ils ont été saisis. Cependant, cela n'affectera pas les expressions d'une chaîne interpolée, comme illustré dans l'exemple suivant:
Console.WriteLine($@"In case it wasn't clear:
\u00B9
The foo
is {foo},
and the bar
is {bar}.");
Sortie: Au cas où ce ne serait pas clair:
\ u00B9
Le foo
est 34,
et le bar
est 42.
Expressions
Avec l'interpolation de chaîne, les expressions entre accolades {}
peuvent également être évaluées. Le résultat sera inséré à l'emplacement correspondant dans la chaîne. Par exemple, pour calculer le maximum de foo
et de bar
et l'insérer, utilisez Math.Max
dans les accolades:
Console.WriteLine($"And the greater one is: { Math.Max(foo, bar) }");
Sortie:
Et le plus grand est: 42
Remarque: Tout espace de début ou de fin (espace, tabulation et CRLF / newline compris) entre l'accolade et l'expression est complètement ignoré et n'est pas inclus dans la sortie.
Comme autre exemple, les variables peuvent être mises en forme en tant que devise:
Console.WriteLine($"Foo formatted as a currency to 4 decimal places: {foo:c4}");
Sortie:
Foo au format 4 décimales: $ 34.0000
Ou ils peuvent être formatés en dates:
Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");
Sortie:
Nous sommes aujourd'hui: lundi 20 juillet 2015
Les instructions avec un opérateur conditionnel (ternaire) peuvent également être évaluées dans l'interpolation. Cependant, ceux-ci doivent être mis entre parenthèses, car les deux points sont utilisés pour indiquer le formatage comme indiqué ci-dessus:
Console.WriteLine($"{(foo > bar ? "Foo is larger than bar!" : "Bar is larger than foo!")}");
Sortie:
Le bar est plus grand que le foo!
Les expressions conditionnelles et les spécificateurs de format peuvent être mélangés:
Console.WriteLine($"Environment: {(Environment.Is64BitProcess ? 64 : 32):00'-bit'} process");
Sortie:
Environnement: processus 32 bits
Séquences d'échappement
Les barres obliques inverses ( \
) et les guillemets ( "
) fonctionnent exactement de la même manière dans les chaînes interpolées que dans les chaînes non interpolées, à la fois pour les chaînes littérales textuelles et non textuelles:
Console.WriteLine($"Foo is: {foo}. In a non-verbatim string, we need to escape \" and \\ with backslashes.");
Console.WriteLine($@"Foo is: {foo}. In a verbatim string, we need to escape "" with an extra quote, but we don't need to escape \");
Sortie:
Foo a 34 ans. Dans une chaîne non verbatim, nous devons nous échapper "et \ avec des barres obliques inverses.
Foo a 34 ans. Dans une chaîne verbatim, nous devons nous échapper "avec une citation supplémentaire, mais nous n'avons pas besoin de nous échapper \
Pour inclure une accolade {
ou }
dans une chaîne interpolée, utilisez deux accolades {{
ou }}
:
$"{{foo}} is: {foo}"
Sortie:
{foo} est: 34
Type de chaîne formatée
Le type d'une expression d'interpolation de chaîne $"..."
n'est pas toujours une simple chaîne. Le compilateur décide quel type attribuer en fonction du contexte:
string s = $"hello, {name}";
System.FormattableString s = $"Hello, {name}";
System.IFormattable s = $"Hello, {name}";
C'est également l'ordre de préférence de type lorsque le compilateur doit choisir la méthode surchargée à appeler.
Un nouveau type , System.FormattableString
, représente une chaîne de format composite, avec les arguments à mettre en forme. Utilisez ceci pour écrire des applications qui gèrent spécifiquement les arguments d'interpolation:
public void AddLogItem(FormattableString formattableString)
{
foreach (var arg in formattableString.GetArguments())
{
// do something to interpolation argument 'arg'
}
// use the standard interpolation and the current culture info
// to get an ordinary String:
var formatted = formattableString.ToString();
// ...
}
Appelez la méthode ci-dessus avec:
AddLogItem($"The foo is {foo}, and the bar is {bar}.");
Par exemple, on pourrait choisir de ne pas encourir le coût des performances de la mise en forme de la chaîne si le niveau de journalisation était déjà utilisé pour filtrer l'élément de journal. Conversions implicites
Il existe des conversions de types implicites à partir d'une chaîne interpolée:
var s = $"Foo: {foo}";
System.IFormattable s = $"Foo: {foo}";
Vous pouvez également produire une variable IFormattable
qui vous permet de convertir la chaîne avec un contexte invariant: var s = $"Bar: {bar}";
System.FormattableString s = $"Bar: {bar}";
Méthodes de culture actuelles et invariantes
Si l'analyse de code est activée, les chaînes interpolées produiront toutes un avertissement CA1305 (spécifiez IFormatProvider
). Une méthode statique peut être utilisée pour appliquer la culture actuelle.
public static class Culture
{
public static string Current(FormattableString formattableString)
{
return formattableString?.ToString(CultureInfo.CurrentCulture);
}
public static string Invariant(FormattableString formattableString)
{
return formattableString?.ToString(CultureInfo.InvariantCulture);
}
}
Ensuite, pour produire une chaîne correcte pour la culture actuelle, utilisez simplement l'expression:
Culture.Current($"interpolated {typeof(string).Name} string.")
Culture.Invariant($"interpolated {typeof(string).Name} string.")
Remarque : Current
et Invariant
ne peuvent pas être créés en tant que méthodes d'extension car, par défaut, le compilateur attribue le type String
à l' expression de chaîne interpolée , ce qui empêche la compilation du code suivant: $"interpolated {typeof(string).Name} string.".Current();
FormattableString
classe FormattableString
contient déjà la méthode Invariant()
, de sorte que le moyen le plus simple de passer à la culture invariante est d' using static
:
using static System.FormattableString;
string invariant = Invariant($"Now = {DateTime.Now}"); string current = $"Now = {DateTime.Now}";
Dans les coulisses
Les chaînes interpolées ne sont qu'un sucre syntaxique pour String.Format()
. Le compilateur ( Roslyn ) le transformera en String.Format
en coulisse:
var text = $"Hello {name + lastName}";
Ce qui précède sera converti en quelque chose comme ceci:
string text = string.Format("Hello {0}", new object[] {
name + lastName
});
Interpolation de chaînes et Linq
Il est possible d'utiliser des chaînes interpolées dans les instructions Linq pour augmenter la lisibilité.
var fooBar = (from DataRow x in fooBarTable.Rows
select string.Format("{0}{1}", x["foo"], x["bar"])).ToList();
Peut être réécrit comme:
var fooBar = (from DataRow x in fooBarTable.Rows
select $"{x["foo"]}{x["bar"]}").ToList();
Cordes interpolées réutilisables
Avec string.Format
, vous pouvez créer des chaînes de format réutilisables:
public const string ErrorFormat = "Exception caught:\r\n{0}";
// ...
Logger.Log(string.Format(ErrorFormat, ex));
Les chaînes interpolées, cependant, ne seront pas compilées avec les espaces réservés faisant référence à des variables inexistantes. Les éléments suivants ne seront pas compilés:
public const string ErrorFormat = $"Exception caught:\r\n{error}";
// CS0103: The name 'error' does not exist in the current context
Au lieu de cela, créez un Func<>
qui consomme des variables et renvoie une String
:
public static Func<Exception, string> FormatError =
error => $"Exception caught:\r\n{error}";
// ...
Logger.Log(FormatError(ex));
Interpolation et localisation de chaînes
Si vous localisez votre application, vous pouvez vous demander s'il est possible d'utiliser l'interpolation de chaînes avec la localisation. En effet, il serait agréable d'avoir la possibilité de stocker dans des fichiers ressources String
s comme:
"My name is {name} {middlename} {surname}"
au lieu de beaucoup moins lisible: "My name is {0} {1} {2}"
String
processus d'interpolation de String
se produit au moment de la compilation , contrairement à la chaîne de formatage avec string.Format
qui se produit au moment de l'exécution . Les expressions dans une chaîne interpolée doivent référencer des noms dans le contexte actuel et doivent être stockées dans des fichiers de ressources. Cela signifie que si vous voulez utiliser la localisation, vous devez le faire comme:
var FirstName = "John";
// method using different resource file "strings"
// for French ("strings.fr.resx"), German ("strings.de.resx"),
// and English ("strings.en.resx")
void ShowMyNameLocalized(string name, string middlename = "", string surname = "")
{
// get localized string
var localizedMyNameIs = Properties.strings.Hello;
// insert spaces where necessary
name = (string.IsNullOrWhiteSpace(name) ? "" : name + " ");
middlename = (string.IsNullOrWhiteSpace(middlename) ? "" : middlename + " ");
surname = (string.IsNullOrWhiteSpace(surname) ? "" : surname + " ");
// display it
Console.WriteLine($"{localizedMyNameIs} {name}{middlename}{surname}".Trim());
}
// switch to French and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
ShowMyNameLocalized(FirstName);
// switch to German and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
ShowMyNameLocalized(FirstName);
// switch to US English and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
ShowMyNameLocalized(FirstName);
Si les chaînes de ressources des langages utilisés ci-dessus sont correctement stockées dans les fichiers de ressources individuels, vous devez obtenir la sortie suivante:
Bonjour, mon nom est John
Bonjour, nommez-vous John
Bonjour je m'appelle John
Notez que cela implique que le nom suit la chaîne localisée dans toutes les langues. Si ce n'est pas le cas, vous devez ajouter des espaces réservés aux chaînes de ressources et modifier la fonction ci-dessus ou vous devez interroger les informations de culture dans la fonction et fournir une instruction de casse contenant les différents cas. Pour plus de détails sur les fichiers de ressources, consultez Comment utiliser la localisation en C # .
Il est recommandé d'utiliser un langage de secours par défaut que la plupart des gens comprendront, au cas où une traduction ne serait pas disponible. Je suggère d'utiliser l'anglais comme langue de secours par défaut.
Interpolation récursive
Bien que ce ne soit pas très utile, il est permis d'utiliser une string
interpolée récursivement à l'intérieur des accolades d'un autre:
Console.WriteLine($"String has {$"My class is called {nameof(MyClass)}.".Length} chars:");
Console.WriteLine($"My class is called {nameof(MyClass)}.");
Sortie:
La chaîne a 27 caractères:
Ma classe s'appelle MyClass.
Attendez dans la prise et enfin
Il est possible d'utiliser une expression en await
pour appliquer un opérateur en attente à Tasks ou Task (Of TResult) dans les blocs catch
et finally
dans C # 6.
Il n'était pas possible d'utiliser l'expression await
dans les blocs catch
et finally
dans les versions antérieures en raison des limitations du compilateur. C # 6 rend l'attente des tâches asynchrones beaucoup plus facile en permettant l'expression d' await
.
try
{
//since C#5
await service.InitializeAsync();
}
catch (Exception e)
{
//since C#6
await logger.LogAsync(e);
}
finally
{
//since C#6
await service.CloseAsync();
}
Il était nécessaire en C # 5 d'utiliser un bool
ou de déclarer une Exception
dehors du try try pour effectuer des opérations asynchrones. Cette méthode est illustrée dans l'exemple suivant:
bool error = false;
Exception ex = null;
try
{
// Since C#5
await service.InitializeAsync();
}
catch (Exception e)
{
// Declare bool or place exception inside variable
error = true;
ex = e;
}
// If you don't use the exception
if (error)
{
// Handle async task
}
// If want to use information from the exception
if (ex != null)
{
await logger.LogAsync(e);
}
// Close the service, since this isn't possible in the finally
await service.CloseAsync();
Propagation nulle
Le ?.
L'opérateur et l'opérateur ?[...]
sont appelés l' opérateur null-conditionnel . Il est également parfois appelé par d'autres noms tels que l' opérateur de navigation sécurisé .
Ceci est utile, car si le .
L'opérateur (accesseur membre) est appliqué à une expression dont la valeur est null
, le programme lancera une NullReferenceException
. Si le développeur utilise plutôt le ?.
(null-conditionnel) opérateur, l'expression évaluera à null au lieu de lancer une exception.
Notez que si le ?.
l'opérateur est utilisé et l'expression est non nulle, ?.
et .
sont équivalents.
Les bases
var teacherName = classroom.GetTeacher().Name;
// throws NullReferenceException if GetTeacher() returns null
Si la classroom
n'a pas d'enseignant, GetTeacher()
peut renvoyer null
. Lorsqu'il est null
et que la propriété Name
est NullReferenceException
, une NullReferenceException
sera lancée.
Si nous modifions cette déclaration pour utiliser le ?.
la syntaxe, le résultat de l’expression entière sera null
:
var teacherName = classroom.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null
Par la suite, si la classroom
pouvait également être null
, nous pourrions également écrire cette déclaration comme suit:
var teacherName = classroom?.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null OR classroom is null
Voici un exemple de mise en court-circuit: Lorsqu'une opération d'accès conditionnel utilisant l'opérateur null-conditionnel a la valeur null, l'expression entière est évaluée immédiatement à null, sans traitement du reste de la chaîne.
Lorsque le membre terminal d'une expression contenant l'opérateur null-conditionnel est d'un type valeur, l'expression est évaluée à un Nullable<T>
de ce type et ne peut donc pas être utilisée comme remplacement direct de l'expression sans ?.
.
bool hasCertification = classroom.GetTeacher().HasCertification;
// compiles without error but may throw a NullReferenceException at runtime
bool hasCertification = classroom?.GetTeacher()?.HasCertification;
// compile time error: implicit conversion from bool? to bool not allowed
bool? hasCertification = classroom?.GetTeacher()?.HasCertification;
// works just fine, hasCertification will be null if any part of the chain is null
bool hasCertification = classroom?.GetTeacher()?.HasCertification.GetValueOrDefault();
// must extract value from nullable to assign to a value type variable
Utiliser avec l'opérateur Null-Coalescing (??)
Vous pouvez combiner l'opérateur null-conditionnel avec l'opérateur Null-coalescing ( ??
) pour renvoyer une valeur par défaut si l'expression est résolue à null
. En utilisant notre exemple ci-dessus:
var teacherName = classroom?.GetTeacher()?.Name ?? "No Name";
// teacherName will be "No Name" when GetTeacher()
// returns null OR classroom is null OR Name is null
Utiliser avec des indexeurs
L'opérateur null-conditionnel peut être utilisé avec les indexeurs :
var firstStudentName = classroom?.Students?[0]?.Name;
Dans l'exemple ci-dessus:
- Le premier
?.
s'assure que laclassroom
n'est pasnull
. - La seconde
?
s'assure que toute la collectionStudents
n'est pasnull
. - Le troisième
?.
après l'indexeur s'assure que l'indexeur[0]
n'a pas renvoyé d'objetnull
. Il convient de noter que cette opération peut toujours lancer uneIndexOutOfRangeException
.
Utiliser avec fonctions vides
L'opérateur Null-Conditionnel peut également être utilisé avec les fonctions void
. Cependant, dans ce cas, l'instruction ne sera pas évaluée à null
. Cela empêchera simplement une NullReferenceException
.
List<string> list = null;
list?.Add("hi"); // Does not evaluate to null
Utiliser avec l'invocation d'événement
En supposant la définition d'événement suivante:
private event EventArgs OnCompleted;
Lors de l'appel d'un événement, traditionnellement, il est recommandé de vérifier si l'événement est null
si aucun abonné n'est présent:
var handler = OnCompleted;
if (handler != null)
{
handler(EventArgs.Empty);
}
Comme l'opérateur null-conditionnel a été introduit, l'invocation peut être réduite à une seule ligne:
OnCompleted?.Invoke(EventArgs.Empty);
Limites
L'opérateur Null-conditionnel produit rvalue, pas lvalue, c'est-à-dire qu'il ne peut pas être utilisé pour l'affectation de propriété, l'abonnement à un événement, etc. Par exemple, le code suivant ne fonctionnera pas:
// Error: The left-hand side of an assignment must be a variable, property or indexer
Process.GetProcessById(1337)?.EnableRaisingEvents = true;
// Error: The event can only appear on the left hand side of += or -=
Process.GetProcessById(1337)?.Exited += OnProcessExited;
Gotchas
Notez que:
int? nameLength = person?.Name.Length; // safe if 'person' is null
n'est pas la même chose que:
int? nameLength = (person?.Name).Length; // avoid this
parce que le premier correspond à:
int? nameLength = person != null ? (int?)person.Name.Length : null;
et ce dernier correspond à:
int? nameLength = (person != null ? person.Name : null).Length;
Malgré l'opérateur ternaire ?:
Est utilisé ici pour expliquer la différence entre deux cas, ces opérateurs ne sont pas équivalents. Cela peut être facilement démontré avec l'exemple suivant:
void Main()
{
var foo = new Foo();
Console.WriteLine("Null propagation");
Console.WriteLine(foo.Bar?.Length);
Console.WriteLine("Ternary");
Console.WriteLine(foo.Bar != null ? foo.Bar.Length : (int?)null);
}
class Foo
{
public string Bar
{
get
{
Console.WriteLine("I was read");
return string.Empty;
}
}
}
Quelles sorties:
Propagation nulle
J'ai été lu
0
Ternaire
J'ai été lu
J'ai été lu
0
Pour éviter les invocations multiples, l'équivalent serait:
var interimResult = foo.Bar;
Console.WriteLine(interimResult != null ? interimResult.Length : (int?)null);
Et cette différence explique en partie pourquoi l'opérateur de propagation nul n'est pas encore pris en charge dans les arbres d'expression.
En utilisant le type statique
La directive using static [Namespace.Type]
permet d'importer des membres statiques de types et de valeurs d'énumération. Les méthodes d'extension sont importées en tant que méthodes d'extension (à partir d'un seul type) et non dans la portée de niveau supérieur.
using static System.Console;
using static System.ConsoleColor;
using static System.Math;
class Program
{
static void Main()
{
BackgroundColor = DarkBlue;
WriteLine(Sqrt(2));
}
}
using System;
class Program
{
static void Main()
{
Console.BackgroundColor = ConsoleColor.DarkBlue;
Console.WriteLine(Math.Sqrt(2));
}
}
Amélioration de la résolution de la surcharge
L'extrait de code suivant montre un exemple de passage d'un groupe de méthodes (par opposition à un groupe lambda) lorsqu'un délégué est attendu. La résolution de la surcharge va maintenant résoudre ce problème au lieu de générer une erreur de surcharge ambiguë en raison de la capacité de C # 6 à vérifier le type de retour de la méthode qui a été transmise.
using System;
public class Program
{
public static void Main()
{
Overloaded(DoSomething);
}
static void Overloaded(Action action)
{
Console.WriteLine("overload with action called");
}
static void Overloaded(Func<int> function)
{
Console.WriteLine("overload with Func<int> called");
}
static int DoSomething()
{
Console.WriteLine(0);
return 0;
}
}
Résultats:
Erreur
erreur CS0121: l'appel est ambigu entre les méthodes ou propriétés suivantes: 'Program.Overloaded (System.Action)' et 'Program.Overloaded (System.Func)'
C # 6 peut également gérer le cas suivant de correspondance exacte pour les expressions lambda, ce qui aurait entraîné une erreur dans C # 5 .
using System;
class Program
{
static void Foo(Func<Func<long>> func) {}
static void Foo(Func<Func<int>> func) {}
static void Main()
{
Foo(() => () => 7);
}
}
Changements mineurs et corrections de bugs
Les parenthèses sont désormais interdites autour des paramètres nommés. Les éléments suivants sont compilés en C # 5, mais pas en C # 6
Console.WriteLine((value: 23));
Les opérandes de is
et as
ne sont plus autorisés à être des groupes de méthodes. Les éléments suivants sont compilés en C # 5, mais pas en C # 6
var result = "".Any is byte;
Le compilateur natif a autorisé cela (bien qu'il ait montré un avertissement), et n'a même pas vérifié la compatibilité des méthodes d'extension, autorisant des choses délirantes comme
1.Any is string
ouIDisposable.Dispose is object
.
Voir cette référence pour les mises à jour sur les modifications.
Utilisation d'une méthode d'extension pour l'initialisation de la collection
La syntaxe d'initialisation de la collection peut être utilisée lors de l'instanciation de toute classe qui implémente IEnumerable
et possède une méthode nommée Add
qui prend un seul paramètre.
Dans les versions précédentes, cette méthode Add
devait être une méthode d' instance sur la classe en cours d'initialisation. En C # 6, cela peut aussi être une méthode d'extension.
public class CollectionWithAdd : IEnumerable
{
public void Add<T>(T item)
{
Console.WriteLine("Item added with instance add method: " + item);
}
public IEnumerator GetEnumerator()
{
// Some implementation here
}
}
public class CollectionWithoutAdd : IEnumerable
{
public IEnumerator GetEnumerator()
{
// Some implementation here
}
}
public static class Extensions
{
public static void Add<T>(this CollectionWithoutAdd collection, T item)
{
Console.WriteLine("Item added with extension add method: " + item);
}
}
public class Program
{
public static void Main()
{
var collection1 = new CollectionWithAdd{1,2,3}; // Valid in all C# versions
var collection2 = new CollectionWithoutAdd{4,5,6}; // Valid only since C# 6
}
}
Cela va sortir:
Elément ajouté avec la méthode d'instance ajouter: 1
Elément ajouté avec la méthode d'instance ajouter: 2
Elément ajouté avec la méthode d'instance ajouter: 3
Article ajouté avec l'extension add method: 4
Élément ajouté avec l'extension add method: 5
Article ajouté avec l'extension add method: 6
Désactiver les améliorations des avertissements
Dans C # 5.0 et versions antérieures, le développeur ne pouvait supprimer que les avertissements par numéro. Avec l'introduction des analyseurs Roslyn, C # doit pouvoir désactiver les avertissements émis par des bibliothèques spécifiques. Avec C # 6.0, la directive pragma peut supprimer les avertissements par nom.
Avant:
#pragma warning disable 0501
C # 6.0:
#pragma warning disable CS0501