Exemple pratique où Tuple peut être utilisé dans .Net 4.0?

97

J'ai vu le Tuple introduit dans .Net 4 mais je ne peux pas imaginer où il peut être utilisé. Nous pouvons toujours créer une classe personnalisée ou Struct.

Amitabh
la source
13
C'est peut-être un bon endroit pour noter que ce sujet est très ancien. Regardez ce qui s'est passé en C # 7 !
maf-soft

Réponses:

83

C'est le point - il est plus pratique de ne pas créer de classe ou de structure personnalisée tout le temps. C'est une amélioration comme Actionou Func... vous pouvez créer ces types vous-même, mais il est pratique qu'ils existent dans le cadre.

Tanascius
la source
5
Il vaut probablement la peine de souligner de MSDN : "Tous les membres statiques publics de ce type sont thread-safe. Les membres de l'instance ne sont pas garantis d'être thread-safe. "
Matt Borja
Eh bien, peut-être que les concepteurs de langage auraient dû faciliter la création de types personnalisés à la volée, alors? On pourrait donc garder la même syntaxe au lieu d'en introduire une autre?
Thomas Eyde
@ThomasEyde, c'est exactement ce qu'ils font depuis 15 ans. C'est ainsi que les membres à corps d'expression ont été ajoutés et valorisent désormais les tuples. Classes de style record de justesse manqué le dos coupé en Août (9 mois avant ce commentaire) pour cette version de C # 7, et sont probablement à venir en C # 8. Notez également que la valeur tuples offrent une valeur d' égalité où classes pures et simples ne le font pas . Présenter tout cela en 2002 nécessiterait de la prescience
Panagiotis Kanavos
que voulez-vous dire par convenient not to make a custom class. Voulez-vous dire créer une instance de classe personnalisée en utilisant =new..()?
Alex
75

Avec les tuples, vous pouvez facilement implémenter un dictionnaire bidimensionnel (ou n-dimensionnel d'ailleurs). Par exemple, vous pouvez utiliser un tel dictionnaire pour implémenter un mappage de change:

var forex = new Dictionary<Tuple<string, string>, decimal>();
forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR
forex.Add(Tuple.Create("USD", "GBP"), 0.64128m);
forex.Add(Tuple.Create("EUR", "USD"), 1.33635m);
forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m);
forex.Add(Tuple.Create("GBP", "USD"), 1.55938m);
forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m);
forex.Add(Tuple.Create("USD", "USD"), 1.00000m);
forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m);
forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m);

decimal result;
result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20
result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99
result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58
MarioVW
la source
Je préfère utiliser un Enum pour les abréviations de pays. Est-ce possible?
Zack
1
Bien sûr, cela devrait fonctionner sans problème car les énumérations sont des types valeur.
MarioVW
@MarioVW Cela pourrait également être accompli en utilisant un tableau multidimensionnel. Comment tuple fait une différence?
Alex
26

Il y a un excellent article dans le magazine MSDN qui parle des maux de ventre et des considérations de conception qui ont conduit à l'ajout de Tuple à la BCL. Le choix entre un type valeur et un type référence est particulièrement intéressant.

Comme l'article l'indique clairement, la force motrice derrière Tuple était que de nombreux groupes à l'intérieur de Microsoft l'utilisent, l'équipe F # à l'avant. Bien que cela ne soit pas mentionné, je pense que le nouveau mot-clé "dynamic" en C # (et VB.NET) y était également pour quelque chose, les tuples sont très courants dans les langages dynamiques.

Sinon, ce n'est pas particulièrement supérieur à la création de votre propre poco, au moins vous pouvez donner un meilleur nom aux membres.


MISE À JOUR: en raison d'une grosse révision en C # version 7, maintenant beaucoup plus d'amour de la syntaxe. Annonce préliminaire dans ce billet de blog .

Hans Passant
la source
23

