web-dev-qa-db-fra.com

L'utilisation de tuples .NET 4.0 dans mon code C # est-elle une mauvaise décision de conception?

Avec l’ajout de Tuple class dans .net 4, j’essaie de décider si les utiliser dans ma conception est un mauvais choix ou non. La façon dont je le vois, un Tuple peut être un raccourci pour écrire une classe de résultat (je suis sûr qu'il existe d'autres utilisations aussi). 

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, en laissant de côté la possibilité que je manque le point de Tuples, l'exemple avec un Tuple est-il un mauvais choix de conception? Pour moi, cela semble être moins encombré, mais pas comme documentant et propre. Ce qui 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 à gérer. Avec le Tuple<string, int>, vous devrez rechercher et comprendre ce que chaque Item représente, mais vous écrivez et conservez moins de code.

Toute expérience de ce choix serait grandement appréciée.

164
Jason Webb

Les tuples sont parfaits si vous contrôlez à la fois leur création et leur utilisation - vous pouvez conserver le contexte, 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 les classes de résultats pour les membres publics/protégés.

Cette réponse a aussi quelques informations.

160
Bryan Watts

À mon avis, un tuple est un raccourci pour écrire une classe de résultat (je suis sûr qu'il existe d'autres utilisations aussi).

Il existe en effet d'autres utilisations intéressantes pour 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 n-uplets est qu'ils évitent d'encombrer votre espace de noms avec des classes contenant uniquement des données, qui exposent des propriétés mais pas des méthodes.

Voici un exemple d'utilisation raisonnable de Tuple<>:

var opponents = new Tuple<Player,Player>( playerBob, playerSam );

Dans l'exemple ci-dessus, nous voulons représenter une paire d'opposants. Un tuple est un moyen pratique de coupler ces instances sans avoir à créer une nouvelle classe. Voici un autre exemple:

var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );

Une main de poker peut être considérée comme un simple jeu de cartes - et Tuple (peut-être) un moyen raisonnable d’exprimer ce concept.

écartant la possibilité que je manque le point de Tuples, est le exemple avec un Tuple un mauvais design choix?

Renvoyer des instances Tuple<> fortement typées dans le cadre d'une API publique pour un type public est rarement une bonne idée. Comme vous le savez vous-même, les tuples imposent aux parties concernées (auteur de la bibliothèque, utilisateur de la bibliothèque) de s’accorder à l’avance sur le but et l’interprétation des types de tuple utilisés. Il est assez difficile de créer des API intuitives et claires. Utiliser Tuple<> publiquement ne fait que masquer l'intention et le comportement de l'API.

Les types anonymes sont aussi une sorte de Tuple - mais 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 avec 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 de noms. (Oui, je sais que les types anonymes avec les mêmes types et les propriétés nommées sont consolidés par le compilateur).

Ma règle générale est la suivante: si vous le renvoyez depuis votre interface publique, faites-en un type nommé

Mon autre règle de base pour utiliser les tuples est la suivante: name arguments de méthode et variables locales de type Tuple<> aussi clairement que possible - faire en sorte que le nom représente la signification des relations entre les éléments du Tuple. Pensez à mon exemple var opponents = ....

Voici un exemple de cas concret dans lequel 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 lorsqu’on utilise des dictionnaires génériques contenant des types anonymes, il devient difficile d’utiliser la méthode TryGetValue() pour rechercher des éléments dans le dictionnaire car la méthode nécessite un paramètre out qui ne peut pas être nommé:

public static class DictionaryExt 
{
    // helper method that allows compiler to provide type inference
    // when attempting to locate optionally existent items in a dictionary
    public static Tuple<TValue,bool> Find<TKey,TValue>( 
        this IDictionary<TKey,TValue> dict, TKey keyToFind ) 
    {
        TValue foundValue = default(TValue);
        bool wasFound = dict.TryGetValue( keyToFind, out foundValue );
        return Tuple.Create( foundValue, wasFound );
    }
}

public class Program
{
    public static void Main()
    {
        var people = new[] { new { LastName = "Smith", FirstName = "Joe" },
                             new { LastName = "Sanders", FirstName = "Bob" } };

        var peopleDict = people.ToDictionary( d => d.LastName );

        // ??? foundItem <= what type would you put here?
        // peopleDict.TryGetValue( "Smith", out ??? );

        // so instead, we use our Find() extension:
        var result = peopleDict.Find( "Smith" );
        if( result.First )
        {
            Console.WriteLine( result.Second );
        }
    }
}

P.S. Il existe un autre moyen (plus simple) de résoudre les problèmes soulevés par les types anonymes dans les dictionnaires. Il consiste à utiliser le mot clé var pour permettre au compilateur de "déduire" le type pour vous. Voici cette version:

var foundItem = peopleDict.FirstOrDefault().Value;
if( peopleDict.TryGetValue( "Smith", out foundItem ) )
{
   // use foundItem...
}
78
LBushkin

Les tuples peuvent être utiles ... mais ils peuvent aussi être pénibles plus tard. Si vous avez une méthode qui retourne Tuple<int,string,string,int>, comment savoir quelles sont ces valeurs ultérieurement? Étaient-ils ID, FirstName, LastName, Age ou étaient-ils UnitNumber, Street, City, ZipCode.

17
Matthew Whited

Les n-uplets sont un ajout assez décevant au CLR du point de vue d'un programmeur C #. Si vous avez une collection d'éléments de longueur variable, 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 signifie que les emplacements fixes de la collection ont chacun une signification prédéfinie. Et il est toujours préférable de leur donner des noms statiques appropriés dans ce cas, plutôt que de devoir se souvenir de la signification de Item1, Item2, etc.

Les classes anonymes en C # fournissent déjà une superbe solution à l'utilisation privée la plus courante des n-uplets, et attribuent des noms significatifs aux éléments, de sorte qu'ils sont réellement supérieurs en ce sens. Le seul problème est qu'ils ne peuvent pas sortir des méthodes nommées. Je préférerais que cette restriction soit levée (peut-être uniquement pour les méthodes privées) plutôt que d'avoir un support spécifique pour les n-uplets en C #:

private var GetDesserts()
{
    return _icecreams.Select(
        i => new { icecream = i, topping = new Topping(i) }
    );
}

public void Eat()
{
    foreach (var dessert in GetDesserts())
    {
        dessert.icecream.AddTopping(dessert.topping);
        dessert.Eat();
    }
}
9
Daniel Earwicker

Semblable au mot clé var, il est conçu à des fins de commodité - mais est tout aussi facilement utilisé de manière abusive.

À mon humble avis, n'exposez pas Tuple en tant que classe de retour. Utilisez-le en privé, si la structure de données d'un service ou d'un composant l'exige, mais renvoyez des classes bien formées et bien connues à partir de méthodes publiques.

// one possible use of Tuple within a private context. would never
// return an opaque non-descript instance as a result, but useful
// when scope is known [ie private] and implementation intimacy is
// expected
public class WorkflowHost
{
    // a map of uri's to a workflow service definition 
    // and workflow service instance. By convention, first
    // element of Tuple is definition, second element is
    // instance
    private Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> _map = 
        new Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> ();
}
8
johnny g

Que diriez-vous d'utiliser Tuples dans un motif décorer-trier-décorer? (Transformation de Schwartzian pour le peuple Perl). Voici un exemple astucieux, mais Tuples semble être un bon moyen de gérer ce genre de choses:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] files = Directory.GetFiles("C:\\Windows")
                    .Select(x => new Tuple<string, string>(x, FirstLine(x)))
                    .OrderBy(x => x.Item2)
                    .Select(x => x.Item1).ToArray();
        }
        static string FirstLine(string path)
        {
            using (TextReader tr = new StreamReader(
                        File.Open(path, FileMode.Open)))
            {
                return tr.ReadLine();
            }
        }
    }
}

Maintenant, j'aurais pu utiliser un objet [] de deux éléments ou dans cet exemple spécifique une chaîne de caractères [] de deux éléments. Le fait est que j'aurais pu utiliser n'importe quoi comme deuxième élément d'un tuple utilisé en interne et qui est assez facile à lire.

5
Clinton Pierce

