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. A mi modo de ver, una Tupla 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;
}
Entonces, dejando a un lado la posibilidad de que me esté perdiendo el punto de Tuples, ¿es el ejemplo con una Tuple una mala elección de diseño? Para mí, parece menos desorden, pero no tan autodocumentado y limpio. Lo que significa que con el tipo ResultType
, queda muy claro más adelante qué significa cada parte de la clase, pero tiene que mantener un código adicional. Con el Tuple<string, int>
tendrá que buscar y descubrir qué Item
representa cada uno , pero escribe y mantiene menos código.
Cualquier experiencia que haya tenido con esta elección sería muy apreciada.
fuente
Respuestas:
Las tuplas son geniales si controlas tanto la creación como el uso de ellas; puedes mantener el contexto, que es esencial para comprenderlas.
En una API pública, sin embargo, son menos efectivos. El consumidor (no usted) tiene que adivinar o buscar documentación, especialmente para cosas como
Tuple<int, int>
.Los usaría para miembros privados / internos, pero usar clases de resultados para miembros públicos / protegidos.
Esta respuesta también tiene algo de información.
fuente
StringOps
(básicamente una clase de "métodos de extensión" para JavaString
) tiene un métodoparition(Char => Boolean): (String, String)
(toma en predicado, devuelve una tupla de 2 cadenas). Es obvio cuál es el resultado (obviamente, uno será los caracteres para los que el predicado fue verdadero y el otro para los que fue falso, pero no está claro cuál es cuál). Es la documentación lo que aclara esto (y la coincidencia de patrones se puede usar para aclarar en el uso cuál es cuál).De hecho, existen otros usos valiosos
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 abarrotar su espacio de nombres con clases de solo datos que exponen propiedades pero no métodos.Aquí hay un ejemplo de uso razonable para
Tuple<>
: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:
Una mano de póker puede considerarse como un juego de cartas, y la tupla (puede ser) una forma razonable de expresar ese concepto.
Devolver
Tuple<>
instancias fuertemente tipadas como parte de una API pública para un tipo público rara vez es una buena idea. 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 crear API que sean intuitivas y claras, su usoTuple<>
público solo oscurece la intención y el comportamiento de la API.Los tipos anónimos también son una especie 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 en diferentes métodos: se agregaron principalmente para admitir tecnologías como LINQ, donde las proyecciones producirían tipos a los que normalmente no quisiéramos asignar nombres. (Sí, sé que el compilador consolida los tipos anónimos con los mismos tipos y propiedades con nombre).
Mi regla general es: si lo devolverá desde su interfaz pública, hágalo como un tipo con nombre .
Mi otra regla general para usar tuplas es: nombrar los argumentos del método y las variables de tipo localc con la
Tuple<>
mayor claridad posible: hacer que el nombre represente el significado de las relaciones entre los elementos de la tupla. Piensa en mivar opponents = ...
ejemplo.Aquí hay un ejemplo de un caso del mundo real en el que solía
Tuple<>
evitar declarar un tipo de solo datos para usar solo dentro de mi propio ensamblado . La situación implica el hecho de que cuando se usan diccionarios genéricos que contienen tipos anónimos, se hace difícil usar elTryGetValue()
método para encontrar elementos en el diccionario porque el método requiere unout
parámetro que no se puede nombrar:PD: Hay otra forma (más simple) de solucionar los problemas que surgen de los tipos anónimos en los diccionarios, y es usar la
var
palabra clave para permitir que el compilador 'infiera' el tipo por usted. Aquí está esa versión:fuente
Tuple<>
podría tener sentido. Siempre hay alternativas ...Las tuplas pueden ser útiles ... pero también pueden ser un dolor más tarde. Si tiene un método que devuelve,
Tuple<int,string,string,int>
¿cómo sabe cuáles son esos valores más adelante? ¿EranID, FirstName, LastName, Age
o eran ellosUnitNumber, Street, City, ZipCode
?fuente
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ía en longitud, no necesita que tengan nombres estáticos únicos en el momento de la compilación.
Pero si tiene una colección de longitud constante, esto implica que las ubicaciones fijas de la colección tienen un significado predefinido específico. Y es siempre mejor darles nombres estáticos apropiados en ese caso, en lugar de tener que recordar el significado de
Item1
,Item2
, etc.Las clases anónimas en C # ya proporcionan una excelente solución para el uso privado más común de las tuplas, y dan nombres significativos a los elementos, por lo que en realidad son superiores en ese sentido. El único problema es que no pueden filtrarse de los métodos con nombre. Prefiero ver que se levante esa restricción (quizás solo para métodos privados) que tener soporte específico para tuplas en C #:
fuente
struct var SimpleStruct {int x, int y;}
, defina una estructura con un nombre que comience con un guid asociado con estructuras simples y tiene algo como{System.Int16 x}{System.Int16 y}
adjunto; se requeriría cualquier nombre que comience con ese GUID para representar una estructura que contenga solo esos elementos y contenidos específicos particulares; Si varias definiciones para el mismo nombre están dentro del alcance y coinciden, todas se considerarían del mismo tipo.ref
parámetro, la única forma en que será posible tener un delegado que se pueda pasar a ambas funciones será si la persona produce y compila un delega el tipo y se lo da al otro para construir. No hay forma de que ambas personas puedan indicar que los dos tipos deberían considerarse sinónimos.Tuple
? Acabo de pasar por 50 lugares en mi base de código donde estoy usando tuplas, y todos son valores de retorno (típicamenteyield return
) o son elementos de colecciones que se usan en otros lugares, así que no busques clases anónimas.Similar a la palabra clave
var
, pretende ser una conveniencia, pero se abusa con tanta facilidad.En mi opinión más humilde, no exponer
Tuple
como una clase de retorno. Úselo en privado, si la estructura de datos de un servicio o componente lo requiere, pero devuelva clases bien formadas y conocidas de métodos públicos.fuente
var
no puede ser devuelto o existir fuera de su alcance declarado. sin embargo, todavía es posible usarlo en exceso de su propósito previsto y ser "abusado"Usar una clase como
ResultType
es más claro. Puede dar nombres significativos a los campos de la clase (mientras que con una tupla se llamaríanItem1
yItem2
). Esto es aún más importante si los tipos de los dos campos son iguales: el nombre distingue claramente entre ellos.fuente
¿Qué tal usar Tuples en un patrón decorar-ordenar-decorar? (Transformación Schwartziana para el pueblo Perl). Aquí hay un ejemplo artificial, sin duda, pero las Tuplas parecen ser una buena manera de manejar este tipo de cosas:
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 cualquier cosa como el segundo elemento en una Tuple que se usa internamente y es bastante fácil de leer.
fuente
En mi opinión, estas "tuplas" son básicamente todos los
struct
tipos anónimos de acceso público con miembros sin nombre .El único lugar donde usaría tupla es cuando necesita juntar rápidamente algunos datos, en un alcance muy limitado. La semántica de los datos debe ser obvia , por lo que el código no es difícil de leer. Entonces, usar una tupla (
int
,int
) para (fila, col) parece razonable. Pero me cuesta encontrar una ventaja sobre losstruct
miembros nombrados (por lo que no se cometen errores y la fila / columna no se intercambian accidentalmente)Si está enviando datos de vuelta a la persona que llama, o está aceptando datos de una persona que llama, realmente debería usar un
struct
con miembros nombrados.Tome un ejemplo simple:
La versión tupla
No veo ninguna ventaja en usar tuplas en lugar de una estructura con miembros nombrados. El uso de miembros sin nombre es un paso atrás para la legibilidad y la comprensión 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ú / alguien que encuentra tu código necesitaría miembros nombrados es A Bad Thing ™ si alguna vez viera uno.fuente
No me juzguen, no soy un experto, pero con las nuevas Tuplas en C # 7.x ahora, podría devolver algo como:
Al menos ahora puede nombrarlo y los desarrolladores pueden ver alguna información.
fuente
Depende, por supuesto! Como dijiste, una tupla puede ahorrarte código y tiempo cuando deseas agrupar algunos artículos para el consumo local. También puede usarlos para crear algoritmos de procesamiento más genéricos que si pasa una clase concreta. No recuerdo cuántas veces he deseado tener algo más allá de KeyValuePair o DataRow para pasar rápidamente alguna fecha de un método a otro.
Por otro lado, es bastante posible exagerar y pasar tuplas donde solo puedes adivinar lo que contienen. Si va a utilizar una tupla en todas las clases, tal vez 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 las tuplas en otros lenguajes, pero al final, todos tendremos que experimentar para encontrar la mejor forma en el entorno .NET.
fuente
Las tuplas son una característica 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, para que puedas acceder a los diversos campos de una tupla por nombre en lugar de Value1 , Value2 , etc.
Hubiera requerido un cambio de idioma (sintaxis), pero habría sido muy útil.
fuente
let success, value = MethodThatReturnsATuple()
Yo personalmente nunca usaría una Tupla como un tipo de retorno 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 valor y, por lo tanto, comprenden 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 auxiliar. Tenga en cuenta que si está accediendo con frecuencia a miembros (a través de Item1, Item2, etc.), probablemente debería usar una construcción diferente, como una estructura o una clase anónima.
fuente
He usado tuplas, tanto las
Tuple
nuevas como las nuevasValueTuple
, en varios escenarios diferentes y llegué a la siguiente conclusión: no usar .Cada vez, me encontré con los siguientes problemas:
Mi opinión es que las tuplas son un detrimento, no una característica, de C #.
Tengo una crítica algo similar, pero mucho menos dura, de
Func<>
yAction<>
. Esos son útiles en muchas situaciones, especialmente las simplesAction
y lasFunc<type>
variantes, pero cualquier cosa más allá de eso, he descubierto que crear un tipo de delegado es más elegante, legible, mantenible y le brinda más funciones, comoref
/out
parámetros.fuente
ValueTuple
, y tampoco me gustan. Primero, el nombre no es fuerte, es más una sugerencia, muy fácil de confundir porque se puede asignar implícitamente a cualquieraTuple
oValueTuple
con el mismo número y tipo de elementos. En segundo lugar, la falta de herencia y la necesidad de copiar y pegar toda la definición de un lugar a otro siguen siendo perjudiciales.