Voici un petit exemple - disons que vous avez une méthode qui doit rechercher le pseudo et l'adresse e-mail d'un utilisateur, à partir d'un identifiant d'utilisateur. Vous pouvez toujours créer une classe personnalisée contenant ces données, ou utiliser un paramètre ref / out pour ces données, ou vous pouvez simplement renvoyer un Tuple et avoir une belle signature de méthode sans avoir à créer un nouveau POCO.

public static void Main(string[] args)
{
    int userId = 0;
    Tuple<string, string> userData = GetUserData(userId);
}

public static Tuple<string, string> GetUserData(int userId)
{
    return new Tuple<string, string>("Hello", "World");
}
Tejs
la source
10
Ceci est un bon exemple cependant, ne justifie pas l'utilisation de Tuple.
Amitabh
7
Un tuple est un ajustement décent ici puisque vous renvoyez des valeurs distinctes; mais un tuple brille davantage lorsque vous renvoyez plusieurs valeurs de types différents .
Mark Rushakoff
21
Un autre bon exemple serait int.TryParse, car vous pourriez éliminer le paramètre de sortie et utiliser à la place un Tuple. Ainsi, vous pourriez avoir Tuple<bool, T> TryParse<T>(string input)et au lieu d'avoir à utiliser un paramètre de sortie, vous récupérez les deux valeurs dans un tuple.
Tejs
3
en fait, c'est exactement ce qui se passe lorsque vous appelez une méthode TryParse à partir de F #.
Joel Mueller
23

J'ai utilisé un tuple pour résoudre le problème 11 du projet Euler :

