C# 7 added Tuples and provides an awesome syntax for accessing them. C# 7.1 improved the usability of tuples further with Tuple Name Inference. However, sometimes you need to access them dynamically and this can be tricky.

Accessing tuples dynamically is tricky because there are only specialized tuple types for tuples with 0 through 7 parameters. For tuples with 8 or more parameters, there is a ValueTuple type that holds 7 parameters, plus a Rest field for another ValueTuple containing any additional values beyond the 7th. For huge tuples, the Rest field can be used recursively to yield tuples of arbitrary length.

In this post, I detail the ValueTuple types that internally represent tuples and show how to access tuples using reflection. I then show how to access the 8th parameter and beyond and how to use reflection to iterate over all tuple parameters.

ValueTuple Types

There is one ValueTuple type for each length of tuple up to 7. There is then a special ValueTuple type that as its 8th parameter, takes another ValueTuple. Used recursively, tuples of arbitrary length can be created.

Here is a list of the ValueTuple types:

  • ValueTuple
  • ValueTuple<T1>
  • ValueTuple<T1, T2>
  • ValueTuple<T1, T2, T3>
  • ValueTuple<T1, T2, T3, T4>
  • ValueTuple<T1, T2, T3, T4, T5>
  • ValueTuple<T1, T2, T3, T4, T5, T6>
  • ValueTuple<T1, T2, T3, T4, T5, T6, T7>
  • ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>

Internally, ValueTuples store the tuple parameters in fields named Item1 through Item7. The final ValueTuple, ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>, has an extra field named Rest that stores the next ValueTuple.

The parameter names you assign to tuple fields are merely syntactic sugar provided by C# and the compiler. At runtime these are gone and only the internal tuple names, Item1 through Item7 are available.

For example, in the following code sample, the tuple field first would be Item1 at runtime and last would be Item2.

var name = (first: "John", last: "Smith");

This runtime desugaring, which is known technically as runtime name erasure is why you must use Item1 through Item7 and Rest to access the tuple values dynamically at runtime. This applies whether you are using dynamic types or reflection.

Accessing Tuple Fields using Reflection

Accessing the first 7 tuple parameters is quite straightforward. Simply use reflection to access the fields with names Item1 through Item7.

var item = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

var value1 = item.GetType().GetField("Item1");
Console.Out.WriteLine(value1.GetValue(item)); // Prints "1"

var value7 = item.GetType().GetField("Item7");
Console.Out.WriteLine(value7.GetValue(item)); // Prints "7"

Accessing the 8th Parameter and Beyond

Accessing the 8th tuple parameter and beyond is more complicated, as Vasilios found out while trying to use reflection to access the values stored in the Rest field.

In the following code sample, we see that there is no Item8. Instead we need to get the value of the Rest field, which contains items 8, 9, and 10, and then get the first item, Item1, which corresponds to item 8.

var item = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

var value8a = item.GetType().GetField("Item8");
Console.Out.WriteLine(value8a == null); // Prints "True"

var restField = item.GetType().GetField("Rest");
var rest = restField.GetValue(item);
var value8b = rest.GetType().GetField("Item1");
Console.Out.WriteLine(value8b.GetValue(rest)); // Prints "8"

Vasilios ran into trouble by trying to access Item1 on restField instead of rest. restField is of type FieldInfo, whereas rest is of type ValueTuple<T1, T2, T3>.

Iterating through ValueTuple parameters

Finally, you may want to enumerate all parameters on a ValueTuple. To handle arbitrarily large ValueTuples, you need to recursively handle the Rest field.

In the following code sample, we create a queue to iterate through the chain of ValueTuple Rest fields. You could also implement EnumerateValueTuple using recursion.

var item = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

foreach(var value in EnumerateValueTuple(item))
  Console.Out.WriteLine(value); // Prints "1 2 3 4 5 6 7 8 9 10"

static IEnumerable<object> EnumerateValueTuple(object valueTuple)
{
    var tuples = new Queue<object>();
    tuples.Enqueue(valueTuple);

    while(tuples.Count > 0 && tuples.Dequeue() is object tuple)
    {
        foreach(var field in tuple.GetType().GetFields())
        {
            if(field.Name == "Rest")
                tuples.Enqueue(field.GetValue(tuple));
            else
                yield return field.GetValue(tuple);
        }
    }
}

Accessing Tuples at Runtime without Reflection

Update (3rd February 2018): Airbreather points out on Reddit that as of .NET Core 2.0 and .NET Framework 4.7.1, it is now possible to access the tuple values dynamically at runtime without using reflection.

This is achieved by importing System.Runtime.CompilerServices and casting the tuple to ITuple, which provides an indexer and a Length property:

using System.Runtime.CompilerServices;

var item = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

var tuple = item as ITuple;
for(int i = 0; i < tuple.Length; i++)
    Console.Out.WriteLine(tuple[i]); // Prints "1 2 3 4 5 6 7 8 9 10"

If you're targeting .NET Core 2.0+ or .NET Framework 4.7.1+, then this is a much better way to dynamically access the tuple values. Unfortunately, ITuple is not part of .NET Standard 2.0 and therefore, cannot be used in libraries targeting .NET Standard.