Avec l'ajout de la classe Tuple dans .net 4, j'ai essayé de décider si les utiliser dans ma conception est un mauvais choix ou non. À mon avis, un Tuple peut être un raccourci pour écrire une classe de résultat (je suis sûr qu'il existe également d'autres utilisations).
Donc ça:
public class ResultType
{
public string StringValue { get; set; }
public int IntValue { get; set; }
}
public ResultType GetAClassedValue()
{
//..Do Some Stuff
ResultType result = new ResultType { StringValue = "A String", IntValue = 2 };
return result;
}
Est équivalent à ceci:
public Tuple<string, int> GetATupledValue()
{
//...Do Some stuff
Tuple<string, int> result = new Tuple<string, int>("A String", 2);
return result;
}
Donc , mettre de côté la possibilité que je manque le point de tuples, est l'exemple avec un Tuple un mauvais choix de conception? Pour moi, cela semble moins encombrant, mais pas aussi auto-documenté et propre. Cela signifie qu'avec le type ResultType
, il est très clair plus tard ce que chaque partie de la classe signifie mais vous avez du code supplémentaire à maintenir. Avec le, Tuple<string, int>
vous devrez rechercher et comprendre ce que chacun Item
représente, mais vous écrivez et maintenez moins de code.
Toute expérience que vous auriez eue avec ce choix serait grandement appréciée.
la source
Réponses:
Les tuples sont parfaits si vous contrôlez à la fois leur création et leur utilisation - vous pouvez maintenir le contexte, ce qui est essentiel pour les comprendre.
Sur une API publique, cependant, ils sont moins efficaces. Le consommateur (pas vous) doit deviner ou consulter la documentation, en particulier pour des choses comme
Tuple<int, int>
.Je les utiliserais pour les membres privés / internes, mais utiliserais les classes de résultats pour les membres publics / protégés.
Cette réponse contient également quelques informations.
la source
StringOps
(essentiellement une classe de «méthodes d'extension» pour JavaString
) a une méthodeparition(Char => Boolean): (String, String)
(prend un prédicat, retourne un tuple de 2 chaînes). La sortie est évidente (évidemment, l'un va être les caractères pour lesquels le prédicat était vrai et l'autre pour lequel il était faux, mais on ne sait pas qui est lequel). C'est la documentation qui clarifie cela (et la correspondance de modèle peut être utilisée pour clarifier l'utilisation qui est laquelle).Il existe en effet d'autres utilisations intéressantes
Tuple<>
- la plupart d'entre elles impliquent d'abstraire la sémantique d'un groupe particulier de types partageant une structure similaire et de les traiter simplement comme un ensemble ordonné de valeurs. Dans tous les cas, l'un des avantages des tuples est qu'ils évitent d'encombrer votre espace de noms avec des classes de données uniquement qui exposent des propriétés mais pas des méthodes.Voici un exemple d'utilisation raisonnable pour
Tuple<>
:Dans l'exemple ci-dessus, nous voulons représenter une paire d'adversaires, un tuple est un moyen pratique de coupler ces instances sans avoir à créer une nouvelle classe. Voici un autre exemple:
Une main de poker peut être considérée comme un simple jeu de cartes - et le tuple (peut être) une manière raisonnable d'exprimer ce concept.
Le renvoi d'
Tuple<>
instances fortement typées dans le cadre d'une API publique pour un type public est rarement une bonne idée. Comme vous le reconnaissez vous-même, les tuples exigent que les parties impliquées (auteur de la bibliothèque, utilisateur de la bibliothèque) se mettent d'accord à l'avance sur le but et l'interprétation des types de tuple utilisés. Il est déjà assez difficile de créer des API intuitives et claires, l'utilisationTuple<>
publique obscurcit uniquement l'intention et le comportement de l'API.Les types anonymes sont également une sorte de tuple - cependant, ils sont fortement typés et vous permettent de spécifier des noms clairs et informatifs pour les propriétés appartenant au type. Mais les types anonymes sont difficiles à utiliser dans différentes méthodes - ils ont été principalement ajoutés pour prendre en charge des technologies telles que LINQ où les projections produiraient des types auxquels nous ne voudrions normalement pas attribuer des noms. (Oui, je sais que les types anonymes avec les mêmes types et propriétés nommées sont consolidés par le compilateur).
Ma règle de base est la suivante: si vous le renvoyez depuis votre interface publique, faites-en un type nommé .
Mon autre règle de base pour l'utilisation des tuples est la suivante: nommez les arguments de méthode et les variables localc de type
Tuple<>
aussi clairement que possible - faites en sorte que le nom représente la signification des relations entre les éléments du tuple. Pensez à monvar opponents = ...
exemple.Voici un exemple de cas réel où j'ai utilisé
Tuple<>
pour éviter de déclarer un type de données uniquement à utiliser uniquement dans mon propre assembly . La situation implique le fait que lors de l'utilisation de dictionnaires génériques contenant des types anonymes, il devient difficile d'utiliser laTryGetValue()
méthode pour trouver des éléments dans le dictionnaire car la méthode nécessite unout
paramètre qui ne peut pas être nommé:PS Il existe un autre moyen (plus simple) de contourner les problèmes liés aux types anonymes dans les dictionnaires, et c'est d'utiliser le
var
mot - clé pour laisser le compilateur «déduire» le type pour vous. Voici cette version:la source
Tuple<>
pourrait avoir un sens. Il y a toujours des alternatives ...Les tuples peuvent être utiles ... mais ils peuvent aussi être pénibles plus tard. Si vous avez une méthode qui renvoie,
Tuple<int,string,string,int>
comment savez-vous quelles sont ces valeurs plus tard. Étaient-ilsID, FirstName, LastName, Age
ou étaient-ilsUnitNumber, Street, City, ZipCode
.la source
Les tuples sont un ajout assez décevant au CLR du point de vue d'un programmeur C #. Si vous avez une collection d'éléments dont la longueur varie, vous n'avez pas besoin qu'ils aient des noms statiques uniques au moment de la compilation.
Mais si vous avez une collection de longueur constante, cela implique que les emplacements fixes de la collection ont chacun une signification prédéfinie spécifique. Et il est toujours préférable de leur donner des noms de statiques appropriés dans ce cas, plutôt que d' avoir à se rappeler l'importance de
Item1
,Item2
etc.Les classes anonymes en C # fournissent déjà une excellente solution à l'utilisation privée la plus courante des tuples, et elles donnent des noms significatifs aux éléments, donc ils sont en fait supérieurs dans ce sens. Le seul problème est qu'ils ne peuvent pas s'échapper des méthodes nommées. Je préférerais voir cette restriction levée (peut-être uniquement pour les méthodes privées) plutôt que d'avoir un support spécifique pour les tuples en C #:
la source
struct var SimpleStruct {int x, int y;}
, définirait une structure avec un nom commençant par un guid associé à des structures simples et a quelque chose comme{System.Int16 x}{System.Int16 y}
ajouté; tout nom commençant par ce GUID serait nécessaire pour représenter une structure contenant uniquement ces éléments et le contenu spécifié particulier; si plusieurs définitions pour le même nom sont dans la portée et qu'elles correspondent, toutes seraient considérées du même type.ref
paramètre, la seule façon dont il sera possible d'avoir un délégué qui peut être passé aux deux fonctions sera si une personne produit et compile un déléguez le type et le donne à l'autre pour construire. Il est impossible que les deux personnes puissent indiquer que les deux types doivent être considérés comme synonymes.Tuple
? Je viens de parcourir 50 endroits dans ma base de code où j'utilise des tuples, et tous sont soit des valeurs de retour (généralementyield return
), soit des éléments de collections qui sont utilisés ailleurs, donc n'allez pas pour les classes anonymes.Semblable au mot
var
- clé , il est conçu comme une commodité - mais il est aussi facile d'en abuser.À mon avis le plus humble, ne pas exposer
Tuple
comme une classe de retour. Utilisez-le en privé, si la structure de données d'un service ou d'un composant l'exige, mais retournez des classes bien formées bien connues à partir de méthodes publiques.la source
var
ne pouvez pas être renvoyé ou exister en dehors de sa portée déclarée. cependant, il est toujours possible de l'utiliser au- delà de son usage prévu et d'être «abusé»Utiliser une classe comme
ResultType
est plus clair. Vous pouvez donner des noms significatifs aux champs de la classe (alors qu'avec un tuple, ils seraient appelésItem1
etItem2
). Ceci est d'autant plus important si les types des deux champs sont les mêmes: le nom les distingue clairement.la source
Que diriez-vous d'utiliser des tuples dans un motif décorer-trier-non décorer? (Transformée Schwartzian pour le peuple Perl). Voici un exemple artificiel, bien sûr, mais les tuples semblent être un bon moyen de gérer ce genre de chose:
Maintenant, j'aurais pu utiliser un Object [] de deux éléments ou dans cet exemple spécifique une chaîne [] de deux éléments. Le fait est que j'aurais pu utiliser n'importe quoi comme deuxième élément d'un Tuple qui est utilisé en interne et est assez facile à lire.
la source
IMO ces "tuples" sont essentiellement tous les
struct
types anonymes d'accès public avec des membres non nommés .Le seul endroit où j'utiliserais tuple est lorsque vous avez besoin de rassembler rapidement des données, dans une portée très limitée. La sémantique des données doit être évidente , donc le code n'est pas difficile à lire. Donc, utiliser un tuple (
int
,int
) pour (row, col) semble raisonnable. Mais j'ai du mal à trouver un avantage sur unstruct
avec des membres nommés (donc aucune erreur n'est commise et les lignes / colonnes ne sont pas accidentellement interchangées)Si vous renvoyez des données à l'appelant ou acceptez les données d'un appelant, vous devriez vraiment utiliser un
struct
avec des membres nommés.Prenons un exemple simple:
La version tuple
Je ne vois aucun avantage à utiliser tuple à la place d'une structure avec des membres nommés. L'utilisation de membres sans nom est un pas en arrière pour la lisibilité et la compréhensibilité de votre code.
Tuple me semble être un moyen paresseux d'éviter de créer un
struct
avec des membres nommés réels. La surutilisation de tuple, où vous sentez vraiment que vous / ou quelqu'un d'autre rencontrant votre code aurait besoin de membres nommés est A Bad Thing ™ si jamais j'en ai vu un.la source
Ne me jugez pas, je ne suis pas un expert, mais avec les nouveaux tuples en C # 7.x maintenant, vous pouvez retourner quelque chose comme:
Au moins maintenant, vous pouvez le nommer et les développeurs peuvent voir des informations.
la source
Cela dépend, bien sûr! Comme vous l'avez dit, un tuple peut vous faire gagner du code et du temps lorsque vous souhaitez regrouper certains éléments pour une consommation locale. Vous pouvez également les utiliser pour créer des algorithmes de traitement plus génériques que si vous transmettez une classe concrète. Je ne me souviens pas combien de fois j'ai souhaité avoir quelque chose au-delà de KeyValuePair ou d'un DataRow pour passer rapidement une date d'une méthode à une autre.
D'un autre côté, il est tout à fait possible d'en faire trop et de faire passer des tuples où vous ne pouvez que deviner ce qu'ils contiennent. Si vous envisagez d'utiliser un tuple entre les classes, il serait peut-être préférable de créer une classe concrète.
Utilisés avec modération bien sûr, les tuples peuvent conduire à un code plus concis et plus lisible. Vous pouvez consulter C ++, STL et Boost pour des exemples de la façon dont les tuples sont utilisés dans d'autres langages, mais à la fin, nous devrons tous expérimenter pour trouver comment ils s'intègrent le mieux dans l'environnement .NET.
la source
Les tuples sont une fonctionnalité de framework inutile dans .NET 4. Je pense qu'une grande opportunité a été manquée avec C # 4.0. J'aurais aimé avoir des tuples avec des membres nommés, afin que vous puissiez accéder aux différents champs d'un tuple par nom au lieu de Value1 , Value2 , etc ...
Cela aurait nécessité un changement de langue (syntaxe), mais cela aurait été très utile.
la source
let success, value = MethodThatReturnsATuple()
Personnellement, je n'utiliserais jamais un Tuple comme type de retour car il n'y a aucune indication de ce que les valeurs représentent. Les tuples ont des utilisations intéressantes car contrairement aux objets, ils sont des types valeur et comprennent donc l'égalité. Pour cette raison, je les utiliserai comme clés de dictionnaire si j'ai besoin d'une clé multipartie ou comme clé pour une clause GroupBy si je veux grouper par plusieurs variables et ne veux pas de regroupements imbriqués (qui veut des groupements imbriqués?). Pour surmonter le problème avec une verbosité extrême, vous pouvez les créer avec une méthode d'assistance. Gardez à l'esprit que si vous accédez fréquemment à des membres (via Item1, Item2, etc.), vous devriez probablement utiliser une construction différente telle qu'une structure ou une classe anonyme.
la source
J'ai utilisé des tuples, le
Tuple
et le nouveauValueTuple
, dans un certain nombre de scénarios différents et suis arrivé à la conclusion suivante: ne pas utiliser .À chaque fois, j'ai rencontré les problèmes suivants:
Mon avis est que les tuples sont un détriment, pas une fonctionnalité, de C #.
J'ai des critiques quelque peu similaires, mais beaucoup moins sévères, sur
Func<>
etAction<>
. Ceux-ci sont utiles dans de nombreuses situations, en particulier les simplesAction
et lesFunc<type>
variantes, mais au-delà de cela, j'ai trouvé que la création d'un type de délégué est plus élégante, lisible, maintenable et vous donne plus de fonctionnalités, telles queref
/out
parameters.la source
ValueTuple
- et je ne les aime pas non plus. Premièrement, la dénomination n'est pas forte, c'est plutôt une suggestion, très facile à gâcher car elle peut être attribuée implicitement à l'unTuple
ou l' autre ouValueTuple
avec le même nombre et les mêmes types d'éléments. Deuxièmement, le manque d'héritage et la nécessité de copier-coller l'ensemble de la définition d'un endroit à l'autre sont toujours des inconvénients.