tuple ejemplo c# .net tuples application-design

ejemplo - c# 8



¿Usar Tuplas.NET 4.0 en mi C#Code es una decisión de diseño deficiente? (12)

Con la adición de la clase Tuple en .net 4, he estado tratando de decidir si usarlos en mi diseño es una mala elección o no. Tal como lo veo, un Tuple puede ser un atajo para escribir una clase de resultado (estoy seguro de que también hay otros usos).

Así que esto:

public class ResultType { public string StringValue { get; set; } public int IntValue { get; set; } } public ResultType GetAClassedValue() { //..Do Some Stuff ResultType result = new ResultType { StringValue = "A String", IntValue = 2 }; return result; }

Es equivalente a esto:

public Tuple<string, int> GetATupledValue() { //...Do Some stuff Tuple<string, int> result = new Tuple<string, int>("A String", 2); return result; }

Así que dejando a un lado la posibilidad de que me esté faltando el punto de Tuples, ¿es el ejemplo con una Tuple una mala elección de diseño? Para mí, parece menos desordenado, pero no tan auto documentado y limpio. Lo que significa que con el tipo ResultType , queda muy claro más adelante qué significa cada parte de la clase, pero usted tiene un código adicional para mantener. Con Tuple<string, int> , tendrá que mirar hacia arriba y descubrir qué representa cada Item , pero usted escribe y mantiene menos código.

Cualquier experiencia que haya tenido con esta elección sería muy apreciada.


Tal como lo veo, una Tuple es un atajo para escribir una clase de resultados (estoy seguro de que también hay otros usos).

De hecho, hay otros usos valiosos para Tuple<> : la mayoría de ellos implican abstraer la semántica de un grupo particular de tipos que comparten una estructura similar, y tratarlos simplemente como un conjunto ordenado de valores. En todos los casos, un beneficio de las tuplas es que evitan saturar su espacio de nombres con clases solo de datos que exponen propiedades pero no métodos.

Aquí hay un ejemplo de uso razonable para Tuple<> :

var opponents = new Tuple<Player,Player>( playerBob, playerSam );

En el ejemplo anterior, queremos representar un par de oponentes, una tupla es una forma conveniente de emparejar estas instancias sin tener que crear una nueva clase. Aquí hay otro ejemplo:

var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );

Una mano de póker puede considerarse simplemente como un conjunto de cartas, y una tupla (puede ser) una forma razonable de expresar ese concepto.

dejando de lado la posibilidad de que me esté faltando el punto de Tuples, ¿es el ejemplo con una Tuple una mala elección de diseño?

No es una buena idea Tuple<> instancias Tuple<> fuertemente tipadas como parte de una API pública para un tipo público. Como usted mismo reconoce, las tuplas requieren que las partes involucradas (autor de la biblioteca, usuario de la biblioteca) acuerden de antemano el propósito y la interpretación de los tipos de tuplas que se utilizan. Es lo suficientemente desafiante para crear API que sean intuitivas y claras, usar Tuple<> públicamente solo oscurece la intención y el comportamiento de la API.

Los tipos anónimos también son un tipo de tupla ; sin embargo, están fuertemente tipados y le permiten especificar nombres claros e informativos para las propiedades que pertenecen al tipo. Pero los tipos anónimos son difíciles de usar a través de diferentes métodos: se agregaron principalmente a tecnologías de soporte como LINQ donde las proyecciones producirían tipos a los que normalmente no desearíamos asignar nombres. (Sí, sé que el compilador consolida los tipos anónimos con los mismos tipos y propiedades nombradas).

Mi regla de oro es: si la devuelve desde su interfaz pública, conviértala en un tipo con nombre .

Mi otra regla de oro para usar tuplas es: nombrar argumentos de método y variables de localc de tipo Tuple<> tan claramente como sea posible: haga que el nombre represente el significado de las relaciones entre los elementos de la tupla. Piensa en mis var opponents = ... ejemplo.

Aquí hay un ejemplo de un caso en el mundo real donde he usado Tuple<> para evitar declarar un tipo solo de datos para usarlo solo dentro de mi propio ensamblaje . La situación implica el hecho de que cuando se utilizan diccionarios genéricos que contienen tipos anónimos, es difícil utilizar el método TryGetValue() para buscar elementos en el diccionario porque el método requiere un parámetro de out que no se puede nombrar:

public static class DictionaryExt { // helper method that allows compiler to provide type inference // when attempting to locate optionally existent items in a dictionary public static Tuple<TValue,bool> Find<TKey,TValue>( this IDictionary<TKey,TValue> dict, TKey keyToFind ) { TValue foundValue = default(TValue); bool wasFound = dict.TryGetValue( keyToFind, out foundValue ); return Tuple.Create( foundValue, wasFound ); } } public class Program { public static void Main() { var people = new[] { new { LastName = "Smith", FirstName = "Joe" }, new { LastName = "Sanders", FirstName = "Bob" } }; var peopleDict = people.ToDictionary( d => d.LastName ); // ??? foundItem <= what type would you put here? // peopleDict.TryGetValue( "Smith", out ??? ); // so instead, we use our Find() extension: var result = peopleDict.Find( "Smith" ); if( result.First ) { Console.WriteLine( result.Second ); } } }

PD: hay otra forma (más simple) de evitar los problemas que surgen de los tipos anónimos en los diccionarios, y es usar la palabra clave var para permitir que el compilador "deduzca" el tipo para usted. Aquí está esa versión:

var foundItem = peopleDict.FirstOrDefault().Value; if( peopleDict.TryGetValue( "Smith", out foundItem ) ) { // use foundItem... }


¿Qué hay de usar Tuples en un patrón decorate-sort-undecorate? (Transformación de Schwartz para la gente de Perl). Aquí hay un ejemplo inventado, sin duda, pero las Tuplas parecen ser una buena forma de manejar este tipo de cosas:

namespace ConsoleApplication1 { class Program { static void Main(string[] args) { string[] files = Directory.GetFiles("C://Windows") .Select(x => new Tuple<string, string>(x, FirstLine(x))) .OrderBy(x => x.Item2) .Select(x => x.Item1).ToArray(); } static string FirstLine(string path) { using (TextReader tr = new StreamReader( File.Open(path, FileMode.Open))) { return tr.ReadLine(); } } } }

Ahora, podría haber usado un Objeto [] de dos elementos o en este ejemplo específico una cadena [] de dos elementos. El punto es que podría haber usado algo como el segundo elemento en una Tuple que se usa internamente y es bastante fácil de leer.


Depende, por supuesto! Como dijo, una tupla puede ahorrarle código y tiempo cuando desee agrupar algunos elementos para el consumo local. También puede usarlos para crear algoritmos de procesamiento más genéricos que los que puede si pasa una clase concreta. No recuerdo cuántas veces deseé tener algo más que KeyValuePair o DataRow para pasar rápidamente una fecha de un método a otro.

Por otro lado, es muy posible exagerar y pasar tuplas donde solo puedes adivinar lo que contienen. Si va a utilizar una tupla en todas las clases, quizás sería mejor crear una clase concreta.

Utilizado con moderación, por supuesto, las tuplas pueden conducir a un código más conciso y legible. Puede consultar C ++, STL y Boost para ver ejemplos de cómo se usan Tuplas en otros idiomas, pero al final, todos tendremos que experimentar para encontrar la mejor forma de encajar en el entorno .NET.


IMO estas "tuplas" son básicamente todos los tipos de struct anónimas de acceso público con miembros sin nombre .

El único lugar donde utilizaría la tupla es cuando necesitas juntar rápidamente algunos datos, en un ámbito muy limitado. La semántica de los datos debería ser obvia , por lo que el código no es difícil de leer. Entonces, usar una tupla ( int , int ) para (row, col) parece razonable. Pero estoy en apuros para encontrar una ventaja sobre una struct con miembros nombrados (por lo que no se cometen errores y la fila / columna no se intercambian accidentalmente)

Si envía datos de vuelta a la persona que llama, o si acepta datos de una persona que llama, realmente debería utilizar una struct con miembros nombrados.

Tome un ejemplo simple:

struct Color{ float r,g,b,a ; } public void setColor( Color color ) { }

La versión de tupla

public void setColor( Tuple<float,float,float,float> color ) { // why? }

No veo ninguna ventaja al usar tuple en el lugar de una estructura con miembros nombrados. Usar miembros sin nombre es un paso atrás para la legibilidad y comprensibilidad de su código.

Tuple me parece una forma perezosa de evitar crear una struct con miembros reales nombrados. El uso excesivo de la tupla, donde realmente sientes que tú o alguien más que encuentre tu código necesitaría miembros con nombre es A Bad Thing ™ si alguna vez viera alguno.


Las tuplas pueden ser útiles ... pero también pueden ser un problema después. Si tiene un método que devuelve Tuple<int,string,string,int> ¿cómo sabe cuáles son esos valores más adelante? ¿Eran ID, FirstName, LastName, Age o eran UnitNumber, Street, City, ZipCode ?


Las tuplas son geniales si controlas tanto la creación como el uso: puedes mantener el contexto, lo cual es esencial para entenderlas.

En una API pública, sin embargo, son menos efectivos. El consumidor (no usted) tiene que adivinar o consultar la documentación, especialmente para cosas como Tuple<int, int> .

Los usaría para miembros privados / internos, pero use clases de resultados para miembros públicos / protegidos.

Esta respuesta también tiene algo de información.


Las tuplas son una adición bastante decepcionante al CLR desde la perspectiva de un programador de C #. Si tiene una colección de elementos que varían en longitud, no necesita que tengan nombres estáticos únicos en tiempo de compilación.

Pero si tiene una colección de longitud constante, esto implica que las ubicaciones fijas de la colección tienen cada una un significado predefinido específico. Y siempre es mejor darles nombres estáticos apropiados en ese caso, en lugar de tener que recordar la importancia de Item2 , Item2 , etc.

Las clases anónimas en C # ya proporcionan una solución excelente para el uso privado más común de las tuplas, y dan nombres significativos a los artículos, por lo que son realmente superiores en ese sentido. El único problema es que no pueden escapar de los métodos nombrados. Preferiría ver que se levantara esa restricción (quizás solo para métodos privados) que tener soporte específico para tuplas en C #:

private var GetDesserts() { return _icecreams.Select( i => new { icecream = i, topping = new Topping(i) } ); } public void Eat() { foreach (var dessert in GetDesserts()) { dessert.icecream.AddTopping(dessert.topping); dessert.Eat(); } }


Las tuplas son una función de marco inútil en .NET 4. Creo que se perdió una gran oportunidad con C # 4.0. Me hubiera encantado tener tuplas con miembros nombrados, por lo que podría acceder a los diversos campos de una tupla por nombre en lugar de Value1 , Value2 , etc ...

Habría requerido un cambio de lenguaje (sintaxis), pero hubiera sido muy útil.


No me juzgues, no soy un experto, pero con las nuevas Tuples en C # 7.x ahora, podrías devolver algo como:

return (string Name1, int Name2)

Al menos ahora puedes ponerle un nombre y los desarrolladores pueden ver algo de información.


Personalmente nunca usaría una Tupla como un tipo de devolución porque no hay indicación de lo que representan los valores. Las tuplas tienen algunos usos valiosos porque, a diferencia de los objetos, son tipos de valores y, por lo tanto, entienden la igualdad. Debido a esto, los usaré como claves de diccionario si necesito una clave multiparte o como clave para una cláusula GroupBy si quiero agrupar por múltiples variables y no quiero agrupaciones anidadas (¿Quién quiere agrupaciones anidadas?). Para superar el problema con extrema verbosidad, puede crearlos con un método de ayuda. Tenga en cuenta que si está accediendo con frecuencia a miembros (a través de Item1, Item2, etc.) entonces probablemente debería usar una construcción diferente como una estructura o una clase anónima.


Similar a la palabra clave var , tiene la intención de ser una conveniencia, pero se utiliza con la misma facilidad.

En mi opinión más humilde, no expongas a Tuple como una clase de retorno. Úselo de forma privada, si la estructura de datos de un servicio o componente lo requiere, pero devuelva clases conocidas bien formadas de métodos públicos.

// one possible use of tuple within a private context. would never // return an opaque non-descript instance as a result, but useful // when scope is known [ie private] and implementation intimacy is // expected public class WorkflowHost { // a map of uri''s to a workflow service definition // and workflow service instance. By convention, first // element of tuple is definition, second element is // instance private Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> _map = new Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> (); }


Usar una clase como ResultType es más clara. Puede dar nombres significativos a los campos de la clase (mientras que con una tupla se llamarían Item2 y Item2 ). Esto es aún más importante si los tipos de los dos campos son los mismos: el nombre distingue claramente entre ellos.