How to easily initialize a list of Tuples?
Asked Answered
S

8

409

I love tuples. They allow you to quickly group relevant information together without having to write a struct or class for it. This is very useful while refactoring very localized code.

Initializing a list of them however seems a bit redundant.

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

Isn't there a better way? I would love a solution along the lines of the Dictionary initializer.

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

Can't we use a similar syntax?

Submersible answered 3/11, 2011 at 22:1 Comment(3)
In this case why wouldn't you use a dictionary instead of a list of Tuples?Catalpa
@Ed S.: A Dictionary doesn't allow duplicate keys.Submersible
@EdS.: Every time it's not a two-tuple where one item is hashable/orderable and unique.Crim
G
502

c# 7.0 lets you do this:

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

If you don't need a List, but just an array, you can do:

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

And if you don't like "Item1" and "Item2", you can do:

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

or for an array:

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

which lets you do: tupleList[0].Index and tupleList[0].Name

Framework 4.6.2 and below

You must install System.ValueTuple from the Nuget Package Manager.

Framework 4.7 and above

It is built into the framework. Do not install System.ValueTuple. In fact, remove it and delete it from the bin directory.

note: In real life, I wouldn't be able to choose between cow, chickens or airplane. I would be really torn.

Gamophyllous answered 25/3, 2017 at 0:2 Comment(4)
Can this be used on a .net core 2.0 ?Implied
@АлексаЈевтић, System.ValueTuple supports core 2.0. But, try it without the Nuget first, as unnecessary packages can cause problems. So one way or another, yes. Use c# v7 or greater if possible.Gamophyllous
Here's a relevant microsoft link about the collection initializers mentioned in this answer.Spirituous
Error CS1503 Argument 1: cannot convert from '(string, string)' to 'System.Tuple<string, string>' MauiApp1 (net7.0-windows10.0.19041.0) C:\projects\MauiApp1\MainPage.xaml.csHostler
S
240

Yes! This is possible.

The { } syntax of the collection initializer works on any IEnumerable type which has an Add method with the correct amount of arguments. Without bothering how that works under the covers, that means you can simply extend from List<T>, add a custom Add method to initialize your T, and you are done!

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

This allows you to do the following:

var groceryList = new TupleList<int, string>
{
    { 1, "kiwi" },
    { 5, "apples" },
    { 3, "potatoes" },
    { 1, "tomato" }
};
Submersible answered 3/11, 2011 at 22:3 Comment(5)
A downside is having to write the class. Like you, I love tuples because I don't have to write a class or struct.Afrikander
@BillW Not too certain this is the exact post, ... but I know Jon Skeet has written about it before.Submersible
does this work groceryList[0] == groceryList[1] or does a comparison have to be implemented?Koziol
I created a Nuget Package with Add methods on ICollection to save others the time of having to maintain this code. See here for package: nuget.org/packages/Naos.Recipes.TupleInitializers See here for code: github.com/NaosProject/Naos.Recipes/blob/master/… This will include a cs file in your solution under a ".Naos.Recipes" folder, so you don't have to drag-around an assembly dependencyForego
What's new in each C# version language: C# 5, C# 6, C# 7, C# 8, C# 9., C# 10...Fidget
M
90

C# 6 adds a new feature just for this: extension Add methods. This has always been possible for VB.net but is now available in C#.

Now you don't have to add Add() methods to your classes directly, you can implement them as extension methods. When extending any enumerable type with an Add() method, you'll be able to use it in collection initializer expressions. So you don't have to derive from lists explicitly anymore (as mentioned in another answer), you can simply extend it.

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...
}

This will allow you to do this on any class that implements 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 },
};

Of course you're not restricted to extending collections of tuples, it can be for collections of any specific type you want the special syntax for.

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 will be adding in support for tuples built into the language, though they will be of a different type (System.ValueTuple instead). So to it would be good to add overloads for value tuples so you have the option to use them as well. Unfortunately, there are no implicit conversions defined between the two.

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

This way the list initialization will look even nicer.

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

But instead of going through all this trouble, it might just be better to switch to using ValueTuple exclusively.

var points = new List<(int, int, int)>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};
Museum answered 13/12, 2014 at 5:22 Comment(3)
I finally took a decent look at this while updating my library to C# 6.0. Although it looks good at a glance, I prefer my previously posted solution over this because I find TupleList<int, string>. to be more readable than List<Tuple<int, string>>.Submersible
That's something that a type alias can fix. Granted the alias itself cannot be generic which may be preferable. using TupleList = System.Collections.Generic.List<System.Tuple<int, string>>;Museum
I created a Nuget Package with Add methods on ICollection to save others the time of having to maintain this code. See here for package: nuget.org/packages/Naos.Recipes.TupleInitializers See here for code: github.com/NaosProject/Naos.Recipes/blob/master/… This will include a cs file in your solution under a ".Naos.Recipes" folder, so you don't have to drag-around an assembly dependencyForego
S
47

You can do this by calling the constructor each time with is slightly better

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" )
};
Sordello answered 3/11, 2011 at 22:4 Comment(3)
I couldn't get the original code to work, so I've amended it to what I think it should be... which may reverse your opinion on whether the syntax "is slightly better" after all :)Ravens
at least use Tuple.Create instead and you can infer the type argumentsMicrophysics
This is what I was looking for. Thanks @RavensMenhir
M
30

Old question, but this is what I typically do to make things a bit more readable:

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" )
};
Machinist answered 31/5, 2015 at 6:53 Comment(1)
Works on old versions of .NET, too!Weisberg
F
4

Super Duper Old I know but I would add my piece on using Linq and continuation lambdas on methods with using C# 7. I try to use named tuples as replacements for DTOs and anonymous projections when reused in a class. Yes for mocking and testing you still need classes but doing things inline and passing around in a class is nice to have this newer option IMHO. You can instantiate them from

  1. Direct Instantiation
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
  1. Off of an existing collection, and now you can return well typed tuples similar to how anonymous projections used to be done.
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. Reuse the tuples later with grouping methods or use a method to construct them inline in other logic:
//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());
Farinaceous answered 6/12, 2018 at 17:22 Comment(2)
You make me feel old. :) I might be missing something here, but initializing 'holds' is not concise at all. This was the exact point of the question.Submersible
@Steven Jeuris No, you were right I copied and pasted the wrong bit of code for Point 1. Need to go a little slower before I hit 'submit'.Farinaceous
I
1

One technique I think is a little easier and that hasn't been mentioned before here:

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

I think that's a little cleaner than:

var asdf = new List<Tuple<int, string>> { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
};
Indigene answered 23/1, 2018 at 16:6 Comment(1)
That comes more down to taste whether you prefer to mention types explicitly or not, in the same vain as to use var or not. I personally prefer explicit typing (when it is not duplicated in e.g. the constructor).Submersible
R
-6
    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>
Romero answered 30/9, 2015 at 21:32 Comment(2)
How do you get the value/name syntax with a Tuple?Goulden
Compiler tells that anonymous type are not implicitly convertible to TupleTamberg

© 2022 - 2024 — McMap. All rights reserved.