¿Cuál es la diferencia entre System.ValueTuple y System.Tuple?

Descompilé algunas bibliotecas de C # 7 y vi ValueTuple generics ValueTuple . ¿Qué son ValueTuples y por qué no Tuple ?

  • https://docs.microsoft.com/en-gb/dotnet/api/system.tuple
  • https://docs.microsoft.com/en-gb/dotnet/api/system.valuetuple

¿Qué son ValuedTuples y por qué no Tuple ?

Un ValueTuple es una estructura que refleja una tupla, igual que la clase System.Tuple original.

La diferencia principal entre Tuple y ValueTuple es:

  • System.ValueTuple es un tipo de valor (struct), mientras que System.Tuple es un tipo de referencia ( class ). Esto es significativo cuando se habla de asignaciones y presión de GC.
  • System.ValueTuple no es solo una struct , es mutable , y hay que tener cuidado al usarlas como tales. Piense en lo que ocurre cuando una clase tiene un System.ValueTuple como campo.
  • System.ValueTuple expone sus elementos a través de campos en lugar de propiedades.

Hasta C # 7, el uso de tuplas no era muy conveniente. Sus nombres de campo son Item2 , Item2 , etc., y el idioma no les ha proporcionado azúcar sintáctica como la mayoría de los otros idiomas (Python, Scala).

Cuando el equipo de diseño de lenguaje de .NET decidió incorporar tuplas y agregarles azúcar syntax en el nivel de idioma, un factor importante fue el rendimiento. Con ValueTuple como un tipo de valor, puede evitar la presión GC al usarlos porque (como un detalle de implementación) se asignarán en la stack.

Además, una struct obtiene semántica de igualdad automática (superficial) por el tiempo de ejecución, donde una class no lo hace. Aunque el equipo de diseño se aseguró de que hubiera una igualdad aún más optimizada para las tuplas, implementó una igualdad personalizada para ello.

Aquí hay un párrafo de las notas de diseño de Tuples :

Estructura o clase:

Como se mencionó, propongo hacer structs tipos de tuplas en lugar de classes , de modo que no se asocie ninguna penalización de asignación. Deben ser lo más livianos posible.

Podría decirse que las structs pueden llegar a ser más costosas, porque la asignación copia un valor mayor. Entonces, si se les asigna mucho más de lo que se crean, entonces las structs serían una mala elección.

En su propia motivación, sin embargo, las tuplas son efímeras. Los usarías cuando las partes son más importantes que el todo. Entonces, el patrón común sería construir, devolver e inmediatamente deconstruirlos. En esta situación, las estructuras son claramente preferibles.

Las estructuras también tienen una serie de otros beneficios, que se harán evidentes a continuación.

Ejemplos:

Puede ver fácilmente que trabajar con System.Tuple vuelve ambiguo muy rápidamente. Por ejemplo, supongamos que tenemos un método que calcula una sum y un recuento de una List :

 public Tuple DoStuff(IEnumerable ints) { var sum = 0; var count = 0; foreach (var value in values) { sum += value; count++; } return new Tuple(sum, count); } 

En el extremo receptor, terminamos con:

 Tuple result = DoStuff(Enumerable.Range(0, 10)); // What is Item1 and what is Item2? // Which one is the sum and which is the count? Console.WriteLine(result.Item1); Console.WriteLine(result.Item2); 

La forma en que puede deconstruir tuplas de valor en argumentos con nombre es el poder real de la característica:

 public (int sum, int count) DoStuff(IEnumerable values) { var res = (sum: 0, count: 0); foreach (var value in values) { res.sum += value; res.count++; } return res; } 

Y en el extremo receptor:

 var result = DoStuff(Enumerable.Range(0, 10)); Console.WriteLine($"Sum: {result.Sum}, Count: {result.Count}"); 

O:

 var (sum, count) = DoStuff(Enumerable.Range(0, 10)); Console.WriteLine($"Sum: {sum}, Count: {count}"); 

Comstackdor de golosinas:

Si miramos bajo la cubierta de nuestro ejemplo anterior, podemos ver exactamente cómo el comstackdor interpreta ValueTuple cuando le pedimos que lo deconstruya:

 [return: TupleElementNames(new string[] { "sum", "count" })] public ValueTuple DoStuff(IEnumerable values) { ValueTuple result; result..ctor(0, 0); foreach (int current in values) { result.Item1 += current; result.Item2++; } return result; } public void Foo() { ValueTuple expr_0E = this.DoStuff(Enumerable.Range(0, 10)); int item = expr_0E.Item1; int arg_1A_0 = expr_0E.Item2; } 

