¿Cómo inicializar fácilmente una lista de tuplas?


286

Me encantan las tuplas . Le permiten agrupar rápidamente información relevante sin tener que escribir una estructura o clase para ella. Esto es muy útil al refactorizar código muy localizado.

Sin embargo, inicializar una lista de ellos parece un poco redundante.

var tupleList = new List<Tuple<int, string>>
{
    Tuple.Create( 1, "cow" ),
    Tuple.Create( 5, "chickens" ),
    Tuple.Create( 1, "airplane" )
};

¿No hay una mejor manera? Me encantaría una solución similar a la del inicializador de Diccionario .

Dictionary<int, string> students = new Dictionary<int, string>()
{
    { 111, "bleh" },
    { 112, "bloeh" },
    { 113, "blah" }
};

¿No podemos usar una sintaxis similar?


2
En este caso, ¿por qué no usarías un diccionario en lugar de una lista de Tuplas?
Ed S.

17
@Ed S .: A Dictionaryno permite claves duplicadas.
Steven Jeuris el

2
@EdS .: Cada vez no se trata de dos tuplas en las que un elemento es hashable / pedible y único.

Buen punto, no noté la clave duplicada.
Ed S.

Respuestas:


279

c # 7.0 te permite hacer esto:

  var tupleList = new List<(int, string)>
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

Si no necesita un List, pero solo una matriz, puede hacer:

  var tupleList = new(int, string)[]
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

Y si no te gusta "Item1" y "Item2", puedes hacer:

  var tupleList = new List<(int Index, string Name)>
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

o para una matriz:

  var tupleList = new (int Index, string Name)[]
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

que te permite hacer: tupleList[0].IndexytupleList[0].Name

Marco 4.6.2 y abajo

Debe instalar System.ValueTupledesde el Administrador de paquetes Nuget.

Marco 4.7 y superior

Está integrado en el marco. No , no instalar System.ValueTuple. De hecho, elimínelo y elimínelo del directorio bin.

nota: en la vida real, no podría elegir entre vacas, pollos o aviones. Estaría realmente desgarrado.


2
¿Se puede usar esto en un .net core 2.0?
Алекса Јевтић

2
@ АлексаЈевтић, System.ValueTupleadmite core 2.0. Pero, pruébelo sin Nuget primero, ya que los paquetes innecesarios pueden causar problemas. Entonces, de una forma u otra, sí. Use c # v7 o superior si es posible.
toddmo

1
¡Respuesta absolutamente perfecta! ¡Gracias!
Vitox

224

¡Si! Esto es posible .

La sintaxis {} del inicializador de colección funciona en cualquier tipo IEnumerable que tenga un método Add con la cantidad correcta de argumentos. Sin preocuparse de cómo funciona eso debajo de las cubiertas, eso significa que puede simplemente extender desde la Lista <T> , agregar un método de adición personalizado para inicializar su T , ¡y listo!

public class TupleList<T1, T2> : List<Tuple<T1, T2>>
{
    public void Add( T1 item, T2 item2 )
    {
        Add( new Tuple<T1, T2>( item, item2 ) );
    }
}

Esto le permite hacer lo siguiente:

var groceryList = new TupleList<int, string>
{
    { 1, "kiwi" },
    { 5, "apples" },
    { 3, "potatoes" },
    { 1, "tomato" }
};

39
Un inconveniente es tener que escribir la clase. Como tú, me encantan las tuplas porque no tengo que escribir una clase o estructura.
goodeye

2
@BillW No estoy muy seguro de que esta sea la publicación exacta, pero sé que Jon Skeet ha escrito sobre eso antes .
Steven Jeuris

1
¿esto funciona groceryList[0] == groceryList[1]o hay que implementar una comparación?
Byyo

Creé un paquete Nuget con métodos Add en ICollection para ahorrarle a otros el tiempo de tener que mantener este código. Consulte aquí el paquete: nuget.org/packages/Naos.Recipes.TupleInitializers. Consulte aquí el código: github.com/NaosProject/Naos.Recipes/blob/master/… Esto incluirá un archivo cs en su solución bajo ".Naos .Recipes ", para que no tenga que arrastrar una dependencia de ensamblado
SFun28

83

C # 6 agrega una nueva característica solo para esto: extensión Agregar métodos. Esto siempre ha sido posible para VB.net, pero ahora está disponible en C #.

Ahora no tiene que agregar Add()métodos a sus clases directamente, puede implementarlos como métodos de extensión. Al extender cualquier tipo enumerable con un Add()método, podrá usarlo en expresiones de inicializador de colección. Para que ya no tenga que derivar explícitamente de las listas ( como se menciona en otra respuesta ), simplemente puede extenderlo.

public static class TupleListExtensions
{
    public static void Add<T1, T2>(this IList<Tuple<T1, T2>> list,
            T1 item1, T2 item2)
    {
        list.Add(Tuple.Create(item1, item2));
    }