L'utilisation d'une classe telle que ResultType est plus claire. Vous pouvez donner des noms significatifs aux champs de la classe (alors qu'avec un tuple, ils seraient appelés Item1 et Item2). Ceci est d'autant plus important si les types des deux champs sont identiques: le nom les distingue clairement.

5
Richard Fearn

OMI ces "tuples" sont fondamentalement tous les accès publics anonymes struct types avec des membres non nommés.

Le lieu uniquement que je voudrais utiliser Tuple est lorsque vous avez besoin de blober rapidement certaines données, dans un champ très limité. La sémantique des données doit être évidente , le code n'est donc pas difficile à lire. Donc, utiliser un tuple (int, int) pour (row, col) semble raisonnable. Mais j'ai beaucoup de mal à trouver un avantage sur une struct avec des membres nommés (aucune erreur n'est donc commise et les lignes/colonnes ne sont pas interchangées accidentellement)

Si vous renvoyez des données à l'appelant ou acceptez des données d'un appelant, vous devriez vraiment utiliser une variable struct avec des membres nommés.

Prenons un exemple simple:

struct Color{ float r,g,b,a ; }
public void setColor( Color color )
{
}

La version du tuple

public void setColor( Tuple<float,float,float,float> color )
{
  // why?
}

Je ne vois aucun avantage à utiliser Tuple à la place d'une structure avec des membres nommés. L'utilisation de membres non nommés est un retour 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 une struct avec des membres nommés réels. Surutilisation de Tuple, où vous pensez vraiment que vous/ou une autre personne rencontrant votre code aurait besoin de membres nommés est une mauvaise chose si j'en ai déjà vu un.

4
bobobobo

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:

return (string Name1, int Name2)

Au moins maintenant, vous pouvez le nommer et les développeurs peuvent voir certaines informations.

2
Denis Gordin

Cela dépend, bien sûr! Comme vous l'avez dit, un tuple peut vous faire économiser 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 plus du nombre de fois où 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'autre part, il est tout à fait possible d'en faire trop et de faire passer des n-uplets où vous ne pouvez que deviner ce qu'ils contiennent. Si vous envisagez d'utiliser un tuple d'une classe à l'autre, il serait peut-être préférable de créer une classe concrète.

Bien que utilisés avec modération, les n-uplets peuvent conduire à un code plus concis et lisible. Vous pouvez consulter C++, STL et Boost pour obtenir des exemples d'utilisation de Tuples dans d'autres langages. Au final, nous devrons tous faire des essais pour trouver la meilleure manière de les intégrer à l'environnement .NET.

2
Panagiotis Kanavos

Les tuples sont une fonctionnalité de framework inutile dans .NET 4. Je pense qu'une excellente opportunité a été manquée avec C # 4.0. J'aurais bien aimé avoir des nuplets avec des membres nommés pour pouvoir accéder aux différents champs d'un tuple par leur nom au lieu de Valeur1 , Valeur2 , etc ...

Cela aurait nécessité un changement de langue (syntaxe), mais cela aurait été très utile.

1
Philippe Leybaert

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é multipart ou comme clé d'une clause GroupBy si je veux grouper par plusieurs variables et ne veux pas de groupes imbriqués (Qui a jamais voulu des groupes imbriqués?). Pour résoudre 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 aux membres (via Item1, Item2, etc.), vous devriez probablement utiliser une construction différente telle qu'une structure ou une classe anonyme.

1
Seth Paulson

J'ai utilisé des n-uplets, à la fois la Tuple et la nouvelle ValueTuple, dans un certain nombre de scénarios différents et je suis arrivé à la conclusion suivante: ne pas utiliser .

À chaque fois, j'ai rencontré les problèmes suivants:

  • le code est devenu illisible par manque de nommage fort;
  • incapable d'utiliser les fonctionnalités de hiérarchie de classe, telles que DTO de classe de base et DTO de classe enfant, etc.
  • si elles sont utilisées à plus d'un endroit, vous finissez par copier-coller ces définitions laides, au lieu d'un nom de classe vierge.

Mon opinion est que les n-uplets sont un détriment, pas une fonctionnalité, de C #.

J'ai un peu similaire, mais beaucoup moins sévère, la critique de Func<> et Action<>. Celles-ci sont utiles dans de nombreuses situations, en particulier les variantes simples Action et Func<type>, mais pour le reste, j'ai constaté que la création d'un type de délégué est plus élégante, lisible, maintenable et vous offre davantage de fonctionnalités, comme les paramètres ref/out.

0
Mr. TA