c# .net vb.net .net-4.0 c#-4.0

c# - Ejemplo práctico donde Tuple se puede usar en.Net 4.0?



vb.net .net-4.0 (19)

He visto el Tuple introducido en .Net 4 pero no puedo imaginar dónde se puede usar. Siempre podemos hacer una clase personalizada o Struct.


Acabo de encontrar la solución de uno de mis problemas en Tuple. Es como declarar una clase en el alcance de un método, pero con una declaración perezosa de sus nombres de campos. Usted opera con colecciones de tuplas, sus instancias únicas y luego crea una colección de tipo anónimo con los nombres de campo requeridos, basándose en su tupla. Esto evita que crees la nueva clase para este propósito.

La tarea es escribir una respuesta JSON de LINQ sin ninguna clase adicional:

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

De hecho, podríamos hacer esto al declarar una nueva clase para mis grupos, pero la idea de crear colecciones tan anónimas sin declarar nuevas clases.


Algunos ejemplos fuera de mi cabeza:

  • Una ubicación X e Y (y Z si lo desea)
  • un ancho y una altura
  • Cualquier cosa medida a lo largo del tiempo

Por ejemplo, no desea incluir System.Drawing en una aplicación web solo para usar Point / PointF y Size / SizeF.


Aquí hay un pequeño ejemplo: supongamos que tiene un método que necesita buscar el identificador y la dirección de correo electrónico de un usuario, dado un Id de usuario. Siempre puede crear una clase personalizada que contenga esa información, o usar un parámetro de ref / out para esa información, o simplemente puede devolver un Tuple y tener una buena firma de método sin tener que crear un nuevo 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"); }


Bueno, en mi caso, tuve que usar un Tuple cuando descubrí que no podemos usar el parámetro out en un método asíncrono. Lea sobre esto here . También necesitaba un tipo de devolución diferente. Entonces utilicé una Tuple como mi tipo de devolución y marqué el método como asíncrono.

Muestra de código a continuación.

... ... // 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). }

Lea más sobre Tuple aquí . Espero que esto ayude.


Bueno, probé 3 formas de resolver el mismo problema en C # 7 y encontré un caso de uso para Tuples.

Trabajar con datos dinámicos en proyectos web a veces puede ser un problema al mapear, etc.

Me gusta la forma en que el Tuple se correlacionó automáticamente en item1, item2, itemN, que me parece más robusto que utilizar índices de matriz en los que puede quedar atrapado en un ítem sin índice o usar el tipo anónimo donde puede escribir mal el nombre de una propiedad.

Se siente como si se hubiera creado un DTO de forma gratuita solo con un Tuple y puedo acceder a todas las propiedades usando itemN, que se parece más al tipado estático sin tener que crear un DTO separado para ese propósito.

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 }; } } } }


Cambiar las formas de los objetos cuando necesita enviarlos a través de un cable o pasarlos a una capa de aplicación diferente y varios objetos se fusionan en uno solo:

Ejemplo:

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; }


Con las tuplas, podría implementar fácilmente un diccionario bidimensional (o n-dimensional para el caso). Por ejemplo, podría usar dicho diccionario para implementar un mapeo de cambio de moneda:

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


Debe tener mucho cuidado con el uso de Tuple y probablemente pensar dos veces antes de hacer esto. Según mi experiencia previa, descubrí que usar Tuple hace que el código sea muy difícil de leer y admitir en el futuro. Hace un tiempo, tuve que arreglar un código donde las tuplas se usaban en casi todas partes. En lugar de pensar en modelos de objetos adecuados, simplemente usaron tuplas. Eso fue una pesadilla ... a veces quería matar al tipo que escribió el código ...

No quiero decir que no deberías usar Tuple y es malvado o algo así y estoy cien por ciento seguro de que hay algunas tareas donde la Tuple es la mejor candidata para ser utilizada, pero probablemente deberías pensar de nuevo, ¿REALMENTE? ¿necesito?


El mejor uso para Tuples que he encontrado es cuando se necesita devolver más de 1 tipo de objeto de un método, usted sabe qué tipos de objetos y número serán, y no es una lista larga.

Otras alternativas simples usarían un parámetro ''out''

private string MyMethod(out object)

o hacer un diccionario

Dictionary<objectType1, objectType2>

Sin embargo, usar un Tuple ahorra la creación del objeto ''out'' o la necesidad de buscar esencialmente la entrada en el diccionario;


Hay un excelente artículo en la revista MSDN que habla sobre las consideraciones de dolor y de estómago que entraron en agregar Tuple al BCL. Elegir entre un tipo de valor y un tipo de referencia es particularmente interesante.

Como el artículo deja en claro, la fuerza impulsora detrás de Tuple era que muchos grupos dentro de Microsoft tenían un uso para ello, el equipo F # por adelantado. Aunque no se menciona, creo que la nueva palabra clave "dinámica" en C # (y VB.NET) también tuvo algo que ver con esto, las tuplas son muy comunes en los lenguajes dinámicos.

De lo contrario, no es especialmente superior a crear tu propio poco, al menos puedes darles un nombre mejor a los miembros.

ACTUALIZACIÓN: debido a una gran revisión en C # versión 7, ahora obteniendo mucho más sintaxis de amor. Anuncio preliminar en esta publicación de blog .