    public static void Add<T1, T2, T3>(this IList<Tuple<T1, T2, T3>> list,
            T1 item1, T2 item2, T3 item3)
    {
        list.Add(Tuple.Create(item1, item2, item3));
    }

    // and so on...
}

Esto le permitirá hacer esto en cualquier clase que implemente IList<>:

var numbers = new List<Tuple<int, string>>
{
    { 1, "one" },
    { 2, "two" },
    { 3, "three" },
    { 4, "four" },
    { 5, "five" },
};
var points = new ObservableCollection<Tuple<double, double, double>>
{
    { 0, 0, 0 },
    { 1, 2, 3 },
    { -4, -2, 42 },
};

Por supuesto, no está restringido a extender colecciones de tuplas, puede ser para colecciones de cualquier tipo específico para el que desea la sintaxis especial.

public static class BigIntegerListExtensions
{
    public static void Add(this IList<BigInteger> list,
        params byte[] value)
    {
        list.Add(new BigInteger(value));
    }

    public static void Add(this IList<BigInteger> list,
        string value)
    {
        list.Add(BigInteger.Parse(value));
    }
}

var bigNumbers = new List<BigInteger>
{
    new BigInteger(1), // constructor BigInteger(int)
    2222222222L,       // implicit operator BigInteger(long)
    3333333333UL,      // implicit operator BigInteger(ulong)
    { 4, 4, 4, 4, 4, 4, 4, 4 },               // extension Add(byte[])
    "55555555555555555555555555555555555555", // extension Add(string)
};

C # 7 agregará soporte para las tuplas integradas en el lenguaje, aunque serán de un tipo diferente (en su System.ValueTuplelugar). Por lo tanto, sería bueno agregar sobrecargas para las tuplas de valor para que también tenga la opción de usarlas. Desafortunadamente, no hay conversiones implícitas definidas entre los dos.

public static class ValueTupleListExtensions
{
    public static void Add<T1, T2>(this IList<Tuple<T1, T2>> list,
        ValueTuple<T1, T2> item) => list.Add(item.ToTuple());
}

De esta manera, la inicialización de la lista se verá aún mejor.

var points = new List<Tuple<int, int, int>>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};

Pero en lugar de pasar por todos estos problemas, podría ser mejor cambiar a usar ValueTupleexclusivamente.

var points = new List<(int, int, int)>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};

Finalmente le eché un vistazo decente al actualizar mi biblioteca a C # 6.0. Aunque se ve bien de un vistazo, prefiero mi solución publicada anteriormente sobre esto porque me parece TupleList<int, string>.más legible que List<Tuple<int, string>>.
Steven Jeuris

1
Eso es algo que un alias de tipo puede arreglar. Por supuesto, el alias en sí no puede ser genérico, lo que puede ser preferible. using TupleList = System.Collections.Generic.List<System.Tuple<int, string>>;
Jeff Mercado

Creé un paquete Nuget con métodos Add en ICollection para ahorrarle a otros el tiempo de tener que mantener este código. Consulte aquí el paquete: nuget.org/packages/Naos.Recipes.TupleInitializers. Consulte aquí el código: github.com/NaosProject/Naos.Recipes/blob/master/… Esto incluirá un archivo cs en su solución bajo ".Naos .Recipes ", para que no tenga que arrastrar una dependencia de ensamblado
SFun28

42

Puede hacer esto llamando al constructor cada vez que es ligeramente mejor

var tupleList = new List<Tuple<int, string>>
{
    new Tuple<int, string>(1, "cow" ),
    new Tuple<int, string>( 5, "chickens" ),
    new Tuple<int, string>( 1, "airplane" )
};

66
No pude hacer funcionar el código original, así que lo modifiqué a lo que creo que debería ser ... lo que puede revertir su opinión sobre si la sintaxis "es un poco mejor" después de todo :)
día

55
al menos use Tuple.Createen su lugar y puede inferir los argumentos de tipo
Dave Cousineau

28

Antigua pregunta, pero esto es lo que normalmente hago para que las cosas sean un poco más legibles:

Func<int, string, Tuple<int, string>> tc = Tuple.Create;

var tupleList = new List<Tuple<int, string>>
{
    tc( 1, "cow" ),
    tc( 5, "chickens" ),
    tc( 1, "airplane" )
};

¡Funciona también en versiones antiguas de .NET!
Ed Bayiates

1

Super Duper Old, lo sé, pero agregaría mi parte sobre el uso de Linq y la continuación de lambdas en los métodos con el uso de C # 7. Intento usar tuplas con nombre como reemplazos para DTO y proyecciones anónimas cuando se reutilizan en una clase. Sí, para burlarse y probar aún necesita clases, pero hacer las cosas en línea y pasar en una clase es bueno tener esta nueva opción en mi humilde opinión. Puedes instanciarlos desde

  1. Instanciación directa
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
  1. Fuera de una colección existente, y ahora puede devolver tuplas bien escritas de forma similar a como solían hacerse las proyecciones anónimas.
