Choisir entre les types anonymes et tuples

Le choix du type approprié implique de prendre en compte sa facilité d’utilisation, ses performances et ses compromis par rapport aux autres types. Les types anonymes sont disponibles depuis C# 3.0, tandis que les types System.Tuple<T1,T2> génériques ont été introduits avec .NET Framework 4.0. Depuis lors, de nouvelles options ont été introduites avec la prise en charge au niveau du langage, comme System.ValueTuple<T1,T2>, qui comme son nom l’indique, fournit un type valeur avec la flexibilité des types anonymes. Dans cet article, vous allez découvrir quand il est approprié de choisir un type plutôt que l’autre.

Facilité d’utilisation et fonctionnalités

Les types anonymes ont été introduits dans C# 3.0 avec les expressions LINQ (Language-Integrated Query). Avec LINQ, les développeurs projettent souvent les résultats des requêtes dans des types anonymes qui contiennent quelques propriétés sélectionnées des objets avec lesquels ils travaillent. Prenons l’exemple suivant, qui instancie un tableau d’objets DateTime et les itère en les projetant dans un type anonyme avec deux propriétés.

var dates = new[]
{
    DateTime.UtcNow.AddHours(-1),
    DateTime.UtcNow,
    DateTime.UtcNow.AddHours(1),
};

foreach (var anonymous in
             dates.Select(
                 date => new { Formatted = $"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks }))
{
    Console.WriteLine($"Ticks: {anonymous.Ticks}, formatted: {anonymous.Formatted}");
}

Les types anonymes sont instanciés à l’aide de l’opérateur new, et les noms de propriétés et les types sont déduits de la déclaration. Si plusieurs initialiseurs d’objet dans un même 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.

L’extrait de code C# précédent projette un type anonyme avec deux propriétés, tout comme la classe C# générée par le compilateur suivante :

internal sealed class f__AnonymousType0
{
    public string Formatted { get; }
    public long Ticks { get; }

    public f__AnonymousType0(string formatted, long ticks)
    {
        Formatted = formatted;
        Ticks = ticks;
    }
}

Pour plus d’informations, consultez Types anonymes. La même fonctionnalité existe avec les tuples lors de la projection dans des requêtes LINQ. Vous pouvez sélectionner des propriétés dans des tuples. Ces tuples circulent dans la requête, comme le feraient les types anonymes. Considérez maintenant l’exemple suivant qui utilise System.Tuple<string, long>.

var dates = new[]
{
    DateTime.UtcNow.AddHours(-1),
    DateTime.UtcNow,
    DateTime.UtcNow.AddHours(1),
};

foreach (var tuple in
            dates.Select(
                date => new Tuple<string, long>($"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks)))
{
    Console.WriteLine($"Ticks: {tuple.Item2}, formatted: {tuple.Item1}");
}

Avec System.Tuple<T1,T2>, l’instance expose les propriétés d’élément numérotées, comme Item1 et Item2. Ces noms de propriétés peuvent rendre plus difficile la compréhension de l’intention des valeurs de propriété, car le nom de propriété fournit uniquement l’ordinal. En outre, les types System.Tuple sont des types class de référence. System.ValueTuple<T1,T2>, toutefois, est un type struct valeur. L’extrait de code C# suivant, utilise ValueTuple<string, long> pour la projection. Ce faisant, il assigne à l’aide d’une syntaxe littérale.

var dates = new[]
{
    DateTime.UtcNow.AddHours(-1),
    DateTime.UtcNow,
    DateTime.UtcNow.AddHours(1),
};

foreach (var (formatted, ticks) in
            dates.Select(
                date => (Formatted: $"{date:MMM dd, yyyy at hh:mm zzz}", date.Ticks)))
{
    Console.WriteLine($"Ticks: {ticks}, formatted: {formatted}");
}

Pour plus d’informations sur les tuples, consultez Types Tuple (référence C#) ou Tuples (Visual Basic).

Les exemples précédents sont tous fonctionnellement équivalents, mais il existe de légères différences dans leur facilité d’utilisation et leurs implémentations sous-jacentes.

Compromis

Vous souhaiterez peut-être toujours utiliser ValueTuple plutôt que Tuple, et les types anonymes, mais il existe des compromis que vous devez prendre en compte. Les types ValueTuple sont mutables, tandis que les Tuple sont en lecture seule. Les types anonymes peuvent être utilisés dans les arborescences d’expressions, contrairement aux tuples. Le tableau suivant présente une vue d’ensemble de certaines des principales différences.

Différences clés

Nom Modificateur d’accès Type Nom de membre personnalisé Prise en charge de la déconstruction Prise en charge de l’arborescence d’expression
Types anonymes internal class ✔️ ✔️
Tuple public class ✔️
ValueTuple public struct ✔️ ✔️

Sérialisation

Une considération importante lors du choix d’un type est de savoir s’il doit ou non être sérialisé. La sérialisation correspond au processus de conversion de l'état d'un objet en un formulaire persistant ou transportable. Pour plus d’informations, consultez Sérialisation. Lorsque la sérialisation est importante, la création d’un class ou struct est préférable aux types anonymes ou tuples.

Performances

Les performances entre ces types dépendent du scénario. L’impact majeur concerne le compromis entre les allocations et la copie. Dans la plupart des scénarios, l’impact est faible. Lorsque des impacts majeurs peuvent survenir, des mesures doivent être prises pour éclairer la décision.

Conclusion

En tant que développeur choisissant entre les tuples et les types anonymes, plusieurs facteurs doivent être pris en compte. En règle générale, si vous n’utilisez pas d’arborescences d’expression et que vous êtes à l’aise avec la syntaxe tuple, choisissez les ValueTuple, qui fournissent un type valeur avec la flexibilité de pouvoir nommer des propriétés. Si vous utilisez des arborescences d’expression et que vous préférez nommer les propriétés, choisissez des types anonymes. Sinon, utilisez Tuple.

Voir aussi