C# 7 tuples and lambdas
C#LambdaTuplesC# 7.0C# Problem Overview
With new c# 7 tuple syntax, is it possible to specify a lambda with a tuple as parameter and use unpacked values inside the lambda?
Example:
var list = new List<(int,int)>();
normal way to use a tuple in lambda:
list.Select(value => value.Item1*2 + value.Item2/2);
i expected some new sugar to avoid .Item1
.Item2
, like:
list.Select((x,y) => x*2 + y/2);
The last line does not work because it is treated as two parameters for lambda. I am not sure if there is a way to do it actually.
EDIT:
I tried double parentesis in lambda definition and it didn't work: ((x,y)) => ...
, and maybe it was stupid to try, but double parenthesis actually work here:
list.Add((1,2));
Also, my question is not quite about avoiding ugly default names .Item .Item2
, it is about actual unpacking a tuple in lambda (and maybe why it's not implemented or not possible). If you came here for a solution to default names, read Sergey Berezovskiy's answer.
EDIT 2:
Just thought of a more general use case: is it possible (or why not) to "deconstruct" tuple passed to a method? Like this:
void Foo((int,int)(x,y)) { x+y; }
Instead of this:
void Foo((int x,int y) value) { value.x+value.y }
C# Solutions
Solution 1 - C#
As you have observed, for:
var list = new List<(int,int)>();
One would at least expect to be able to do the following:
list.Select((x,y) => x*2 + y/2);
But the C# 7 compiler doesn't (yet) support this. It is also reasonable to desire sugar that would allow the following:
void Foo(int x, int y) => ...
Foo(list[0]);
with the compiler converting Foo(list[0]);
to Foo(list[0].Item1, list[0].Item2);
automatically.
Neither of these is currently possible. However, the issue, Proposal: Tuple deconstruction in lambda argument list, exists on the dotnet/csharplang
repo on GitHub, requesting that the language team consider these features for a future version of C#. Please do add your voices to that thread if you too would like to see support for this.
Solution 2 - C#
You should specify names of tuple properties (well, ValueTuple have fields) otherwise default names will be used, as you have seen:
var list = new List<(int x, int y)>();
Now tuple have nicely named fields which you can use
list.Select(t => t.x * 2 + t.y / 2)
Don't forget to add System.ValueTuple package from NuGet and keep in mind that ValueTuples are mutable structs.
Update: Deconstruction currently represented only as an assignment to existing variables (deconstructon-assignment) or to newly created local variables (deconstruction-declaration). Applicable function member selection algorithm is the same as before:
> Each argument in argument list corresponds to a parameter in the function member declaration as described in §7.5.1.1, and any parameter to which no argument corresponds is an optional parameter.
Tuple variable is a single argument. It cannot correspond to several parameters in the formal parameters list of the method.
Solution 3 - C#
Deconstructions in C# 7.0 support three forms:
- deconstruction-declaration (like
(var x, var y) = e;
), - deconstruction-assignment (like
(x, y) = e;
), - and deconstruction-foreach (like
foreach(var(x, y) in e) ...
).
Other contexts were considered, but likely have decreasing utility and we couldn't complete them in the C# 7.0 timeframe. Deconstruction in a let clause (let (x, y) = e ...
) and in lambdas seem good candidates for future expansion.
The latter is being discussed in https://github.com/dotnet/csharplang/issues/258
Do voice your feedback and interest there, as it will help champion proposals.
More details on what was included in C# 7.0 deconstruction in the design doc.
Solution 4 - C#
The problem you're running into is the inability of the compiler to infer the type in this expression:
list.Select(((int x, int y) t) => t.x * 2 + t.y / 2);
But since (int, int)
and (int x, int y)
are the same CLR type (System.ValueType<int, int>
), if you specify the type parameters:
list.Select<(int x, int y), int>(t => t.x * 2 + t.y / 2);
It will work.