La sintaxis de tupla de C # es ridículamente voluminosa, por lo que las tuplas son dolorosas de declarar. Y no tiene coincidencia de patrones, por lo que también son dolorosos de usar.

Pero ocasionalmente, solo quiere una agrupación de objetos ad-hoc sin crear una clase para ello. Por ejemplo, digamos que quería agregar una lista, pero quería dos valores en lugar de uno:

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

En lugar de combinar una colección de valores en un único resultado, expandamos un solo resultado a una colección de valores. La forma más fácil de escribir esta función es:

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; } }

f convierte un estado en una tupla. Devolvemos el primer valor de la tupla y establecemos nuestro nuevo estado en el segundo valor. Esto nos permite retener el estado a lo largo del cálculo.

Lo usas como tal:

// 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 es bastante sencillo, pero fibs es un poco más inteligente. Su state es en realidad una tupla que contiene fib (n-2) y fib (n-1), respectivamente.


Las tuplas se usan mucho en los lenguajes funcionales que pueden hacer más cosas con ellos, ahora F # es un lenguaje .net ''oficial'', puede que quiera interoperar con él desde C # y pasarlo entre códigos escritos en dos idiomas.


Las tuplas son geniales para realizar múltiples operaciones asíncronas IO a la vez y devolver todos los valores juntos. Aquí están los ejemplos de hacerlo con y sin Tuple. ¡Tuples puede hacer que tu código sea más claro!

Sin (desagradable anidación!):

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); } }); } }); } });

Con 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 estaba usando una función anónima con un tipo implícito de todos modos, entonces no está haciendo que el código sea menos claro al usar la Tupla. Retuning un Tuple de un método? Úselo con moderación cuando la claridad del código es clave, en mi humilde opinión. Sé que la programación funcional en C # es difícil de resistir, pero tenemos que considerar todos esos viejos programadores de C # molestos "orientados a objetos".


No me gusta el abuso de ellos, ya que producen código que no se explica a sí mismo, pero son geniales para implementar claves compuestas sobre la marcha, ya que implementan IStructuralEquatable e IStructuralComparable (para usar tanto para buscar como para ordenar propósitos).

Y combinan todos los códigos de hash de sus artículos, internamente; por ejemplo, aquí está el GetHashCódigo de Tuple (tomado 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)); }


Solo para creación de prototipos: las tuplas no tienen sentido. Es conveniente usarlos, ¡pero es un atajo solamente! Para prototipos, bien. Solo asegúrate de borrar este código más tarde.

Es fácil de escribir, difícil de leer. No tiene ventajas visibles sobre clases, clases internas, clases anónimas, etc.


Tiendo a evitar Tuple para la mayoría de los escenarios, ya que daña la legibilidad. Sin embargo, Tuple es útil cuando necesita agrupar datos no relacionados.

Por ejemplo, supongamos que tiene una lista de automóviles y las ciudades en las que se compraron:

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

Desea agregar los recuentos para cada auto por ciudad:

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

Para hacer esto, creas un Dictionary . Tienes pocas opciones:

  1. Crear un Dictionary<string, Dictionary<string, int>> .
  2. Crea un Dictionary<CarAndCity, int> .
  3. Cree un Dictionary<Tuple<string, string>, int> .

La legibilidad se pierde con la primera opción. Te requerirá escribir mucho más código.

La segunda opción funciona y es sucinta, pero el automóvil y la ciudad no están realmente relacionados y probablemente no pertenezcan a una clase juntos.

La tercera opción es sucinta y limpia. Es un buen uso de Tuple .


Un parámetro de salida es excelente cuando solo se deben devolver unos pocos valores, pero cuando comienza a encontrar 4, 5, 6 o más valores que deben devolverse, puede resultar difícil de manejar. Otra opción para devolver varios valores es crear y devolver una clase / estructura definida por el usuario o usar una Tupla para empaquetar todos los valores que un método debe devolver.

La primera opción, usar una clase / estructura para devolver los valores, es directa. Simplemente cree el tipo (en este ejemplo es una estructura) como ese:

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

La segunda opción, usar un Tuple, es una solución aún más elegante que usar un objeto definido por el usuario. Se puede crear una Tuple para contener cualquier cantidad de valores de distintos tipos. Además, la información que almacena en el Tuple es inmutable; una vez que agregue los datos al Tuple mediante el constructor o el método de Creación estático, esos datos no se pueden cambiar. Tuples puede aceptar hasta ocho valores separados. Si necesita devolver más de ocho valores, necesitará usar la clase Tuple especial: Clase Tuple Al crear una Tupla con más de ocho valores, no puede usar el método estático Create; en su lugar, debe usar el constructor de la clase. Así es como crearía una Tupla de 10 valores enteros:

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));

Por supuesto, puede continuar agregando más Tuplas al final de cada Tupla incrustada, creando cualquier Tuple de tamaño que necesite.


Usé una tupla para resolver el problema 11 del Proyecto 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 } }

Ahora puedo resolver todo el problema con:

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); } }

Podría haber usado un tipo personalizado para esto, pero se habría visto exactamente como Tuple .


Ese es el punto: es más conveniente no hacer una clase o estructura personalizada todo el tiempo. Es una mejora como Action o Func ... puede hacer esto usted mismo tipos, pero es conveniente que existan en el marco.