Éléments ignorés-notions de base du langage C#

À compter de C# 7,0, C# prend en charge les éléments ignorés, qui sont des variables d’espace réservé qui sont intentionnellement inutilisées dans le code de l’application. Les éléments ignorés sont équivalents aux variables non affectées ; ils n’ont pas de valeur. Un abandon communique l’intention au compilateur et d’autres personnes qui lisent votre code : vous avez prévu d’ignorer le résultat d’une expression. Vous pouvez ignorer le résultat d’une expression, d’un ou de plusieurs membres d’une expression de tuple, d’un out paramètre d’une méthode ou de la cible d’une expression de critères spéciaux.

Étant donné qu’il n’y a qu’une seule variable à rejet, cette variable ne peut même pas être allouée au stockage. Les éléments ignorés peuvent réduire les allocations de mémoire. Les éléments ignorés rendent l’objectif de votre code clair. Ils améliorent la lisibilité et la maintenabilité.

Vous indiquez qu’une variable est un élément ignoré en lui affectant comme nom le trait de soulignement (_). Par exemple, l’appel de méthode suivant retourne un tuple dans lequel les première et deuxième valeurs sont ignorées. area est une variable déclarée précédemment définie sur le troisième composant retourné par GetCityInformation :

(_, _, area) = city.GetCityInformation(cityName);

À compter de C# 9,0, vous pouvez utiliser des éléments ignorés pour spécifier les paramètres d’entrée inutilisés d’une expression lambda. Pour plus d’informations, consultez la section paramètres d’entrée d’une expression lambda de l’article expressions lambda .

Lorsque _ est un ignore valide, toute tentative de récupération de sa valeur ou son utilisation dans une opération d’assignation génère l’erreur de compilateur CS0301, « le nom' _ 'n’existe pas dans le contexte actuel ». Cette erreur est due _ au fait qu’une valeur n’est pas affectée à et peut même ne pas être affectée à un emplacement de stockage. S’il s’agit d’une variable réelle, vous ne pouviez pas ignorer plus d’une valeur, comme le faisait l’exemple précédent.

Déconstruction de tuple et d’objet

Les éléments ignorés sont utiles lors de l’utilisation de tuples lorsque votre code d’application utilise des éléments tuples, mais en ignore d’autres. Par exemple, la QueryCityDataForYears méthode suivante retourne un tuple avec le nom d’une ville, sa zone, une année, la population de la ville pour cette année, une seconde année et la population de la ville pour cette seconde année. L’exemple montre la différence de population entre ces deux années. Parmi les données disponibles dans le tuple, nous ne sommes pas intéressés par la région de la ville, et nous connaissons le nom de la ville et les deux dates au moment du design. Par conséquent, nous sommes intéressés seulement par les deux valeurs de la population stockées dans le tuple et nous pouvons gérer ses valeurs restantes comme éléments ignorés.

var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");

static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
{
    int population1 = 0, population2 = 0;
    double area = 0;

    if (name == "New York City")
    {
        area = 468.48;
        if (year1 == 1960)
        {
            population1 = 7781984;
        }
        if (year2 == 2010)
        {
            population2 = 8175133;
        }
        return (name, area, year1, population1, year2, population2);
    }

    return ("", 0, 0, 0, 0, 0);
}
// The example displays the following output:
//      Population change, 1960 to 2010: 393,149

Pour plus d’informations sur la déconstruction de tuples avec des éléments ignorés, consultez Déconstruction de tuples et d’autres types.

La méthode Deconstruct d’une classe, d’un struct ou d’une interface vous permet aussi de récupérer et de déconstruire un ensemble spécifique de données d’un objet. Vous pouvez utiliser des éléments ignorés lorsque vous êtes intéressé par l’utilisation d’un seul sous-ensemble des valeurs déconstruites. L’exemple suivant déconstruit un objet Person en quatre chaînes (le prénom, le nom, la ville et l’État), mais ignore le nom et l’État.

using System;

namespace Discards
{
    public class Person
    {
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public string City { get; set; }
        public string State { get; set; }

        public Person(string fname, string mname, string lname,
                      string cityName, string stateName)
        {
            FirstName = fname;
            MiddleName = mname;
            LastName = lname;
            City = cityName;
            State = stateName;
        }