class Grid
{
    public static int[,] Cells = { { 08, 02, 22, // whole grid omitted

    public static IEnumerable<Tuple<int, int, int, int>> ToList()
    {
        // code converts grid to enumeration every possible set of 4 per rules
        // code omitted
    }
}

Maintenant, je peux résoudre tout le problème avec:

class Program
{
    static void Main(string[] args)
    {
        int product = Grid.ToList().Max(t => t.Item1 * t.Item2 * t.Item3 * t.Item4);
        Console.WriteLine("Maximum product is {0}", product);
    }
}

J'aurais pu utiliser un type personnalisé pour cela, mais cela aurait ressemblé exactement à Tuple .

Craig Stuntz
la source
16

La syntaxe de tuple de C # est ridiculement volumineuse, donc les tuples sont difficiles à déclarer. Et il n'a pas de correspondance de modèle, donc ils sont également douloureux à utiliser.

Mais parfois, vous souhaitez simplement un regroupement d'objets ad hoc sans créer de classe pour celui-ci. Par exemple, disons que je voulais agréger une liste, mais que je voulais deux valeurs au lieu d'une:

// sum and sum of squares at the same time
var x =
    Enumerable.Range(1, 100)
    .Aggregate((acc, x) => Tuple.Create(acc.Item1 + x, acc.Item2 + x * x));

Au lieu de combiner une collection de valeurs en un seul résultat, développons un seul résultat en une collection de valeurs. La façon la plus simple d'écrire cette fonction est:

static IEnumerable<T> Unfold<T, State>(State seed, Func<State, Tuple<T, State>> f)
{
    Tuple<T, State> res;
    while ((res = f(seed)) != null)
    {
        yield return res.Item1;
        seed = res.Item2;
    }
}

fconvertit un état en un tuple. Nous renvoyons la première valeur du tuple et définissons notre nouvel état sur la deuxième valeur. Cela nous permet de conserver l'état tout au long du calcul.

Vous l'utilisez comme tel:

// return 0, 2, 3, 6, 8
var evens =
    Unfold(0, state => state < 10 ? Tuple.Create(state, state + 2) : null)
    .ToList();

// returns 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
var fibs =
    Unfold(Tuple.Create(0, 1), state => Tuple.Create(state.Item1, Tuple.Create(state.Item2, state.Item1 + state.Item2)))
    .Take(10).ToList();

evensest assez simple, mais fibsest un peu plus intelligent. C'est en statefait un tuple qui contient respectivement fib (n-2) et fib (n-1).

Juliette
la source
4
+1 Tuple.Create est un raccourci pratique pournew Tuple<Guid,string,...>
AaronLS
@Juliet What the use of State keyword
irfandar
7

Je n'aime pas leur abus, car ils produisent du code qui ne s'explique pas, mais ils sont géniaux pour implémenter des clés composées à la volée, car ils implémentent IStructuralEquatable et IStructuralComparable (à utiliser à la fois pour la recherche et la commande fins).

Et ils combinent tous les codes de hachage de leurs éléments, en interne; par exemple, voici le GetHashCode de Tuple (tiré de ILSpy):

    int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
    {
        return Tuple.CombineHashCodes(comparer.GetHashCode(this.m_Item1), comparer.GetHashCode(this.m_Item2), comparer.GetHashCode(this.m_Item3));
    }
Notoriousxl
la source
7

Les tuples sont parfaits pour effectuer plusieurs opérations d'E / S asynchrones à la fois et renvoyer toutes les valeurs ensemble. Voici les exemples de le faire avec et sans Tuple. Les tuples peuvent réellement rendre votre code plus clair!

Sans (mauvaise nidification!):

Task.Factory.StartNew(() => data.RetrieveServerNames())
    .ContinueWith(antecedent1 =>
        {
            if (!antecedent1.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent1.Result);
                Task.Factory.StartNew(() => data.RetrieveLogNames())
                    .ContinueWith(antecedent2 =>
                        {
                            if (antecedent2.IsFaulted)
                            {
                                LogNames = KeepExistingFilter(LogNames, antecedent2.Result);
                                Task.Factory.StartNew(() => data.RetrieveEntryTypes())
                                    .ContinueWith(antecedent3 =>
                                        {
                                            if (!antecedent3.IsFaulted)
                                            {
                                                EntryTypes = KeepExistingFilter(EntryTypes, antecedent3.Result);
                                            }
                                        });
                            }
                        });
            }
        });

Avec Tuple

Task.Factory.StartNew(() =>
    {
        List<string> serverNames = data.RetrieveServerNames();
        List<string> logNames = data.RetrieveLogNames();
        List<string> entryTypes = data.RetrieveEntryTypes();
        return Tuple.Create(serverNames, logNames, entryTypes);
    }).ContinueWith(antecedent =>
        {
            if (!antecedent.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent.Result.Item1);
                LogNames = KeepExistingFilter(LogNames, antecedent.Result.Item2);
                EntryTypes = KeepExistingFilter(EntryTypes, antecedent.Result.Item3);
            }
        });

Si vous utilisez une fonction anonyme avec un type implicite de toute façon , alors vous ne faites pas le code moins clair en utilisant le Tuple. Réaccorder un tuple à partir d'une méthode? À utiliser avec parcimonie lorsque la clarté du code est essentielle, à mon humble avis. Je sais que la programmation fonctionnelle en C # est difficile à résister, mais nous devons considérer tous ces vieux programmeurs C # "orientés objet".

AndyClaw
la source
5

Les tuples sont fortement utilisés dans les langages fonctionnels qui peuvent faire plus de choses avec eux, maintenant F # est un langage .net «officiel» que vous voudrez peut-être interagir avec lui à partir de C # et les passer entre du code écrit en deux langues.

Mant101
la source
Les tuples sont également des types intégrés pour certains langages de script populaires tels que Python et Ruby (qui ont également des implémentations .Net pour interop ... IronPython / Ruby).
MutantNinjaCodeMonkey
5

J'ai tendance à éviter Tuplepour la plupart des scénarios car cela nuit à la lisibilité. Cependant, Tupleest utile lorsque vous devez regrouper des données non liées.

Par exemple, supposons que vous ayez une liste de voitures et des villes dans lesquelles elles ont été achetées:

Mercedes, Seattle
Mustang, Denver
Mercedes, Seattle
Porsche, Seattle
Tesla, Seattle
Mercedes, Seattle

Vous souhaitez agréger les décomptes pour chaque voiture par ville:

