¿Ejemplo práctico en el que se puede usar Tuple en .Net 4.0?

97

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

Amitabh
fuente
13
Quizás este sea un buen lugar para señalar que este tema es muy antiguo. ¡Mira lo que pasó en C # 7 !
maf-soft

Respuestas:

83

Ese es el punto: es más conveniente no hacer una clase o estructura personalizada todo el tiempo. Es una mejora como Actiono Func... puedes hacer estos tipos tú mismo, pero es conveniente que existan en el marco.

tanascius
fuente
5
Probablemente valga la pena señalar de MSDN : "Cualquier miembro estático público de este tipo es seguro para subprocesos. No se garantiza que ningún miembro de instancia sea seguro para subprocesos "
Matt Borja
Bueno, tal vez los diseñadores de idiomas deberían haber facilitado la creación de tipos personalizados sobre la marcha, entonces. ¿Entonces podríamos mantener la misma sintaxis en lugar de introducir otra?
Thomas Eyde
@ThomasEyde, que es exactamente lo que han estado haciendo durante los últimos 15 años. Así es como se agregaron miembros con cuerpo de expresión y ahora tuplas de valor. Clases de estilo de grabación estuvo a punto de la parte posterior corte en agosto (9 meses antes de este comentario) para esta versión de C # 7, y probablemente están saliendo en C # 8. También tenga en cuenta que el valor tuplas ofrecen igualdad de valor donde las clases llanura de edad, no lo hacen . Introducir todo esto en 2002 requeriría presciencia
Panagiotis Kanavos
a qué te refieres con convenient not to make a custom class. ¿Te refieres a crear una instancia de clase personalizada usando =new..()?
Alex
75

Con tuplas, podría implementar fácilmente un diccionario bidimensional (o n-dimensional para el caso). Por ejemplo, podría usar un diccionario de este tipo para implementar una asignación 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
MarioVW
fuente
Prefiero usar un Enum para las abreviaturas de países. ¿Es eso posible?
Zack
1
Seguro, debería funcionar sin problemas ya que las enumeraciones son tipos de valor.
MarioVW
@MarioVW También se podría lograr utilizando una matriz multidimensional. ¿Cómo la tupla hace la diferencia?
Alex
26

Hay un excelente articulo en la revista MSDN que habla sobre el dolor de estómago y las consideraciones de diseño que se utilizaron para agregar Tuple al BCL. Es especialmente interesante elegir entre un tipo de valor y un tipo de referencia.

Como deja en claro el artículo, la fuerza impulsora detrás de Tuple fueron tantos grupos dentro de Microsoft que lo usaban, el equipo F # al frente. Aunque no se menciona, creo que la nueva palabra clave "dinámica" en C # (y VB.NET) también tuvo algo que ver, las tuplas son muy comunes en los lenguajes dinámicos.

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


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

Hans Passant
fuente
23

Aquí hay un pequeño ejemplo: digamos que tiene un método que necesita buscar el identificador y la dirección de correo electrónico de un usuario, dada una identificación de usuario. Siempre puede crear una clase personalizada que contenga esos datos, o usar un parámetro ref / out para esos datos, o simplemente puede devolver una Tupla y tener una firma de método agradable 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");
}
Tejs
fuente
10
Este es un buen ejemplo, sin embargo, no justifica el uso de Tuple.
Amitabh
7
Una tupla es un ajuste decente aquí ya que está devolviendo valores distintos; pero una tupla brilla más cuando devuelve varios valores de diferentes tipos .
Mark Rushakoff
21
Otro buen ejemplo sería int. TryParse, ya que podría eliminar el parámetro de salida y, en su lugar, usar una Tupla. Entonces podría haberlo hecho Tuple<bool, T> TryParse<T>(string input)y en lugar de tener que usar un parámetro de salida, obtiene ambos valores en una tupla.
Tejs
3
de hecho, eso es exactamente lo que sucede cuando llama a cualquier método TryParse desde F #.
Joel Mueller
23

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

Yo podría haber utilizado un tipo personalizado para esto, pero hubiera mirado exactamente como tupla .

Craig Stuntz
fuente
16

La sintaxis de tuplas 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 desea una agrupación ad-hoc de objetos sin crear una clase para ella. 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 solo resultado, expandiremos un solo resultado en una colección de valores. La forma más sencilla 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;
    }
}

fconvierte algún 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 durante todo el 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();

evenses bastante sencillo, pero fibses un poco más inteligente. En staterealidad, es una tupla que contiene fib (n-2) y fib (n-1) respectivamente.

Julieta
fuente
4
+1 Tuple.Create es una abreviatura útil paranew Tuple<Guid,string,...>
AaronLS
@Juliet Lo que el uso de la palabra clave Estado
irfandar
7

No me gusta el abuso de ellos, ya que producen código que no se explica por sí mismo, pero es increíble implementar claves compuestas sobre la marcha, ya que implementan IStructuralEquatable e IStructuralComparable (para usar tanto para búsquedas como para ordenar propósitos).

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

Las tuplas son excelentes para realizar varias operaciones de E / S asíncronas a la vez y devolver todos los valores juntos. Aquí están los ejemplos de cómo hacerlo con y sin Tuple. ¡Las tuplas pueden hacer que tu código sea más claro!