        // Return the first and last name.
        public void Deconstruct(out string fname, out string lname)
        {
            fname = FirstName;
            lname = LastName;
        }

        public void Deconstruct(out string fname, out string mname, out string lname)
        {
            fname = FirstName;
            mname = MiddleName;
            lname = LastName;
        }

        public void Deconstruct(out string fname, out string lname,
                                out string city, out string state)
        {
            fname = FirstName;
            lname = LastName;
            city = City;
            state = State;
        }
    }
    class Example
    {
        public static void Main()
        {
            var p = new Person("John", "Quincy", "Adams", "Boston", "MA");

            // Deconstruct the person object.
            var (fName, _, city, _) = p;
            Console.WriteLine($"Hello {fName} of {city}!");
            // The example displays the following output:
            //      Hello John of Boston!
        }
    }
}

Pour plus d’informations sur la déconstruction de types définis par l’utilisateur avec des éléments ignorés, consultez Déconstruction de tuples et d’autres types.

Utilisation des critères spéciaux avec switch

Le modèle de rejet peut être utilisé dans les critères spéciaux avec l' expression de commutateur. Chaque expression, y compris null , correspond toujours au modèle d’abandon.

L’exemple suivant définit une ProvidesFormatInfo méthode qui utilise une switch expression pour déterminer si un objet fournit une IFormatProvider implémentation et teste si l’objet a la valeur null . Il utilise également le modèle d’élément ignoré pour gérer les objets non null de n’importe quel autre type.

object[] objects = { CultureInfo.CurrentCulture,
                   CultureInfo.CurrentCulture.DateTimeFormat,
                   CultureInfo.CurrentCulture.NumberFormat,
                   new ArgumentException(), null };
foreach (var obj in objects)
    ProvidesFormatInfo(obj);

static void ProvidesFormatInfo(object obj) =>
    Console.WriteLine(obj switch
    {
        IFormatProvider fmt => $"{fmt.GetType()} object",
        null => "A null object reference: Its use could result in a NullReferenceException",
        _ => "Some object type without format information"
    });
// The example displays the following output:
//    System.Globalization.CultureInfo object
//    System.Globalization.DateTimeFormatInfo object
//    System.Globalization.NumberFormatInfo object
//    Some object type without format information
//    A null object reference: Its use could result in a NullReferenceException

Appels à des méthodes avec des out paramètres

Quand vous appelez la méthode Deconstruct pour déconstruire un type défini par l’utilisateur (une instance d’une classe, d’une structure ou d’une interface), vous pouvez ignorer les valeurs d’arguments out individuels. Toutefois, vous pouvez également ignorer la valeur des out arguments lors de l’appel d’une méthode avec un out paramètre.

L’exemple suivant appelle la méthode DateTime.TryParse (String, out DateTime) pour déterminer si la représentation sous forme de chaîne d’une date est valide dans la culture actuelle. Comme l’exemple concerne ici uniquement la validation de la chaîne de date et pas son analyse pour extraire la date, l’argument out de la méthode est un élément ignoré.

string[] dateStrings = {"05/01/2018 14:57:32.8", "2018-05-01 14:57:32.8",
                      "2018-05-01T14:57:32.8375298-04:00", "5/01/2018",
                      "5/01/2018 14:57:32.80 -07:00",
                      "1 May 2018 2:57:32.8 PM", "16-05-2018 1:00:32 PM",
                      "Fri, 15 May 2018 20:10:57 GMT" };
foreach (string dateString in dateStrings)
{
    if (DateTime.TryParse(dateString, out _))
        Console.WriteLine($"'{dateString}': valid");
    else
        Console.WriteLine($"'{dateString}': invalid");
}
// The example displays output like the following:
//       '05/01/2018 14:57:32.8': valid
//       '2018-05-01 14:57:32.8': valid
//       '2018-05-01T14:57:32.8375298-04:00': valid
//       '5/01/2018': valid
//       '5/01/2018 14:57:32.80 -07:00': valid
//       '1 May 2018 2:57:32.8 PM': valid
//       '16-05-2018 1:00:32 PM': invalid
//       'Fri, 15 May 2018 20:10:57 GMT': invalid

Élément ignoré autonome

