Réponses:
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.
convenient not to make a custom class. Voulez-vous dire créer une instance de classe personnalisée en utilisant =new..()?
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
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 .
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");
}
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.
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 .
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).
new Tuple<Guid,string,...>
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));
}
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".
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.
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:
Dictionary<string, Dictionary<string, int>>.Dictionary<CarAndCity, int>.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.
Quelques exemples du haut de ma tête:
Par exemple, vous ne souhaitez pas inclure System.Drawing dans une application Web uniquement pour utiliser Point / PointF et Size / SizeF.
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.
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 ?
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;
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.
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.
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;
}
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.
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.
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 };
}
}
}
}