Sin (¡desagradable anidamiento!):

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 tupla

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 de todos modos estaba usando una función anónima con un tipo implícito, entonces no está haciendo que el código sea menos claro al usar Tuple. ¿Volver a sintonizar una tupla desde 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 y torpes programadores de C # "orientados a objetos".

AndyClaw
fuente
5

Las tuplas se utilizan mucho en lenguajes funcionales que pueden hacer más cosas con ellas, ahora F # es un lenguaje .net 'oficial', es posible que desee interoperar con él desde C # y pasarlas entre el código escrito en dos lenguajes.

Mant101
fuente
Las tuplas también están integradas en tipos para algunos lenguajes de scripting populares como Python y Ruby (que también tienen implementaciones .Net para interoperabilidad ... IronPython / Ruby).
MutantNinjaCodeMonkey
5

Tiendo a evitarlo Tupleen la mayoría de los escenarios, ya que perjudica la legibilidad. Sin embargo, Tuplees útil cuando necesita agrupar datos no relacionados.

Por ejemplo, suponga 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 sumar los recuentos de cada automóvil por ciudad:

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

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

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

La legibilidad se pierde con la primera opción. Requerirá que escriba mucho más código.

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

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

John Kurlak
fuente
4

Algunos ejemplos que se me vienen a la cabeza:

  • Una ubicación X e Y (y Z si lo desea)
  • a Ancho y Alto
  • Cualquier cosa medida en el tiempo

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

James Westgate
fuente
2
Tuplees tan útil en situaciones críticas como Point, o SomeVectorpuede ser útil al realizar operaciones gráficas. Destaca dos valores que están muy ligados entre sí. A menudo veo propiedades llamadas StartTime y EndTime al leer el código, pero incluso mejor que una fecha, una hora y una duración, una tupla te obliga a considerar ambos valores cada vez que operas en esta área de tu lógica empresarial. O devolviendo "hey, los datos han cambiado (bool), aquí están los datos (otro tipo)" en un procesamiento pesado en lugar de pasar por enlaces intensivos.
Léon Pelletier
3

Debe tener mucho cuidado con el uso Tupley probablemente pensarlo dos veces antes de hacer esto. De mi experiencia anterior descubrí que usarTuple código dificulta mucho la lectura y el soporte en el futuro. Hace un tiempo, tuve que arreglar un código donde se usaban tuplas en casi todas partes. En lugar de pensar en modelos de objetos adecuados, solo 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 Tupley es malo o algo así y estoy cien por ciento seguro de que hay algunas tareas en las que Tuplees el mejor candidato para ser usado, pero probablemente deberías pensarlo de nuevo, ¿REALMENTE lo necesitas? ?

Sr. Calabaza
fuente
1

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

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

private string MyMethod(out object)

o haciendo un diccionario

Dictionary<objectType1, objectType2>

Sin embargo, el uso de una tupla ahorra la creación del objeto "out" o tener que buscar esencialmente la entrada en el diccionario;

sidjames
fuente
1

Acabo de encontrar la solución a uno de mis problemas en Tuple. Es como declarar una clase en el alcance de un método, pero con una declaración diferida de los nombres de sus 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 le evita crear la nueva clase para este propósito.

La tarea es escribir una respuesta JSON desde LINQ sin clases adicionales:

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

Por supuesto, podríamos hacer esto declarando una nueva clase para mis grupos, pero la idea de crear colecciones anónimas sin declarar nuevas clases.

Alex
fuente
1

Bueno, en mi caso, tuve que usar una Tupla cuando descubrí que no podemos usar nuestro parámetro en un método asincrónico. Lea sobre esto aquí . También necesitaba un tipo de devolución diferente. Entonces usé una Tupla como mi tipo de retorno y marqué el método como asíncrono.

Código de muestra 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.

Amogh Natu
fuente
Supongo que no desea devolver un tipo anónimo o matriz (o contenido dinámico)
ozzy432836
0

Cambiar las formas de los objetos cuando necesita enviarlos a través de un cable o pasar a diferentes capas de aplicación y varios objetos se fusionan en uno:

Ejemplo:

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

Método de extensión:

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
fuente
0

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

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

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

La segunda opción, usar una tupla, es una solución aún más elegante que usar un objeto definido por el usuario. Se puede crear una tupla para contener cualquier número de valores de diferentes tipos. Además, los datos que almacena en Tuple son inmutables; una vez que agrega los datos a la Tupla a través del constructor o el método Create estático, esos datos no se pueden cambiar. Las tuplas pueden aceptar hasta ocho valores separados inclusive. Si necesita devolver más de ocho valores, deberá usar la clase Tuple especial: Clase Tuple Cuando cree una Tuple con más de ocho valores, no puede usar el método Create estático; 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 Tuplas de cualquier tamaño que necesite.

Yashwanth Chowdary Kata
fuente
0

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

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

bunny1985
fuente
0

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 resultar complicado al realizar mapas, etc.

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

Parece que se ha creado un DTO de forma gratuita con solo usar un Tuple y puedo acceder a todas las propiedades usando itemN, que se siente más como una escritura estática 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 };
            }
        }
    }
}
ozzy432836
fuente