Internamente, el código comstackdo utiliza Item2 y Item2 , pero todo esto se abstrae de nosotros ya que trabajamos con una tupla descompuesta. Una tupla con argumentos con nombre se anota con TupleElementNamesAttribute . Si usamos una sola variable nueva en lugar de descomponer, obtenemos:

 public void Foo() { ValueTuple valueTuple = this.DoStuff(Enumerable.Range(0, 10)); Console.WriteLine(string.Format("Sum: {0}, Count: {1})", valueTuple.Item1, valueTuple.Item2)); } 

Tenga en cuenta que el comstackdor todavía tiene que hacer que ocurra algo de magia (a través del atributo) cuando depuramos nuestra aplicación, ya que sería extraño ver Item2 , Item2 .

La diferencia entre Tuple y ValueTuple es que Tuple es un tipo de referencia y ValueTuple es un tipo de valor. Lo último es deseable porque los cambios en el lenguaje en C # 7 hacen que las tuplas se usen mucho más frecuentemente, pero asignar un nuevo objeto al montón para cada tupla es un problema de rendimiento, particularmente cuando es innecesario.

Sin embargo, en C # 7, la idea es que nunca tenga que usar explícitamente ninguno de los dos tipos debido a la adición del azúcar de syntax para el uso de tuplas. Por ejemplo, en C # 6, si quisiera usar una tupla para devolver un valor, tendría que hacer lo siguiente:

 public Tuple GetValues() { // ... return new Tuple(stringVal, intVal); } var value = GetValues(); string s = value.Item1; 

Sin embargo, en C # 7, puedes usar esto:

 public (string, int) GetValues() { // ... return (stringVal, intVal); } var value = GetValues(); string s = value.Item1; 

Incluso puede ir un paso más allá y dar los nombres de los valores:

 public (string S, int I) GetValues() { // ... return (stringVal, intVal); } var value = GetValues(); string s = value.S; 

… O deconstruir la tupla por completo:

 public (string S, int I) GetValues() { // ... return (stringVal, intVal); } var (S, I) = GetValues(); string s = S; 

Las tuplas no se usaban a menudo en C # pre-7 porque eran engorrosas y prolijas, y solo se usaban realmente en los casos en que construir una clase / estructura de datos para una sola instancia de trabajo sería más problemático de lo que valía. Pero en C # 7, las tuplas ahora cuentan con soporte de nivel de lenguaje, por lo que su uso es mucho más limpio y más útil.

Miré la fuente de Tuple y ValueTuple . La diferencia es que Tuple es una class y ValueTuple es una struct que implementa IEquatable .

Eso significa que Tuple == Tuple devolverá false si no son la misma instancia, pero ValueTuple == ValueTuple devolverá true si son del mismo tipo e Equals devuelve true para cada uno de los valores que contienen.

Otras respuestas olvidaron mencionar puntos importantes. En lugar de reformularlo, voy a hacer referencia a la documentación XML desde el código fuente :

Los tipos ValueTuple (de arity 0 a 8) comprenden la implementación en tiempo de ejecución que subyace a las tuplas en C # y struct tuplas en F #.

Además de la syntax creada a través del lenguaje , se crean más fácilmente a través de ValueTuple.Create métodos de fábrica. Los tipos System.ValueTuple difieren de los tipos System.Tuple en eso:

  • son estructuras en lugar de clases,
  • son mutables en lugar de de solo lectura , y
  • sus miembros (como Item1, Item2, etc.) son campos en lugar de propiedades.

Con la introducción de este tipo y el comstackdor C # 7.0, puede escribir fácilmente

 (int, string) idAndName = (1, "John"); 

Y devuelve dos valores de un método:

 private (int, string) GetIdAndName() { //..... return (id, name); } 

A diferencia de System.Tuple , puede actualizar sus miembros (Mutable) porque son campos públicos de lectura y escritura a los que se les pueden dar nombres significativos:

 (int id, string name) idAndName = (1, "John"); idAndName.name = "New Name"; 

Además de los comentarios anteriores, un desafortunado resultado de ValueTuple es que, como tipo de valor, los argumentos con nombre se borran cuando se comstackn en IL, por lo que no están disponibles para la serialización en tiempo de ejecución.

es decir, sus argumentos con nombre dulce todavía terminarán como “Artículo1”, “Artículo2”, etc. cuando se serialicen a través de, por ejemplo, Json.NET.

Este es un buen resumen de lo que es y su limitación

https://josephwoodward.co.uk/2017/04/csharp-7-valuetuple-types-and-their-limitations