Mercedes, Seattle [3]
Mustang, Denver [1]
Porsche, Seattle [1]
Tesla, Seattle [1]

Pour ce faire, vous créez un fichier Dictionary. Vous avez quelques options:

  1. Créez un Dictionary<string, Dictionary<string, int>>.
  2. Créez un Dictionary<CarAndCity, int>.
  3. Créez un Dictionary<Tuple<string, string>, int>.

La lisibilité est perdue avec la première option. Cela vous demandera d'écrire beaucoup plus de code.

La deuxième option fonctionne et est succincte, mais la voiture et la ville ne sont pas vraiment liées et n'appartiennent probablement pas à une classe ensemble.

La troisième option est succincte et propre. C'est une bonne utilisation de Tuple.

John Kurlak
la source
4

Quelques exemples du haut de ma tête:

  • Un emplacement X et Y (et Z si vous le souhaitez)
  • a Largeur et hauteur
  • Tout ce qui est mesuré au fil du temps

Par exemple, vous ne souhaitez pas inclure System.Drawing dans une application Web uniquement pour utiliser Point / PointF et Size / SizeF.

James Westgate
la source
2
Tupleest aussi pratique dans les situations critiques Pointou SomeVectorpeut être utile lors d'opérations graphiques. Il met en évidence deux valeurs très liées l'une à l'autre. J'ai souvent vu des propriétés nommées StartTime et EndTime lors de la lecture de code, mais même mieux qu'une date et une durée, un Tuple vous oblige à prendre en compte les deux valeurs chaque fois que vous travaillez dans ce domaine de votre logique métier. Ou renvoyer "hé, les données ont changé (booléen), voici les données (autre type)" en traitement lourd au lieu de passer par des liaisons intensives.
Léon Pelletier
3

Vous devez être très prudent avec l'utilisation Tupleet probablement réfléchir à deux fois avant de le faire. De mon expérience précédente, j'ai découvert qu'en utilisantTuple rend le code très difficile à lire et à supporter à l'avenir. Il y a quelque temps, j'ai dû corriger du code où les tuples étaient utilisés presque partout. Au lieu de penser à des modèles d'objets appropriés, ils ont juste utilisé des tuples. C'était un cauchemar ... parfois je voulais tuer le type qui écrivait le code ...

Je ne veux pas dire que vous ne devriez pas utiliser Tupleet que c'est mal ou quelque chose et je suis sûr à cent pour cent qu'il y a des tâches où Tuplele meilleur candidat à utiliser, mais vous devriez probablement réfléchir à nouveau, en avez-vous VRAIMENT besoin ?

M. Citrouille
la source
1

La meilleure utilisation des tuples que j'ai trouvée est lorsque vous devez renvoyer plus d'un type d'objet à partir d'une méthode, vous savez quels types d'objet et leur numéro ils seront, et ce n'est pas une longue liste.

D'autres alternatives simples consisteraient à utiliser un paramètre `` out ''

private string MyMethod(out object)

ou faire un dictionnaire

Dictionary<objectType1, objectType2>

L'utilisation d'un tuple évite cependant de créer l'objet 'out' ou d'avoir à rechercher essentiellement l'entrée dans le dictionnaire;

Sidjames
la source
1

Je viens de trouver la solution de l'un de mes problèmes dans Tuple. C'est comme déclarer une classe dans la portée d'une méthode, mais avec une déclaration différée de ses noms de champs. Vous opérez avec des collections de tuples, ses instances uniques, puis créez une collection de type anonyme avec les noms de champ requis, en vous basant sur votre tuple. Cela vous évite de créer la nouvelle classe à cet effet.

La tâche consiste à écrire une réponse JSON à partir de LINQ sans aucune classe supplémentaire:

 //I select some roles from my ORM my with subrequest and save results to Tuple list
 var rolesWithUsers = (from role in roles
                       select new Tuple<string, int, int>(
                         role.RoleName, 
                         role.RoleId, 
                         usersInRoles.Where(ur => ur.RoleId == role.RoleId).Count()
                      ));

 //Then I add some new element required element to this collection
 var tempResult = rolesWithUsers.ToList();
 tempResult.Add(new Tuple<string, int, int>(
                        "Empty", 
                         -1,
                         emptyRoleUsers.Count()
                      ));

 //And create a new anonimous class collection, based on my Tuple list
 tempResult.Select(item => new
            {
                GroupName = item.Item1,
                GroupId = item.Item2,
                Count = item.Item3
            });


 //And return it in JSON
 return new JavaScriptSerializer().Serialize(rolesWithUsers);

Bien sûr, nous pourrions le faire en déclarant une nouvelle classe pour mes groupes, mais l'idée de créer de telles collections anonymes sans déclarer de nouvelles classes.

Alex
la source
1

Eh bien dans mon cas, j'ai dû utiliser un Tuple lorsque j'ai découvert que nous ne pouvions pas utiliser de paramètre dans une méthode asynchrone. Lisez à ce sujet ici . J'avais également besoin d'un type de retour différent. J'ai donc utilisé un Tuple à la place comme type de retour et marqué la méthode comme asynchrone.

Exemple de code ci-dessous.

...
...
// calling code.
var userDetails = await GetUserDetails(userId);
Console.WriteLine("Username : {0}", userDetails.Item1);
Console.WriteLine("User Region Id : {0}", userDetails.Item2);
...
...

private async Tuple<string,int> GetUserDetails(int userId)
{
    return new Tuple<string,int>("Amogh",105);
    // Note that I can also use the existing helper method (Tuple.Create).
}

En savoir plus sur Tuple ici . J'espère que cela t'aides.

Amogh Natu
la source
Je suppose que vous ne vouliez pas retourner un type anonyme ou un tableau (ou un contenu dynamique)
ozzy432836
0

Changer la forme des objets lorsque vous devez les envoyer sur un fil ou passer à différentes couches d'application et que plusieurs objets sont fusionnés en un seul:

Exemple:

var customerDetails = new Tuple<Customer, List<Address>>(mainCustomer, new List<Address> {mainCustomerAddress}).ToCustomerDetails();

ExtensionMethod:

public static CustomerDetails ToCustomerDetails(this Tuple<Website.Customer, List<Website.Address>> customerAndAddress)
    {
        var mainAddress = customerAndAddress.Item2 != null ? customerAndAddress.Item2.SingleOrDefault(o => o.Type == "Main") : null;
        var customerDetails = new CustomerDetails
        {
            FirstName = customerAndAddress.Item1.Name,
            LastName = customerAndAddress.Item1.Surname,
            Title = customerAndAddress.Item1.Title,
            Dob = customerAndAddress.Item1.Dob,
            EmailAddress = customerAndAddress.Item1.Email,
            Gender = customerAndAddress.Item1.Gender,
            PrimaryPhoneNo = string.Format("{0}", customerAndAddress.Item1.Phone)
        };

        if (mainAddress != null)
        {
            customerDetails.AddressLine1 =
                !string.IsNullOrWhiteSpace(mainAddress.HouseName)
                    ? mainAddress.HouseName
                    : mainAddress.HouseNumber;
            customerDetails.AddressLine2 =
                !string.IsNullOrWhiteSpace(mainAddress.Street)
                    ? mainAddress.Street
                    : null;
            customerDetails.AddressLine3 =
                !string.IsNullOrWhiteSpace(mainAddress.Town) ? mainAddress.Town : null;
            customerDetails.AddressLine4 =
                !string.IsNullOrWhiteSpace(mainAddress.County)
                    ? mainAddress.County
                    : null;
            customerDetails.PostCode = mainAddress.PostCode;
        }
...
        return customerDetails;
    }
Matas Vaitkevicius
la source
0

Un paramètre out est idéal lorsqu'il n'y a que quelques valeurs à renvoyer, mais lorsque vous commencez à rencontrer 4, 5, 6 ou plus de valeurs à renvoyer, cela peut devenir compliqué. Une autre option pour renvoyer plusieurs valeurs est de créer et de renvoyer une classe / structure définie par l'utilisateur ou d'utiliser un Tuple pour regrouper toutes les valeurs qui doivent être renvoyées par une méthode.

La première option, utilisant une classe / structure pour renvoyer les valeurs, est simple. Créez simplement le type (dans cet exemple, c'est une structure) comme ceci:

public struct Dimensions
{
public int Height;
public int Width;
public int Depth;
}

La deuxième option, en utilisant un Tuple, est une solution encore plus élégante que l'utilisation d'un objet défini par l'utilisateur. Un tuple peut être créé pour contenir n'importe quel nombre de valeurs de différents types. De plus, les données que vous stockez dans le Tuple sont immuables; une fois que vous avez ajouté les données au Tuple via le constructeur ou la méthode Create statique, ces données ne peuvent pas être modifiées. Les tuples peuvent accepter jusqu'à huit valeurs distinctes. Si vous devez renvoyer plus de huit valeurs, vous devrez utiliser la classe spéciale Tuple: Classe Tuple Lorsque vous créez un Tuple avec plus de huit valeurs, vous ne pouvez pas utiliser la méthode Create statique. Vous devez à la place utiliser le constructeur de la classe. Voici comment créer un tuple de 10 valeurs entières:

var values = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>> (
1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int> (8, 9, 10));

Bien sûr, vous pouvez continuer à ajouter plus de tuples à la fin de chaque tuple incorporé, en créant n'importe quel Tuple de taille dont vous avez besoin.

Yashwanth Chowdary Kata
la source
0

Uniquement pour le prototypage - Les tuples n'ont pas de sens. C'est pratique de les utiliser mais ce n'est qu'un raccourci! Pour les prototypes - très bien. Assurez-vous simplement de supprimer ce code plus tard.

C'est facile à écrire, difficile à lire. Il n'a aucun avantage visible sur les classes, les classes internes, les classes anonymes, etc.

lapin1985
la source
0

Eh bien, j'ai essayé 3 façons de résoudre le même problème en C # 7 et j'ai trouvé un cas d'utilisation pour Tuples.

Travailler avec des données dynamiques dans des projets Web peut parfois être pénible lors de la cartographie, etc.

J'aime la façon dont le Tuple est simplement mappé automatiquement sur item1, item2, itemN qui me semble plus robuste que d'utiliser des index de tableau où vous pourriez être pris sur un élément hors index ou d'utiliser le type anonyme où vous pourriez mal orthographier un nom de propriété.

J'ai l'impression qu'un DTO a été créé gratuitement simplement en utilisant un Tuple et je peux accéder à toutes les propriétés en utilisant itemN qui ressemble plus à un typage statique sans avoir à créer un DTO séparé à cet effet.

using System;

namespace Playground
{
    class Program
    {
        static void Main(string[] args)
        {
            var tuple = GetTuple();
            Console.WriteLine(tuple.Item1);
            Console.WriteLine(tuple.Item2);
            Console.WriteLine(tuple.Item3);
            Console.WriteLine(tuple);

            Console.WriteLine("---");

            var dyn = GetDynamic();
            Console.WriteLine(dyn.First);
            Console.WriteLine(dyn.Last);
            Console.WriteLine(dyn.Age);
            Console.WriteLine(dyn);

            Console.WriteLine("---");

            var arr = GetArray();
            Console.WriteLine(arr[0]);
            Console.WriteLine(arr[1]);
            Console.WriteLine(arr[2]);
            Console.WriteLine(arr);

            Console.Read();

            (string, string, int) GetTuple()
            {
                return ("John", "Connor", 1);
            }

            dynamic GetDynamic()
            {
                return new { First = "John", Last = "Connor", Age = 1 };
            }

            dynamic[] GetArray()
            {
                return new dynamic[] { "John", "Connor", 1 };
            }
        }
    }
}
ozzy432836
la source