Vous pouvez utiliser un élément ignoré autonome pour indiquer une variable que vous choisissez d’ignorer. Une utilisation classique consiste à utiliser une assignation pour garantir qu’un argument n’a pas la valeur null. Le code suivant utilise un ignore pour forcer une assignation. La partie droite de l’assignation utilise l' opérateur de fusion Null pour lever une System.ArgumentNullException lorsque l’argument a la valeur null . Le code n’a pas besoin du résultat de l’assignation. il est donc ignoré. L’expression force une vérification de valeur null. L’abandon clarifie votre intention : le résultat de l’assignation n’est pas nécessaire ou utilisé.

public static void Method(string arg)
{
    _ = arg ?? throw new ArgumentNullException(paramName: nameof(arg), message: "arg can't be null");

    // Do work with arg.
}

L’exemple suivant utilise un élément ignoré autonome pour ignorer l’objet Task retourné par une opération asynchrone. L’assignation de la tâche a pour effet de supprimer l’exception levée par l’opération, car elle est sur le paragraphe de se terminer. L’objectif est clair : vous voulez ignorer le Task et ignorer les erreurs générées à partir de cette opération asynchrone.

private static async Task ExecuteAsyncMethods()
{
    Console.WriteLine("About to launch a task...");
    _ = Task.Run(() =>
    {
        var iterations = 0;
        for (int ctr = 0; ctr < int.MaxValue; ctr++)
            iterations++;
        Console.WriteLine("Completed looping operation...");
        throw new InvalidOperationException();
    });
    await Task.Delay(5000);
    Console.WriteLine("Exiting after 5 second delay");
}
// The example displays output like the following:
//       About to launch a task...
//       Completed looping operation...
//       Exiting after 5 second delay

Sans affecter la tâche à un ignore, le code suivant génère un avertissement du compilateur :

private static async Task ExecuteAsyncMethods()
{
    Console.WriteLine("About to launch a task...");
    // CS4014: Because this call is not awaited, execution of the current method continues before the call is completed.
    // Consider applying the 'await' operator to the result of the call.
    Task.Run(() =>
    {
        var iterations = 0;
        for (int ctr = 0; ctr < int.MaxValue; ctr++)
            iterations++;
        Console.WriteLine("Completed looping operation...");
        throw new InvalidOperationException();
    });
    await Task.Delay(5000);
    Console.WriteLine("Exiting after 5 second delay");

Notes

Si vous exécutez l’un des deux exemples précédents à l’aide d’un débogueur, le débogueur arrêtera le programme quand l’exception sera levée. Si le débogueur n’est pas attaché, l’exception est ignorée silencieusement dans les deux cas.

_ est également un identificateur valide. Quand il est utilisé en dehors d’un contexte pris en charge, _ est traité non pas comme élément ignoré, mais comme variable valide. Si un identificateur nommé _ est déjà dans l’étendue, l’utilisation de _ comme élément ignoré autonome peut provoquer :

  • Une modification accidentelle de la valeur de la variable _ dans l’étendue en lui affectant la valeur de l’élément ignoré prévu. Par exemple :
    private static void ShowValue(int _)
    {
       byte[] arr = { 0, 0, 1, 2 };
       _ = BitConverter.ToInt32(arr, 0);
       Console.WriteLine(_);
    }
     // The example displays the following output:
     //       33619968
    
  • Une erreur de compilateur pour violation de sécurité du type. Par exemple :
    private static bool RoundTrips(int _)
    {
       string value = _.ToString();
       int newValue = 0;
       _ = Int32.TryParse(value, out newValue);
       return _ == newValue;
    }
    // The example displays the following compiler error:
    //      error CS0029: Cannot implicitly convert type 'bool' to 'int'
    
  • Erreur du compilateur CS0136 : « Impossible de déclarer une variable locale ou un paramètre nommé ‘_’ dans cette portée, car ce nom est utilisé dans une portée locale englobante pour définir une variable locale ou un paramètre. » Par exemple :
     public void DoSomething(int _)
    {
     var _ = GetValue(); // Error: cannot declare local _ when one is already in scope
    }
    // The example displays the following compiler error:
    // error CS0136:
    //       A local or parameter named '_' cannot be declared in this scope
    //       because that name is used in an enclosing local scope
    //       to define a local or parameter
    

Voir aussi