public class Hold
{
    public int Id { get; set; }
    public string Name { get; set; }
}

//In some method or main console app:
var holds = new List<Hold> { new Hold { Id = 1, Name = "Me" }, new Hold { Id = 2, Name = "You" } };
var anonymousProjections = holds.Select(x => new { SomeNewId = x.Id, SomeNewName = x.Name });
var namedTuples = holds.Select(x => (TupleId: x.Id, TupleName: x.Name));
  1. Reutilice las tuplas más tarde con métodos de agrupación o use un método para construirlas en línea en otra lógica:
//Assuming holder class above making 'holds' object
public (int Id, string Name) ReturnNamedTuple(int id, string name) => (id, name);
public static List<(int Id, string Name)> ReturnNamedTuplesFromHolder(List<Hold> holds) => holds.Select(x => (x.Id, x.Name)).ToList();
public static void DoSomethingWithNamedTuplesInput(List<(int id, string name)> inputs) => inputs.ForEach(x => Console.WriteLine($"Doing work with {x.id} for {x.name}"));

var namedTuples2 = holds.Select(x => ReturnNamedTuple(x.Id, x.Name));
var namedTuples3 = ReturnNamedTuplesFromHolder(holds);
DoSomethingWithNamedTuplesInput(namedTuples.ToList());

Me haces sentir viejo :) Puede que me falte algo aquí, pero inicializar 'retenciones' no es conciso en absoluto. Este fue el punto exacto de la pregunta.
Steven Jeuris

@ Steven Jeuris No, tenías razón Copié y pegué el código incorrecto para el Punto 1. Necesito ir un poco más lento antes de presionar 'enviar'.
djangojazz

0

¿Por qué te gustan las tuplas? Es como los tipos anónimos: sin nombres. No se puede entender la estructura de los datos.

Me gustan las clases clasicas

class FoodItem
{
     public int Position { get; set; }
     public string Name { get; set; }
}

List<FoodItem> list = new List<FoodItem>
{
     new FoodItem { Position = 1, Name = "apple" },
     new FoodItem { Position = 2, Name = "kiwi" }
};

3
Como dije: "Le permiten agrupar rápidamente información relevante sin tener que escribir una estructura o clase para ello". En caso de que esta clase se use únicamente dentro de un método único como una estructura de datos auxiliar, prefiero usar Tuplas, para no llenar un espacio de nombres con una clase innecesaria.
Steven Jeuris

1
Estás pensando en la clase Tuple anterior (no en la estructura). C # 7 agregó tuplas basadas en estructuras (respaldadas por el nuevo tipo ValueTuple) que PUEDEN tener nombres, para que pueda comprender la estructura de los datos
Steve Niles

0

Creo que una técnica es un poco más fácil y que no se ha mencionado antes aquí:

var asdf = new [] { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
}.ToList();

Creo que es un poco más limpio que:

var asdf = new List<Tuple<int, string>> { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
};

Eso se reduce más al gusto si prefiere mencionar los tipos explícitamente o no, en el mismo sentido que usar varo no. Personalmente, prefiero escribir explícitamente (cuando no está duplicado, por ejemplo, en el constructor).
Steven Jeuris

-4
    var colors = new[]
    {
        new { value = Color.White, name = "White" },
        new { value = Color.Silver, name = "Silver" },
        new { value = Color.Gray, name = "Gray" },
        new { value = Color.Black, name = "Black" },
        new { value = Color.Red, name = "Red" },
        new { value = Color.Maroon, name = "Maroon" },
        new { value = Color.Yellow, name = "Yellow" },
        new { value = Color.Olive, name = "Olive" },
        new { value = Color.Lime, name = "Lime" },
        new { value = Color.Green, name = "Green" },
        new { value = Color.Aqua, name = "Aqua" },
        new { value = Color.Teal, name = "Teal" },
        new { value = Color.Blue, name = "Blue" },
        new { value = Color.Navy, name = "Navy" },
        new { value = Color.Pink, name = "Pink" },
        new { value = Color.Fuchsia, name = "Fuchsia" },
        new { value = Color.Purple, name = "Purple" }
    };
    foreach (var color in colors)
    {
        stackLayout.Children.Add(
            new Label
            {
                Text = color.name,
                TextColor = color.value,
            });
        FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
    }

this is a Tuple<Color, string>

¿Cómo se obtiene la sintaxis de valor / nombre con una Tupla?
Andreas Reiff

El compilador dice que los tipos anónimos no son implícitamente convertibles a Tuple
cthepenier
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.