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:

6,0

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.

Voir la démo


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 clause when , la clause Exception from the when est ignorée et traitée comme false . Cette approche permet aux développeurs d'écrire when 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);
}

Voir la démo

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;
}

Voir la démo

L'approche courante dans les versions précédentes de C # consistait à enregistrer et à renvoyer l'exception.

6,0
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);
}

Voir la démo


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

Voir la démo

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é:

6,0
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,

6,0
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

Voir la démo


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;
}

Voir la démo


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

Voir la démo

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.

Voir la démo

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.

Voir la démo


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.

Voir la démo

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

Voir la démo

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

Voir la démo

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!

Voir la démo

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

Voir la démo


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

Voir la démo

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

Voir la démo

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

Voir la démo

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 la classroom n'est pas null .
  • La seconde ? s'assure que toute la collection Students n'est pas null .
  • Le troisième ?. après l'indexeur s'assure que l'indexeur [0] n'a pas renvoyé d'objet null . Il convient de noter que cette opération peut toujours lancer une IndexOutOfRangeException .

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

Voir la démo

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.

6,0
using static System.Console;
using static System.ConsoleColor;
using static System.Math;

class Program
{
    static void Main()
    {
        BackgroundColor = DarkBlue;
        WriteLine(Sqrt(2));
    }
}

Live Demo Fiddle

6,0
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:

6,0

Sortie

surcharge avec Func <int> appelée

Voir la démo

5.0

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

5.0
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

5.0
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 ou IDisposable.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


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow