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

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 une structure.

C’est le point – il est plus pratique de ne pas créer une classe ou une structure personnalisée en permanence. C’est une amélioration comme Action ou Func … vous pouvez faire cela vous-même, mais il est pratique qu’ils existent dans le framework.

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

 var forex = new Dictionary, 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 

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

Comme l’indique clairement l’article, la force mosortingce de Tuple était qu’il y avait tant de groupes à l’intérieur de Microsoft, l’équipe F # devant. Bien que cela ne soit pas mentionné, je pense que le nouveau mot clé “dynamic” en C # (et VB.NET) y est pour quelque chose, les tuples sont très courants dans les langages dynamics.

Il 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: dû à une révision importante de la version C # 7, maintenant beaucoup plus de syntaxe. Annonce préliminaire dans cet article de blog .

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> 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(ssortingng[] 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é à Tuple .

Voici un petit exemple – disons que vous avez une méthode qui doit rechercher le descripteur et l’adresse e-mail d’un utilisateur, avec un ID utilisateur. Vous pouvez toujours créer une classe personnalisée qui contient 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(ssortingng[] args) { int userId = 0; Tuple userData = GetUserData(userId); } public static Tuple GetUserData(int userId) { return new Tuple("Hello", "World"); } 

La syntaxe du tuple de C # est ridiculement volumineuse, donc les tuples sont difficiles à déclarer. Et il n’y a pas de correspondance de motifs, ils sont donc douloureux à utiliser.

Mais de temps en temps, vous souhaitez simplement un regroupement d’objects ad hoc sans créer de classe. Par exemple, disons que je voulais agréger une liste, mais 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 manière la plus simple d’écrire cette fonction est la suivante:

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

f convertit un état en un tuple. Nous retournons 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(); 

evens est assez simple, mais fibs est un peu plus intelligent. Son state est en fait un tuple qui contient respectivement fib (n-2) et fib (n-1).

Je n’aime pas les 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 l’ordre fins).

Et ils combinent tous les codes de hachage de leurs articles, en interne; Par exemple, voici GetHashCode de Tuple (extrait 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)); } 

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 en fait rendre votre code plus clair!

Sans (nid méchant!):

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

Avec tuple

 Task.Factory.StartNew(() => { List serverNames = data.ResortingeveServerNames(); List logNames = data.ResortingeveLogNames(); List entryTypes = data.ResortingeveEntryTypes(); 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, vous ne rendez pas le code moins clair en utilisant le Tuple. Retoucher un Tuple d’une méthode? Utilisez avec parcimonie lorsque la clarté du code est la clé, à mon humble avis. Je sais que la functional programming en C # est difficile à résister, mais nous devons tenir compte de tous ces vieux programmeurs C # “orientés object”.

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

J’ai tendance à éviter le Tuple pour la plupart des scénarios car cela nuit à la lisibilité. Cependant, Tuple est utile lorsque vous devez regrouper des données sans rapport.

Par exemple, supposons que vous ayez une liste de voitures et les 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 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 Dictionary . Vous avez quelques options:

  1. Créez un Dictionary> .
  2. Créez un Dictionary .
  3. Créez un Dictionary, int> .

La lisibilité est perdue avec la première option. Il vous faudra é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 un bon usage du Tuple .

Quelques exemples en tête:

  • Un emplacement X et Y (et Z si vous voulez)
  • une largeur et une hauteur
  • Tout ce qui a été mesuré au fil du temps

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

Vous devez être très prudent en utilisant Tuple et pensez probablement à deux fois avant de le faire. D’après mon expérience précédente, l’utilisation de Tuple rend le code très difficile à lire et à prendre en charge à l’avenir. Il y a quelque temps, j’ai dû corriger un code où les tuples étaient utilisés presque partout. Au lieu de penser à des modèles d’objects appropriés, ils utilisaient simplement des tuples. C’était un cauchemar … parfois je voulais tuer le gars qui avait écrit le code …

Je ne veux pas dire que tu ne devrais pas utiliser Tuple et que c’est maléfique ou autre chose et je suis sûr à cent pour cent qu’il y a des tâches où le Tuple est le meilleur candidat, mais tu devrais probablement y réfléchir à nouveau. besoin de ça?

La meilleure utilisation de Tuples que j’ai trouvée est lorsque vous devez renvoyer plus d’un type d’object à partir d’une méthode, vous savez quels types d’object et quel nombre ils seront, et ce n’est pas une longue liste.

D’autres alternatives simples consisteraient à utiliser un paramètre ‘out’

 private ssortingng MyMethod(out object) 

ou faire un dictionnaire

 Dictionary 

L’utilisation d’un tuple évite toutefois de créer l’object «out» ou de rechercher essentiellement l’entrée dans le dictionnaire;

Juste trouvé la solution de l’un de mes problèmes dans Tuple. C’est comme déclarer une classe dans la scope d’une méthode, mais avec une déclaration lente 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 champs requirejs, en fonction de 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 classes supplémentaires:

  //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( 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( "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); 

En effet, nous pourrions le faire en déclarant une nouvelle classe pour mes groupes, mais en créant l’idée de créer une telle collection sans déclarer de nouvelles classes.

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 aussi besoin d’un type de retour différent. J’ai donc utilisé un Tuple 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 GetUserDetails(int userId) { return new Tuple("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.

Changer les formes des objects lorsque vous devez les envoyer à travers un fil ou passer à différentes couches d’application et que plusieurs objects soient fusionnés en un seul:

Exemple:

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

ExtensionMethod:

 public static CustomerDetails ToCustomerDetails(this Tuple> 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 = ssortingng.Format("{0}", customerAndAddress.Item1.Phone) }; if (mainAddress != null) { customerDetails.AddressLine1 = !ssortingng.IsNullOrWhiteSpace(mainAddress.HouseName) ? mainAddress.HouseName : mainAddress.HouseNumber; customerDetails.AddressLine2 = !ssortingng.IsNullOrWhiteSpace(mainAddress.Street) ? mainAddress.Street : null; customerDetails.AddressLine3 = !ssortingng.IsNullOrWhiteSpace(mainAddress.Town) ? mainAddress.Town : null; customerDetails.AddressLine4 = !ssortingng.IsNullOrWhiteSpace(mainAddress.County) ? mainAddress.County : null; customerDetails.PostCode = mainAddress.PostCode; } ... return customerDetails; } 

Un paramètre de sortie est excellent lorsque seules quelques valeurs doivent être renvoyées, mais lorsque vous commencez à rencontrer 4, 5, 6 valeurs ou plus qui doivent être renvoyées, cela peut devenir difficile. Une autre option pour renvoyer plusieurs valeurs consiste à créer et renvoyer une classe / structure définie par l’utilisateur ou à utiliser un Tuple pour regrouper toutes les valeurs devant ê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 seconde option, utilisant un Tuple, est une solution encore plus élégante que l’utilisation d’un object défini par l’utilisateur. Un tuple peut être créé pour contenir un nombre quelconque de valeurs de types différents. De plus, les données que vous stockez dans le Tuple sont immuables. Une fois que vous ajoutez 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 retourner plus de huit valeurs, vous devrez utiliser la classe Tuple spéciale: 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 plutôt utiliser le constructeur de la classe. Voici comment créer un Tuple de 10 valeurs entières:

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

Bien sûr, vous pouvez continuer à append plus de tuples à la fin de chaque tuple intégré, en créant n’importe quelle taille de tuple dont vous avez besoin.

Seulement pour le prototypage – les tuples n’ont aucun sens. Il est pratique de les utiliser mais c’est un raccourci seulement! Pour les prototypes – bien. Veillez simplement à supprimer ce code plus tard.

C’est facile à écrire, difficile à lire. Il n’a pas d’avantages visibles par rapport aux classes, aux classes internes, aux classes anonymes, etc.

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 dynamics dans des projets Web peut parfois être pénible lors de la cartographie, etc.

J’aime la façon dont le Tuple se mappe automatiquement sur item1, item2, itemN, ce qui me semble plus robuste que d’utiliser des index de tableaux où vous pourriez tomber sur un élément hors index ou utiliser le type anonyme où vous pourriez mal orthographier un nom de propriété.

On a l’impression qu’un DTO a été créé gratuitement en utilisant un Tuple et que je peux accéder à toutes les propriétés en utilisant itemN, qui ressemble plus à un typage statique sans avoir à créer un DTO distinct pour cela.

 using System; namespace Playground { class Program { static void Main(ssortingng[] 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(); (ssortingng, ssortingng, int) GetTuple() { return ("John", "Connor", 1); } dynamic GetDynamic() { return new { First = "John", Last = "Connor", Age = 1 }; } dynamic[] GetArray() { return new dynamic[] { "John", "Connor", 1 }; } } } }