Partager via


Tuples et types anonymes

Les tuples fournissent une structure de données légère pour plusieurs membres dans une seule structure. Il s’agit du choix préféré par rapport aux types anonymes. Les tuples offrent de meilleures performances, supportent la déconstruction et offrent une syntaxe plus flexible.

Les types anonymes permettent d'encapsuler un ensemble de propriétés en lecture seule dans un unique objet sans avoir à définir explicitement un type. Le compilateur génère le nom du type et n’est pas disponible au niveau du code source. Le compilateur déduit le type de chaque propriété. Utilisez principalement des types anonymes lorsque vous avez besoin d’une prise en charge de l’arborescence d’expressions ou lorsque vous utilisez du code qui nécessite des types de référence.

Tuples et types anonymes

Les tuples et les types anonymes vous permettent de regrouper plusieurs valeurs sans définir de type nommé. Toutefois, les tuples ont une meilleure prise en charge du langage et sont compilés pour une structure de données plus efficace. Le tableau suivant résume les principales différences :

Caractéristique Types anonymes Tuples
Type Type de référence (class) Type valeur (struct)
Performance Allocation de tas Allocation de pile (meilleures performances)
Mutability Propriétés en lecture seule Champs mutables
Déconstruction Non prise en charge Soutenu
Arbres d'expression Soutenu Non prise en charge
Modificateur d’accès internal public
Noms de membres Obligatoire ou implicite Facultatif (avec des noms par défaut tels que Item1, Item2)

Quand utiliser des tuples

Utilisez des tuples quand :

  • Vous avez besoin de performances améliorées grâce à l'allocation de pile.
  • Vous souhaitez déconstructer des valeurs en variables distinctes.
  • Vous retournez plusieurs valeurs à partir d’une méthode.
  • Vous n’avez pas besoin de support des arbres d'expression.

L’exemple suivant montre comment les tuples fournissent des fonctionnalités similaires aux types anonymes avec une syntaxe plus propre :

// Tuple with named elements.
var tupleProduct = (Name: "Widget", Price: 19.99M);
Console.WriteLine($"Tuple: {tupleProduct.Name} costs ${tupleProduct.Price}");

// Equivalent example using anonymous types.
var anonymousProduct = new { Name = "Widget", Price = 19.99M };
Console.WriteLine($"Anonymous: {anonymousProduct.Name} costs ${anonymousProduct.Price}");

Déconstruction de tuple

Vous pouvez déconstructer un tuple en variables distinctes, ce qui offre un moyen pratique d’utiliser des éléments tuple individuels. C# prend en charge plusieurs façons de déconstructer des tuples :

static (string Name, int Age, string City) GetPersonInfo()
{
    return ("Alice", 30, "Seattle");
}
// Deconstruct using var for all variables
var (name, age, city) = GetPersonInfo();
Console.WriteLine($"{name} is {age} years old and lives in {city}");
// Output: Alice is 30 years old and lives in Seattle

// Deconstruct with explicit types
(string personName, int personAge, string personCity) = GetPersonInfo();
Console.WriteLine($"{personName}, {personAge}, {personCity}");

// Deconstruct into existing variables
string existingName;
int existingAge;
string existingCity;
(existingName, existingAge, existingCity) = GetPersonInfo();

// Deconstruct and discard unwanted values using the discard pattern (_)
var (name2, _, city2) = GetPersonInfo();
Console.WriteLine($"{name2} lives in {city2}");
// Output: Alice lives in Seattle

La déconstruction est utile dans les boucles et les scénarios de correspondance de modèles :

var people = new List<(string Name, int Age)>
{
    ("Bob", 25),
    ("Carol", 35),
    ("Dave", 40)
};

foreach (var (personName2, personAge2) in people)
{
    Console.WriteLine($"{personName2} is {personAge2} years old");
}

Tuples comme type de retour de méthode

Un tuple est couramment utilisé en tant que type de retour de fonction. Au lieu de définir out des paramètres, vous pouvez regrouper des résultats de méthode dans un tuple. Vous ne pouvez pas retourner un type anonyme à partir d’une méthode, car il n’a pas de nom et le type de retour ne peut pas être déclaré.

