C# 7元组和Lambda表达式

93

使用新的C# 7元组语法,是否可以指定一个以元组作为参数的lambda,并在lambda内部使用展开的值?

示例:

使用新的C#7元组语法,能否在lambda中指定一个带有元组参数,并使用展开后的变量?

示例:

var list = new List<(int,int)>();

使用lambda表达式中的元组的常规方法:

list.Select(value => value.Item1*2 + value.Item2/2);

我希望有一种新的方式来避免使用.Item1.Item2,例如:

list.Select((x,y) => x*2 + y/2);

最后一行不起作用是因为它被视为lambda的两个参数。我不确定是否有实际解决方法。

编辑:

我尝试在lambda定义中使用双括号,并且它没有起作用:((x,y)) => ...,也许尝试这样做是愚蠢的,但是双括号确实在这里起作用:

list.Add((1,2));

此外,我的问题不完全是关于避免丑陋的默认名称.Item .Item2,而是关于在 lambda 中实际解包元组(以及为什么它未被实现或不可能实现)。如果您来到这里是为了找到默认名称的解决方案,请阅读Sergey Berezovskiy的答案

编辑2:

刚想到一个更一般的用例:是否可以将传递给方法的元组“解构”? 像这样:

void Foo((int,int)(x,y)) { x+y; }

改为这样:

void Foo((int x,int y) value) { value.x+value.y }
4个回答

68

正如您所观察到的那样,对于:

var list = new List<(int,int)>();

至少应该能够做到以下事情:

list.Select((x,y) => x*2 + y/2);

但是目前(尚)不支持C# 7编译器。希望能够提供以下语法糖也是合理的:

void Foo(int x, int y) => ...

Foo(list[0]);

使用编译器会自动将Foo(list[0]);转换为Foo(list[0].Item1, list[0].Item2);,但目前这两种方法都不可行。然而,问题“提案:在lambda参数列表中解构元组”已经存在于GitHub上的dotnet/csharplang存储库中,请求语言团队考虑在未来的C#版本中支持这些特性。如果您也希望看到对此的支持,请在该线程中发表您的意见。


49

如果不指定元组属性的名称(值元组有字段),则将使用默认名称,就像您看到的那样:

var list = new List<(int x, int y)>();

现在元组有易于使用的命名字段

list.Select(t => t.x * 2 + t.y / 2)

请别忘了从NuGet中添加包,需要注意的是ValueTuples是可变结构体。
更新:解构目前仅表示为对现有变量(解构赋值)或新创建的本地变量(解构声明)的分配。适用的函数成员选择算法与以前相同:

参数列表中的每个参数都对应于函数成员声明中的一个参数,如§7.5.1.1中所述,并且任何未对应参数的参数都是可选参数。

元组变量是单个参数,它不能对应于方法的形式参数列表中的多个参数。

不错,但只是重命名,值仍然是传递给lambda的单个对象的字段。我很好奇是否有一种方法将其解构为多个变量。 - Rast
1
据我所知,元组的解构仅适用于变量声明可以包括来自元组的赋值的上下文环境。这将排除参数列表。Sergey的示例似乎非常干净;它有什么不适合您的需求吗? - Peter Duniho
2
@Peter Duniho 这只是我的好奇心。我认为在这种情况下,解构或类似的东西可能会有用。 - Rast
@Rast Enumerable.Select 的参数是一个 Func<TSource, TResult> 委托。它接受 TSource 类型的单个参数,您无法更改它。 - Sergey Berezovskiy
1
@Rast:也许吧,但在参数列表中是不允许非默认值的赋值的。你可以尝试这样做:list.Select(t => { (int x, int y) = t; return x * 2 + y / 2 });,但此时我认为你正在失去元组语法相对于上面的替代方案的简洁性。 - Peter Duniho

17

C# 7.0中的解构支持三种形式:

  • 解构声明(例如,(var x, var y) = e;),
  • 解构赋值(例如,(x, y) = e;),
  • 以及解构循环(例如,foreach(var(x, y) in e) ...)。

其他上下文也被考虑过,但可能效用递减,我们无法在C# 7.0时间范围内完成它们。在let子句(let (x, y) = e ...)和lambda中进行解构似乎是未来扩展的好选择。

后一种正在https://github.com/dotnet/csharplang/issues/258上讨论。

请在那里发表您的反馈和兴趣,因为这将有助于推动提案。

有关C# 7.0中包含的解构的更多详细信息,请参阅设计文档


13
你遇到的问题是编译器无法推断出这个表达式中的类型:
list.Select(((int x, int y) t) => t.x * 2 + t.y / 2);

但是由于(int, int)(int x, int y)是相同的CLR类型(System.ValueType<int, int>),如果你指定了类型参数:

list.Select<(int x, int y), int>(t => t.x * 2 + t.y / 2);

它会起作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接