create - new features c#
Cuándo usar: Tuple vs Class c#7.0 (9)
Como esta respuesta está causando cierta confusión entre algunas personas aquí, debo aclarar que, según la pregunta, todas las referencias a "tupla" aquí se refieren al tipo ValueTuple
y a las nuevas características de azúcar sintáctica de tupla de C # 7 y de ninguna manera se refieren al viejos tipos de referencia System.Tuple
.
Entonces ahora me pregunto, ¿cuándo debería usar tuplas y cuándo debería crear una clase en c # 7.0?
Solo tú puedes responder esa pregunta realmente, ya que realmente depende de tu código.
Sin embargo, existen pautas y reglas que puede seguir para guiarlo a elegir entre ellas:
Las tuplas son valores, por lo que se copian por valor, en lugar de por referencia.
La mayoría de las veces, esto no debería ser un problema. Sin embargo, si está pasando por tuplas de estructuras grandes, esto podría tener un impacto en el rendimiento. Sin embargo, los locales / retornos de referencia se pueden usar para evitar estos problemas de rendimiento.
Además, como son valores, modificar una copia de forma remota no cambiará la copia original. Esto es algo bueno, pero podría atrapar a algunas personas.
Los nombres de los elementos Tuple no se conservan
Los nombres dados a los elementos son utilizados por el compilador y (en la mayoría de los casos) no están disponibles en tiempo de ejecución. Esto significa que la reflexión no se puede usar para descubrir sus nombres; no se puede acceder de forma dinámica y no se pueden usar en vistas de maquinillas de afeitar.
También esta es una consideración importante con las API. Una tupla devuelta por un método es la excepción a la regla relativa a la detección de nombres posteriores a la compilación. El compilador agrega atributos al método que contiene información sobre los nombres de tupla. Esto significa que puede devolver de forma segura una tupla de un método público en un ensamblaje y acceder a sus nombres en otro.
Las tuplas son livianas
Las tuplas son mucho más simples de escribir que los tipos, ya que son menos detalladas y la declaración puede estar "en línea" (es decir, declarada en el punto de uso). Esto funciona bien cuando se declara un método que devuelve múltiples valores, por ejemplo.
Sin embargo, debido a que se declaran en el punto de uso, si tiene el MethodA
que llama a MethodB
que llama a MethodC
y cada uno devuelve una tupla, tendrá que redefinir la tupla en cada etapa. No existe ( yet ) una forma de crear un alias de una tupla y reutilizarla en múltiples métodos.
Solo usa el sentido común
Para cualquier situación en la que considere usar una tupla, simplemente hágase la pregunta: "una tupla simplificará el código aquí". Si la respuesta es "sí", entonces usa una. Y esa es, en última instancia, la principal consideración sobre si se debe usar una tupla o una clase personalizada.
Antes de Tuples, solía crear una class
y sus variables, luego creaba un objeto de esta clase y lo convertía en el tipo de retorno para algunas funciones.
Ahora con las tuplas puedo hacer lo mismo y en c # 7.0 podemos asignar nombres comprensibles para propiedades de tuplas (antes de esto era item1
, item1
, etc.)
Entonces ahora me pregunto, ¿cuándo debería usar tuplas y cuándo debería crear una clase en c # 7.0?
Creo que esto se convertirá en una pregunta que se pregunta mucho. Actualmente no hay "mejores prácticas" para cuándo usar las nuevas tuplas de valor frente a una clase.
Sin embargo, vale la pena leer lo que surgió durante las conversaciones anteriores sobre las versiones anteriores de tuplas frente a una clase .
En mi opinión, la tupla de valor solo se debe usar mínimamente y con no más de un máximo de tres valores. Creo que esto logra un buen equilibrio entre "devolver algunos valores sin la necesidad de una clase" y "horrible desorden de valores". Si tiene más de tres valores para devolver, haga una clase.
Tampoco usaría nunca una tupla para regresar de una API pública que los consumidores deberán usar. De nuevo, solo usa una clase.
Aquí hay un código del mundo real que he usado:
public async Task<(double temperature, double humidity, string description)> DownloadTodaysForecast()
Tan pronto como quiera devolver datos más complejos, hago una clase.
En general, desea tener una clase cuando su objeto va a ser utilizado en otro lugar o si representa un objeto real o un concepto en su dominio. Probablemente va a crear una clase para representar un automóvil o una tienda de automóviles, no tuplas.
Por otro lado, a veces solo quieres devolver un par de objetos de un método. Tal vez no representan nada especial, es solo que necesitas devolverlos juntos en ese método en particular. A veces, incluso si representan un concepto de su dominio (digamos que está volviendo (Car, Store)
, que podría representarse como un objeto de Sale
), en realidad no los va a usar en ninguna parte; solo está moviendo datos. En esos casos, está bien usar tuplas.
Ahora, hablando específicamente de C #, hay una cosa más que debes saber. El tipo de tupla de C # 7 es en realidad el ValueTuple
, que es una estructura. A diferencia de las clases, que son tipos de referencia, las estructuras son tipos de valores. Puede leer más sobre eso en msdn . Lo más importante es saber que pueden implicar una gran cantidad de copias, así que ten cuidado.
En términos generales, las clases con nombre tienen cierta importancia en el diseño de su sistema. También son más prolijo de escribir. Por ejemplo, puede tener una clase llamada MediaFileOpener
. Es importante para el diseño que sepamos qué hace esta clase: ¡estamos trabajando con archivos multimedia!
Los tipos y tuplas anónimos se utilizan cuando no hay un significado de diseño y todo lo que desea es un Objeto de transferencia de datos ligero (DTO) para mover información.
Como regla, si su clase requiere documentación para describir para qué sirve, o si existe un comportamiento que proporciona, use una clase completa. Si todo lo que necesita es almacenamiento temporal o algún tipo de agrupación, use una Tupla. Considere una situación en la que desee devolver más de un valor de un método asíncrono. Tuple está diseñado para resolver ese problema.
Evitaría usar tuplas como un tipo de retorno de los métodos públicos. En tales casos, prefiero definir una clase o estructura.
Las tuplas están destinadas a representar valores múltiples, como cuando un método intenta devolver valores múltiples. El soporte de tupla en C # 7 usa las System.ValueTuple<...>
para representar ese conjunto de valores. Los nombres de esos valores son válidos solo en el contexto donde se usan y no se aplican.
Las clases están destinadas a representar un único valor con varios atributos.
Quiero comenzar mencionando que C # ya tenía soporte para tipos anónimos . Que son tipos de referencia. Por lo tanto, ya tenía una alternativa adecuada para crear una clase con nombre.
Una de las ventajas de una clase con nombre es que será más fácil de reutilizar (por ejemplo, si necesita el mismo tipo en varios lugares) y el documento. Como el tipo anónimo es anónimo, solo se pueden escribir las variables si se puede usar var
, lo que limita los contextos en los que los tipos anónimos son útiles (por ejemplo, no puede usarlos como tipos de campo, tipos de devolución o tipos de parámetros) )
Por supuesto, puede repasar algunas de las limitaciones de los tipos anónimos usando System.Tuple
. Que también es un tipo de referencia, y puede usarlo explícitamente. La desventaja es que carece de nombres personalizados para los miembros.
Las tuplas C # 7 ( System.ValueTuple ) pueden considerarse similares a los tipos anónimos. La primera diferencia es que son tipos de valores. Esto significa que estas tuplas tendrán una ventaja de rendimiento siempre que permanezcan en el ámbito local o se muevan a través de la pila (que es el uso común de tipos anónimos, debido a su limitación).
La segunda diferencia es que la nueva sintaxis permite que las tuplas aparezcan en más lugares que los tipos anónimos, como saben, tiene azúcar sintáctica para definir el tipo de retorno a ValueTuple
(mientras usa tipos anónimos, debe devolver el object
).
La tercera diferencia es que ValueTuple
admite deconstrucción ValueTuple
para ValueTuple
. Para citar lo nuevo en C # 7.0 :
Otra forma de consumir tuplas es deconstruirlas. Una declaración deconstructiva es una sintaxis para dividir una tupla (u otro valor) en sus partes y asignar esas partes individualmente a variables nuevas:
(string first, string middle, string last) = LookupName(id1); // deconstructing declaration WriteLine($"found {first} {last}.");
Que también puede hacer con tipos personalizados agregando un método Deconstruct .
Para resumen:
-
ValueTuple
tiene azúcar sintáctico en C # 7.0, que debe tenerse en cuenta para la legibilidad. -
ValueTuple
es un tipo de valor. Se aplican todos los pros y los contras entre el uso de unaclass
y unastruct
. -
ValueTuple
puede usar de forma explícita (con o sin el azúcar sintáctico) lo que le permite tener la versatilidad deSystem.Tuple
tiempo que conserva los miembros con nombre. -
ValueTuple
admite deconstrucción.
Dado que debe ser azúcar sintáctica, diría que el argumento más fuerte para elegir ValueTuple
es el mismo argumento para elegir una struct
. Lo cual sería ideal para tipos pequeños e inmutables, que viven en su mayoría en la pila (por lo que no tiene mucho boxeo y unboxing).
Al comparar ValueTuple
con una estructura completa, teniendo en cuenta el azúcar sintáctico, sugeriría utilizar ValueTuple
de forma predeterminada, a menos que necesite un diseño explícito o necesite agregar métodos a él.
También quiero decir que el azúcar sintáctico no necesariamente mejora la legibilidad. La razón principal es que no está nombrando el tipo, y el nombre del tipo proporciona significado al código. Además de eso, puede agregar documentación a una declaración de struct
o class
que facilite la comprensión.
Con todo, la situación en la que ValueTuple
realmente brilla es la devolución de múltiples valores desde un método. En este caso, elimina la necesidad de crear nuevos parámetros de out
. Y la documentación del ValueTuple
utilizado puede residir en la documentación del método. Si encuentra que necesita hacer algo más con ValueTuple
(definiendo métodos de extensión, por ejemplo), le sugiero que considere crear un tipo con nombre en su lugar.
Tuple es una excelente opción cuando desea combinar varios valores (pueden ser diferentes tipos) en un objeto sin crear una clase personalizada. En este caso, Tuple sería una opción rápida y perfecta para ir.
Usa una clase
Si sus objetos son entidades que se utilizan ampliamente en su aplicación y también se almacenan en algún tipo de almacenamiento persistente como una base de datos relacional (SQL Server, MySQL, SQLite), una base de datos o caché NoSQL (Redis, Azure DocumentDB) o incluso en simple archivos de texto o CSV.
Así que sí, cualquier cosa persistente debería tener su propia clase.
Usa una tupla
Si sus objetos son de corta vida sin un significado especial para su aplicación. Por ejemplo, si necesita devolver rápidamente un par de coordenadas, es mejor tener algo como esto:
(double Latitude, double Longitude) getCoordinates()
{
return (144.93525, -98.356346);
}
que definir una clase separada
class Coordinates
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
Una Tuple le ahorrará tiempo de tener que asignar memoria en el montón utilizando new
para una operación tan simple.
Otra vez cuando encuentro tuplas útiles es cuando realizo operaciones matemáticas múltiples en algunos operandos
(double Result1, double Result2, double Result3) performCalculations(int operand1, int operand 2)
No tiene sentido definir una clase en este caso. No importa cuáles sean los cálculos, los resultados no pertenecen a una clase. Entonces, la alternativa sería usar variables, pero creo que las tuplas son más expresivas y dan como resultado una mejor legibilidad.