L’exemple suivant illustre l’utilisation de tuples avec des recherches de dictionnaire pour retourner des plages de configuration :

var configLookup = new Dictionary<int, (int Min, int Max)>()
{
    [2] = (4, 10),
    [4] = (10, 20),
    [6] = (0, 23)
};

if (configLookup.TryGetValue(4, out (int Min, int Max) range))
{
    Console.WriteLine($"Found range: min is {range.Min}, max is {range.Max}");
}
// Output: Found range: min is 10, max is 20

Ce modèle est utile lors de l’utilisation de méthodes qui doivent retourner à la fois un indicateur de réussite et plusieurs valeurs de résultat. Le tuple vous permet d’utiliser des champs nommés (Min et Max) au lieu de noms génériques comme Item1 et Item2, rendant le code plus lisible et auto-documentant.

Quand utiliser des types anonymes

Utilisez des types anonymes quand :

  • Vous travaillez avec des arborescences d’expressions (par exemple, dans certains fournisseurs Microsoft Language-Integrated Query (LINQ).
  • Vous avez besoin que l’objet soit un type de référence.

Le scénario le plus courant consiste à initialiser un type anonyme avec les propriétés d'un autre type. Dans l'exemple suivant, une classe existe qui porte le nom Product. La classe Product inclut les propriétés Color et Price, en plus d’autres propriétés qui ne vous intéressent pas :

class Product
{
    public string? Color { get; init; }
    public decimal Price { get; init; }
    public string? Name { get; init; }
    public string? Category { get; init; }
    public string? Size { get; init; }
}

La déclaration de type anonyme commence par l’opérateur new avec un initialiseur d’objet. La déclaration initialise un nouveau type qui utilise uniquement deux propriétés de Product. Les types anonymes sont généralement utilisés dans la select clause d’une expression de requête pour retourner une plus petite quantité de données. Pour plus d'informations sur les requêtes, voir LINQ en C#.

Si vous ne spécifiez pas de noms de membres dans le type anonyme, le compilateur donne aux membres de type anonyme le même nom que la propriété utilisée pour les initialiser. Vous fournissez un nom pour une propriété qui est initialisée avec une expression, comme indiqué dans l’exemple précédent.

Dans l'exemple suivant, les noms des propriétés du type anonyme sont Color et Price. Les instances sont des éléments de la products collection de Product types :

var productQuery =
    from prod in products
    select new { prod.Color, prod.Price };

foreach (var v in productQuery)
{
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

Initialiseurs de projection avec des types anonymes

Les types anonymes prennent en charge les initialiseurs de projection, ce qui vous permet d’utiliser des variables ou des paramètres locaux directement sans spécifier explicitement le nom du membre. Le compilateur déduit les noms de membres des noms de variables. L’exemple suivant illustre cette syntaxe simplifiée :

// Explicit member names.
var personExplicit = new { FirstName = "Kyle", LastName = "Mit" };

// Projection initializers (inferred member names).
var firstName = "Kyle";
var lastName = "Mit";
var personInferred = new { firstName, lastName };

// Both create equivalent anonymous types with the same property names.
Console.WriteLine($"Explicit: {personExplicit.FirstName} {personExplicit.LastName}");
Console.WriteLine($"Inferred: {personInferred.firstName} {personInferred.lastName}");

Cette syntaxe simplifiée est utile lors de la création de types anonymes avec de nombreuses propriétés :

var title = "Software Engineer";
var department = "Engineering";
var salary = 75000;

// Using projection initializers.
var employee = new { title, department, salary };

// Equivalent to explicit syntax:
// var employee = new { title = title, department = department, salary = salary };

Console.WriteLine($"Title: {employee.title}, Department: {employee.department}, Salary: {employee.salary}");

Le nom du membre n’est pas déduit dans les cas suivants :

  • Le nom de candidat duplique un autre membre de propriété dans le même type anonyme, explicite ou implicite.
  • Le nom du candidat n’est pas un identificateur valide (par exemple, il contient des espaces ou des caractères spéciaux).

Dans ces cas, vous devez spécifier explicitement le nom du membre.

Conseil

Vous pouvez utiliser la règle de style .NET IDE0037 pour indiquer si les noms de membres déduits ou explicites sont préférés.

Vous pouvez également définir un champ à l’aide d’un objet d’un autre type : classe, struct ou même un autre type anonyme. Pour ce faire, utilisez la variable qui contient cet objet. L'exemple suivant montre deux types anonymes qui utilisent des types instanciés déjà définis par l'utilisateur. Dans les deux cas, le product champ dans les types shipment anonymes et shipmentWithBonus est de type Product et contient les valeurs par défaut de chaque champ. Le bonus champ est d’un type anonyme créé par le compilateur.

var product = new Product();
var bonus = new { note = "You won!" };
var shipment = new { address = "Nowhere St.", product };
var shipmentWithBonus = new { address = "Somewhere St.", product, bonus };

Quand vous utilisez un type anonyme pour initialiser une variable, vous déclarez la variable en tant que variable locale implicitement typée en utilisant var. Vous ne pouvez pas spécifier le nom de type dans la déclaration de variable, car seul le compilateur a accès au nom sous-jacent du type anonyme. Pour plus d’informations sur var, consultez Variables locales implicitement typées.

Vous pouvez créer un tableau d'éléments typés anonymement en associant une variable locale implicitement typée et un tableau typé implicitement, comme indiqué dans l'exemple suivant.

var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};

Les types anonymes sont class des types qui dérivent directement de object, et vous ne pouvez pas les convertir en n’importe quel type sauf object. Le compilateur fournit un nom pour chaque type anonyme, bien que votre application ne puisse pas y accéder. Du point de vue du CLR, un type anonyme n'est pas différent des autres types de référence.

Si plusieurs initialiseurs d'objet dans un assembly spécifient une séquence de propriétés dans le même ordre et qui sont du même type et portent le même nom, le compilateur traite les objets comme des instances du même type. Elles partagent les mêmes informations de type générées par le compilateur.

Les types anonymes prennent en charge la mutation non destructrice sous la forme d’expressions. Cette fonctionnalité vous permet de créer une instance d’un type anonyme où une ou plusieurs propriétés ont de nouvelles valeurs :

var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };
Console.WriteLine(apple);
Console.WriteLine(onSale);

