Tag Archives: tuple

Déconstruction de tuples en C# 7

Dans mon précedent billet, j’ai parlé d’une nouvelle fonctionnalité de C# 7 : les tuples. Dans Visual Studio 15 Preview 3, cette feature n’était pas tout à fait terminée ; il lui manquait 2 aspects importants :

  • la génération de métadonnées pour les noms des éléments des tuples, pour que les noms soient préservés entre les assemblies
  • la déconstruction des tuples en variables distinctes

Eh bien, il semble que l’équipe du langage C# n’a pas chômé au cours du mois écoulé, car ces deux éléments sont maintenant implémentés dans VS 15 Preview 4, qui a été publié hier ! Ils ont aussi rédigé des guides sur l’utilisation des tuples et de la déconstruction.

Il est maintenant possible d’écrire des choses comme ça :

var values = ...
var (count, sum) = Tally(values);
Console.WriteLine($"There are {count} values and their sum is {sum}");

(la méthode Tally est celle du précédent billet)

Remarquez que la variable t du billet précédent a disparu ; on affecte maintenant directement les variables count et sum à partir du résultat de la méthode, ce qui est à mon sens beaucoup plus élégant. Il ne semble pas y avoir de moyen d’ignorer une partie du tuple (c’est-à-dire ne pas l’affecter à une variable), mais peut-être cette possibilité viendra-t-elle plus tard.

Un aspect intéressant de la déconstruction est qu’elle n’est pas limitée aux tuples ; n’importe quel type peut être déconstruit, à condition d’avoir une méthode Deconstruct avec les paramètres out adéquats :

class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public void Deconstruct(out int x, out int y)
    {
        x = X;
        y = Y;
    }
}

...

var (x, y) = point;
Console.WriteLine($"Coordinates: ({x}, {y})");

La méthode Deconstruct peut également être une méthode d’extension, ce qui peut être utile si vous voulez déconstruire un type dont vous ne contrôlez pas le code. Les vieilles classes Sytem.Tuple, par exemple, peuvent être déconstruites à l’aide de méthodes d’extension comme celle-ci :

public static void Deconstruct<T1, T2>(this Tuple<T1, T2> tuple, out T1 item1, out T2 item2)
{
    item1 = tuple.Item1;
    item2 = tuple.Item2;
}

...

var tuple = Tuple.Create("foo", 42);
var (name, value) = tuple;
Console.WriteLine($"Name: {name}, Value = {value}");

Pour finir, les méthodes qui renvoient des tuples sont maintenant décorées d’un attribut [TupleElementNames] qui indique les noms des éléments du tuple :

// Code décompilé
[return: TupleElementNames(new[] { "count", "sum" })]
public static ValueTuple<int, double> Tally(IEnumerable<double> values)
{
   ...
}

(l’attribut est généré par le compilateur, vous n’avez pas besoin de l’écrire vous-même)

Cela permet de partager les noms des éléments du tuple entre les assemblies, et permet aux outils comme Intellisense de fournir des informations utiles sur la méthode.

L’implémentation des tuples en C# 7 semble donc à peu près terminée ; gardez cependant à l’esprit qu’il s’agit encore d’une preview, et que les choses peuvent encore changer d’ici la release finale.

Tuples en C# 7

Un tuple est une liste finie et ordonnée de valeurs, éventuellement de types différents, et est utilisé pour regrouper des valeurs liées entre elles sans avoir à créer une type spécifique pour les contenir.

.NET 4.0 a introduit un ensemble de classes Tuple , qui s’utilisent de la façon suivante

private static Tuple<int, double> Tally(IEnumerable<double> values)
{
	int count = 0;
	double sum = 0.0;
	foreach (var value in values)
	{
	    count++;
	    sum += value;
	}
	return Tuple.Create(count, sum);
}

...

var values = ...
var t = Tally(values);
Console.WriteLine($"There are {t.Item1} values and their sum is {t.Item2}");

Les classes Tuple ont deux principaux inconvénients:

  • Ce sont des classes, c’est-à-dire des types référence. Cela implique qu’elles doivent être allouées sur le tas, et collectées par le garbage collector quand elles ne sont plus utilisées. Pour les applications où les performances sont critiques, cela peut être problématique. De plus, le fait qu’elles puissent être nulles n’est souvent pas souhaitable.
  • Les éléments du tuple n’ont pas de noms, ou plutôt, ils ont toujours les mêmes noms (Item1, Item2, etc), qui ne sont pas du tout significatifs. Le type Tuple<T1, T2> ne communique absolument aucune information sur ce que le tuple représente réellement, ce qui en fait un mauvais candidat pour les APIs publiques.

En C# 7, une nouvelle fonctionnalité sera introduite pour améliorer le support des tuples : il sera possible de déclarer des types tuple “inline”, un peu comme des types anonymes, à part qu’ils ne sont pas limités à la méthode courante. En utilisant cette feature, le code précédent devient beaucoup plus clair:

static (int count, double sum) Tally(IEnumerable<double> values)
{
	int count = 0;
	double sum = 0.0;
	foreach (var value in values)
	{
	    count++;
	    sum += value;
	}
	return (count, sum);
}

...

var values = ...
var t = Tally(values);
Console.WriteLine($"There are {t.count} values and their sum is {t.sum}");

Notez comment le type de retour de Tally est déclaré, et comment le résultat est utilisé. C’est beaucoup mieux ! Les éléments du tuple ont maintenant des noms significatifs, et la syntaxe est plus agréable. Cette fonctionnalité repose sur un nouveau type ValueTuple<T1, T2>, qui est une structure et ne nécessite donc pas d’allocation sur le tas.

Vous pouvez essayer cette feature dès maintenant dans Visual Studio 15 Preview 3. Cependant, le type ValueTuple<T1, T2> ne fait pas (encore) partie du .NET Framework; pour faire fonctionner cet exemple, il faudra installer le package NuGet System.ValueTuple.

Enfin, une dernière remarque concernant les noms des membres du tuple : comme beaucoup d’autres fonctionnalités du langage, c’est juste du sucre syntaxique. Dans le code compilé, les membres du tuple sont référencés en tant que Item1 et Item2, et non count et sum. La méthode Tally renvoie en fait un ValueTuple<int, double>, et non un type spécialement généré.

Notez que le compilateur qui est livré avec VS 15 Preview 3 ne génère aucune métadonnée concernant les noms des membres du tuple. Cette partie de la feature n’est pas encore implémentée, mais devrait être incluse dans la version finale. Cela signifie qu’en attendant, on ne peut pas utiliser de tuples entre différents assemblies (enfin, on peut, mais en perdant les noms des membres, il faudra donc utiliser Item1 et Item2 pour y faire référence).