Avant Tuples, j'avais l'habitude de créer une class
et ses variables, puis de créer un objet à partir de cette classe et de faire de cet objet le type de retour pour certaines fonctions.
Maintenant, avec les tuples, je peux faire la même chose et en c # 7.0, nous pouvons assigner des noms compréhensibles pour les propriétés des tuples (auparavant, c'était item1
, item2
, etc.)
Alors maintenant je me demande, quand devrais-je utiliser des n-uplets et quand devrais-je créer une classe en c # 7.0?
Comme cette réponse est source de confusion chez certaines personnes ici, je devrais préciser que - conformément à la question - toutes les références à "Tuple" font ici référence au type ValueTuple
et aux nouvelles fonctionnalités sucre Tuple de C # 7 et anciens types de référence System.Tuple
.
Alors maintenant, je me demande quand devrais-je utiliser des n-uplets et quand devrais-je créer une classe en c # 7.0?
Vous seul pouvez vraiment répondre à cette question, car cela dépend vraiment de votre code.
Cependant, il existe des directives et des règles que vous pouvez suivre pour vous aider à choisir entre elles:
La plupart du temps, cela ne devrait pas être un problème. Cependant, si vous passez des n-uplets de grandes structures, cela peut avoir un impact sur les performances. Les sections locales/retours peuvent être utilisées pour contourner ces problèmes de performances.
De plus, comme il s'agit de valeurs, la modification d'une copie à distance ne modifiera pas la copie d'origine. Ceci est une bonne chose, mais pourrait attraper certaines personnes.
Les noms donnés aux éléments sont utilisés par le compilateur et (dans la plupart des cas) ne sont pas disponibles au moment de l'exécution. Cela signifie que la réflexion ne peut pas être utilisée pour découvrir leurs noms; ils ne sont pas accessibles de manière dynamique et ne peuvent pas être utilisés dans des vues rasantes.
C'est également un élément important à prendre en compte avec les API. Un tuple renvoyé par une méthode est l'exception à la règle concernant la possibilité de découvrir le nom après la compilation. Le compilateur ajoute des attributs à la méthode contenant des informations sur les noms de tuple. Cela signifie que vous pouvez retourner en toute sécurité un tuple d'une méthode publique dans un assembly et accéder à ses noms dans un autre.
Les nuplets sont beaucoup plus simples à écrire que les types car ils sont moins verbeux et la déclaration peut être "en ligne" (c'est-à-dire déclarée au moment de l'utilisation). Cela fonctionne bien lors de la déclaration d'une méthode qui renvoie plusieurs valeurs, par exemple.
Cependant, comme ils sont déclarés au moment de l’utilisation, si vous avez MethodA
qui appelle MethodB
qui appelle MethodC
et que chacun renvoie un tuple, vous devrez redéfinir le tuple à chaque étape. Il n'y a pas ( encore ) de moyen de créer un alias de Tuple et de le réutiliser via plusieurs méthodes.
Pour toute situation dans laquelle vous pourriez envisager d'utiliser un tuple: posez-vous simplement la question: "un tuple simplifiera-t-il le code ici". Si la réponse est "oui", utilisez-en un. Et c’est finalement la considération primordiale pour savoir s’il faut utiliser un tuple ou une classe personnalisée.
De manière générale, les classes nommées ont une certaine importance dans la conception de votre système. Ils sont aussi plus verbeux à écrire. Par exemple, vous pouvez avoir une classe appelée MediaFileOpener
. Il est important pour la conception que nous sachions ce que fait cette classe - nous travaillons avec des fichiers multimédias!
Les types et les nuplets anonymes sont utilisés lorsqu'il n'y a aucune importance conceptuelle et que vous souhaitez simplement un objet de transfert de données (DTO) léger pour déplacer les informations.
En règle générale, si votre classe a besoin de documentation pour décrire à quoi elle sert, ou si elle comporte un comportement, utilisez une classe complète. Si tout ce dont vous avez besoin est un stockage temporaire ou une sorte de regroupement, utilisez un tuple. Envisagez une situation dans laquelle vous souhaitez renvoyer plusieurs valeurs d'une méthode asynchrone. Tuple est conçu pour résoudre ce problème.
Utiliser une classe
Si vos objets sont des entités largement utilisées dans votre application et sont également stockées dans un stockage persistant tel qu'une base de données relationnelle (SQL Server, MySQL, SQLite), une base de données NoSQL ou un cache fichiers texte ou CSV.
Alors oui, tout ce qui est persistant devrait avoir sa propre classe.
Utilisez un tuple
Si vos objets ont une courte durée de vie sans signification particulière pour votre application. Par exemple, si vous devez renvoyer rapidement une paire de coordonnées, il est préférable d’avoir quelque chose comme ceci:
(double Latitude, double Longitude) getCoordinates()
{
return (144.93525, -98.356346);
}
que définir une classe séparée
class Coordinates
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
Un tuple vous évitera d'avoir à allouer de la mémoire sur le tas en utilisant new
pour une opération aussi simple.
Une autre fois où je trouve les nuplets utiles, c’est lorsqu’on effectue plusieurs opérations mathématiques sur des opérandes
(double Result1, double Result2, double Result3) performCalculations(int operand1, int operand 2)
Cela n'a aucun sens de définir une classe dans ce cas. Quels que soient les calculs, les résultats n'appartiennent pas à une classe. Donc, l’alternative serait d’utiliser des variables out
mais je pense que les n-uplets sont plus expressifs et qu’ils améliorent la lisibilité.
Je veux commencer par mentionner que C # prenait déjà en charge les types anonymous . Quels sont les types de référence. Ainsi, vous aviez déjà une alternative appropriée à la création d'une classe nommée.
L'un des avantages d'une classe nommée est qu'il sera plus facile de le réutiliser (par exemple, si vous avez besoin du même type à plusieurs endroits) et de documenter. Étant donné que le type anonyme est anonyme, vous ne pouvez obtenir les variables typées que si vous pouvez utiliser var
, ce qui limite les contextes dans lesquels les types anonymes sont utiles (par exemple, vous ne pouvez pas les utiliser comme types de champ, types de retour ou types de paramètre ).
Bien sûr, vous pouvez passer en revue certaines des limitations des types anonymes en utilisant System.Tuple
. Qui est aussi un type de référence, et vous pouvez l’utiliser explicitement. L'inconvénient est qu'il manque des noms personnalisés pour les membres.
Les tuples C # 7 ( ValueTuple
) peuvent être considérés comme similaires aux types anonymes. La première différence est qu'ils sont des types de valeur. Cela signifie que ces n-uplets auront un avantage en termes de performances tant qu'ils resteront dans la portée locale ou se déplaceront dans la pile (utilisation courante des types anonymes, en raison de sa limitation).
La deuxième différence est que la nouvelle syntaxe permet aux tuples d’apparaître à plus d’endroits que de types anonymes, comme vous le savez, vous avez le sucre syntaxique de définir le type de retour à ValueTuple
(lors de l’utilisation de types anonymes, vous deviez retourner object
).
La troisième différence est que ValueTuple
prend en charge la déconstruction en sortie de boîte. Pour citer Quoi de neuf en C # 7.0 :
Une autre façon de consommer des n-uplets est de les déconstruire. Une déclaration de déconstruction est une syntaxe permettant de scinder un tuple (ou une autre valeur) en ses parties et d'assigner ces parties individuellement à de nouvelles variables:
(string first, string middle, string last) = LookupName(id1); // deconstructing declaration WriteLine($"found {first} {last}.");
Ce que vous pouvez également faire avec des types personnalisés en ajoutant une méthode Deconstruct .
Pour résumé:
ValueTuple
a un sucre syntaxique en C # 7.0, ce qui devrait être pris en compte pour la lisibilité.ValueTuple
est un type de valeur. Tous les avantages et les inconvénients entre l’utilisation de class
et de struct
s’appliquent.ValueTuple
peut être utilisé explicitement (avec ou sans le sucre syntaxique), ce qui lui permet d’avoir la polyvalence de System.Tuple
tout en conservant les membres nommés.ValueTuple
prend en charge la déconstruction.Étant donné que le sucre syntaxique est incontournable, je dirais que l'argument le plus fort pour choisir ValueTuple
est le même argument que pour choisir un struct
. Ce qui serait idéal pour les petits types immuables, qui vivent principalement sur la pile (vous n'avez donc pas beaucoup de boxe et de déballage).
En comparant ValueTuple
à une structure complète, en considérant le sucre syntaxique, je suggérerais d'utiliser ValueTuple
par défaut, sauf si vous avez besoin d'une disposition explicit ou de l'ajout de méthodes.
Je tiens également à dire que le sucre syntaxique n'améliore pas nécessairement la lisibilité. La raison principale étant que vous ne nommez pas le type et que le nom du type donne un sens au code. De plus, vous pouvez ajouter de la documentation dans une déclaration struct
ou class
qui facilite la compréhension.
Au total, la situation où ValueTuple
brille vraiment renvoie des valeurs multiples à partir d'une méthode. Dans ce cas, il n'est plus nécessaire de créer de nouveaux paramètres out
. Et la documentation de la ValueTuple
utilisée peut vivre dans la documentation de la méthode. Si vous pensez que vous devez faire autre chose avec la variable ValueTuple
(en définissant des méthodes d'extension, par exemple), je suggérerais plutôt de créer un type nommé.
Les tuples sont destinés à représenter plusieurs valeurs, comme lorsqu'une méthode a l'intention de renvoyer plusieurs valeurs. Le support Tuple dans C # 7 utilise les instances System.ValueTuple<...>
pour représenter cet ensemble de valeurs. Les noms de ces valeurs ne sont valides que dans le contexte où elles sont utilisées et ne sont pas appliquées.
Les classes sont destinées à représenter une seule valeur avec plusieurs attributs.
Généralement, vous voulez avoir une classe lorsque votre objet sera utilisé ailleurs ou s'il représente un objet réel ou un concept de votre domaine. Vous allez probablement créer une classe pour représenter une voiture ou un magasin de voiture, pas des n-uplets.
D'autre part, parfois, vous voulez simplement renvoyer quelques objets d'une méthode. Peut-être qu'ils ne représentent rien de spécial, c'est juste que vous devez les renvoyer ensemble avec cette méthode particulière. Parfois, même s'ils représentent un concept de votre domaine (supposons que vous retourniez (Car, Store)
, qui pourrait être représenté sous la forme d'un objet Sale
), vous ne les utiliserez réellement nulle part, vous ne faites que déplacer des données. Dans ces cas, c'est bien d'utiliser des n-uplets.
Maintenant, en parlant de C #, il y a une dernière chose que vous devriez savoir. Le type de tuples de C # 7 est en fait la ValueTuple
, qui est une structure. Contrairement aux classes, qui sont des types de référence, les structures sont des types de valeur. Vous pouvez en savoir plus à ce sujet sur msdn . Plus important encore, sachez qu’ils peuvent nécessiter beaucoup de copies, soyez donc prudent.
Tuple est une excellente option lorsque vous souhaitez combiner plusieurs valeurs (pouvant être de types différents) dans un même objet sans créer de classe personnalisée. Dans ce cas, Tuple serait une option rapide et parfaite.
Je pense que cela va devenir une question qui se pose souvent. Il n'y a actuellement aucune "bonne pratique" pour savoir quand utiliser la nouvelle valeur tuples vs une classe.
Cependant, cela vaut la peine de lire ce qui est apparu lors de conversations précédentes sur les versions précédentes de tuples vs une classe .
À mon avis, la valeur Tuple ne devrait être utilisée que de manière minimale et avec un maximum de trois valeurs. Je pense que cela fait un bon compromis entre "restituer certaines valeurs sans avoir besoin d'une classe" et "d'horrible fouillis de valeurs". Si vous avez plus de trois valeurs à renvoyer, créez une classe.
Je n'utiliserais jamais non plus un Tuple pour revenir d'une API publique que les consommateurs devront utiliser. Encore une fois, utilisez simplement une classe.
Voici un code du monde réel que j'ai utilisé:
public async Task<(double temperature, double humidity, string description)> DownloadTodaysForecast()
Dès que je veux renvoyer des données plus complexes, je fais un cours.
J'éviterais d'utiliser des n-uplets comme méthode de retour public. Dans de tels cas, je préférerais définir une classe ou une structure.