Vous ne pouvez pas déclarer un champ, une propriété, un événement ou le type de retour d’une méthode comme ayant un type anonyme. De même, vous ne pouvez pas déclarer de paramètre formel d’une méthode, d’une propriété, d’un constructeur ou d’un indexeur comme ayant un type anonyme. Pour passer un type anonyme, ou une collection qui contient des types anonymes, en tant qu'argument d'une méthode, vous pouvez déclarer le paramètre comme object de type. Toutefois, l’utilisation de object pour les types anonymes va à l’encontre de l’objectif d’un typage fort. Si vous devez stocker les résultats de requête ou les passer en dehors des limites de la méthode, utilisez un struct ordinaire ou une classe au lieu d'un type anonyme.

Dans la mesure où les méthodes Equals et GetHashCode dans les types anonymes sont définies selon les termes des méthodes Equals et GetHashCode des propriétés, deux instances du même type anonyme sont égales si toutes leurs propriétés sont égales.

Remarque

Le niveau d’accessibilité d’un type anonyme est internal. Par conséquent, deux types anonymes définis dans des assemblys différents ne sont pas du même type. Par conséquent, les instances de types anonymes ne peuvent pas être égales les unes aux autres lorsqu’elles sont définies dans différents assemblys, même si toutes leurs propriétés sont égales.

Les types anonymes remplacent la méthode ToString, en concaténant le nom et la sortie ToString de chaque propriété entourée d’accolades.

var v = new { Title = "Hello", Age = 24 };

Console.WriteLine(v.ToString()); // "{ Title = Hello, Age = 24 }"

Voir aussi