Éléments ignorés - Notions de base C#

Les éléments ignorés sont des variables d’espace réservé, qui sont inutilisées de façon intentionnelle dans le code d’une application. Les éléments ignorés sont équivalents à des variables non affectées ; ils n’ont pas de valeur. Un élément ignoré communique l’intention au compilateur et à d’autres personnes qui lisent votre code : vous avez l’intention d’ignorer le résultat d’une expression. Vous pouvez ignorer le résultat d’une expression, un ou plusieurs membres d’une expression tuple, un paramètre out vers une méthode ou la cible d’une expression de critères spéciaux.

Les éléments ignorés permettent de clarifier l’objectif de votre code. Un élément ignoré indique que notre code n’utilise jamais la variable. Ils améliorent sa lisibilité et sa facilité de maintenance.

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, où la première et la seconde valeurs sont des éléments ignorés. area est une variable précédemment déclarée définie sur le troisième composant retourné par GetCityInformation:

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

Vous pouvez utiliser les discards 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.

Quand _ constitue un discard valide, tout tentative de récupérer sa valeur ou de l’utiliser dans une opération d’affectation génère l’erreur de compilateur CS0103, « Le nom "_" n’existe pas dans le contexte actuel ». Cette erreur se produit parce qu’aucune valeur n’est affectée à _ et qu’il n’est même pas possible de lui affecter un emplacement de stockage. S’il s’agissait d’une variable réelle, vous ne pourriez pas ignorer plus d’une valeur, comme l’a fait l’exemple précédent.

Déconstruction de tuple et d’objet

Les éléments ignorés sont utiles pour travailler avec des tuples quand le code de votre application utilise certains éléments d’un tuple, mais ignore les autres. Par exemple, la méthode QueryCityDataForYears suivante retourne un tuple avec le nom d’une ville, sa région, 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 quand vous êtes intéressé seulement par un 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 d’élément ignoré peut être utilisé dans des critères spéciaux avec l’expression de commutateur. Chaque expression, y comprisnull, correspond toujours au modèle d’élément ignoré.

L’exemple suivant définit une méthode ProvidesFormatInfo qui utilise une expression switch pour déterminer si un objet fournit une implémentation de IFormatProvider et teste si l’objet est 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 paramètres out

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. Vous pouvez cependant aussi ignorer la valeur d’arguments out lors de l’appel de n’importe quelle méthode avec un paramètre out.

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. L’une des utilisations courantes consiste à utiliser une affectation pour s’assurer qu’un argument n’est pas null. Le code suivant utilise un élément ignoré pour forcer une affectation. Le côté droit de l’affectation utilise l’opérateur de fusion null pour lever un System.ArgumentNullException lorsque l’argument est null. Le code n’a pas besoin du résultat de l’affectation, il est donc ignoré. L’expression force une vérification null. L’élément ignoré clarifie votre intention : le résultat de l’affectation n’est ni nécessaire ni 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. Attribuer la tâche a pour effet de supprimer l’exception que l’opération lève au moment où elle est sur le point de se terminer. Votre intention est claire : vous souhaitez ignorer le Task et ignorer toutes 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 élément ignoré, 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 échantillons précédents à l’aide d’un débogueur, le débogueur arrête le programme lorsque l’exception est levée. Sans débogueur joint, l’exception est ignorée en mode silencieux dans les deux cas.

_ est aussi 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