Tuples are a great new feature in C# 7 and I've used them a few times already. The syntax is great, they're easy to use, and they're a whole lot better than the alternatives.
Tuples are implemented using ValueTuple
, with name erasure at runtime. This means the tuple field names are not available at runtime and are therefore, not accessible via dynamically typed objects or by reflection.
var name = (first: "John", last: "Smith");
Console.WriteLine(name.first); // John
dynamic dynamicName = name;
Console.WriteLine(dynamicName.first); // RuntimeBinderException
If you really need to access tuples using dynamic or reflection, you can use the underlying fields Item1
, Item2
, ..., ItemN
, which exist on ValueTuple
and correspond to the first, second, and nth fields respectively on the tuple.
var name = (first: "John", last: "Smith");
foreach(var field in name.GetType().GetFields())
Console.WriteLine($"{field.Name} {field.GetValue(name)}");
Output:
Item1 John
Item2 Smith
However, you should be aware that Jon Skeet discovered that you cannot access beyond Item7
using dynamic. At present, you'll get a RuntimeBinderException
if you try to access Item8
or higher through a dynamic
typed object (or via reflection). You can track this issue at GitHub.
var bigTuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Console.WriteLine(bigTuple.Item7); // 7
Console.WriteLine(bigTuple.Item8); // 8
dynamic dynamicTuple = bigTuple;
Console.WriteLine(dynamicTuple.Item7); // 7
Console.WriteLine(dynamicTuple.Item8); // RuntimeBinderException
This happens because of the implementation of ValueTuple
. There are ValueTuples with one through seven fields, but the eighth ValueTuple is different and has a special field Rest
that holds another ValueTuple
. This technique is applied recursively to allow tuples with arbitrarily many fields.
var bigTuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
foreach(var field in bigTuple.GetType().GetFields())
Console.WriteLine($"{field.Name} {field.GetValue(bigTuple)}");
Output:
Item1 1
Item2 2
Item3 3
Item4 4
Item5 5
Item6 6
Item7 7
Rest(8, 9, 10)
I don't think you'll encounter this issue in real world code, because you probably shouldn't have tuples with eight fields anyway. It's unwieldly to manage tuples with so many fields and there are performance concerns. As tuples are structs, every field is copied when they are passed to another method; as opposed to classes, where only the pointer is copied.
If for some reason, you need to access more than 7 fields using dynamic types or reflection, you can do it using the Rest
field.
var bigTuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Console.WriteLine(dynamicTuple.Item7); // 7
Console.WriteLine(dynamicTuple.Rest.Item1); // 8
Console.WriteLine(dynamicTuple.Rest.Item2); // 9
Console.WriteLine(dynamicTuple.Rest.Item3); // 10