С# 7 кортежей и лямбда
С новым синтаксисом кортежей С# 7 можно ли указать лямбда с кортежем в качестве параметра и использовать распакованные значения внутри лямбда?
Пример:
var list = new List<(int,int)>();
обычный способ использования кортежа в лямбда:
list.Select(value => value.Item1*2 + value.Item2/2);
Я ожидал появления нового сахара, чтобы избежать .Item1
.Item2
, например:
list.Select((x,y) => x*2 + y/2);
Последняя строка не работает, потому что она рассматривается как два параметра для лямбда. Я не уверен, есть ли способ сделать это на самом деле.
EDIT:
Я попробовал двойной родительский учет в определении лямбда, и он не работал: ((x,y)) => ...
, и, возможно, было глупо пытаться, но двойная скобка действительно работает здесь:
list.Add((1,2));
Кроме того, мой вопрос не совсем о том, чтобы избежать уродливых имен по умолчанию .Item .Item2
, речь идет о фактической распаковке кортежа в лямбда (и, возможно, почему это не реализовано или невозможно). Если вы пришли сюда для решения имен по умолчанию, прочитайте ответ Сергея Березовского.
ИЗМЕНИТЬ 2:
Просто подумал о более общем случае: возможно ли (или почему нет) "деконструировать" кортеж, переданный методу? Вот так:
void Foo((int,int)(x,y)) { x+y; }
Вместо этого:
void Foo((int x,int y) value) { value.x+value.y }
Ответы
Ответ 1
Как вы заметили, для:
var list = new List<(int,int)>();
Можно было бы, по крайней мере, ожидать, что вы сможете сделать следующее:
list.Select((x,y) => x*2 + y/2);
Но компилятор С# 7 (пока) не поддерживает это. Также разумно желать сахара, который позволил бы:
void Foo(int x, int y) => ...
Foo(list[0]);
с автоматическим преобразованием компилятора Foo(list[0]);
в Foo(list[0].Item1, list[0].Item2);
.
В настоящее время ни один из них не является возможным. Тем не менее, проблема Предложение: Деконструкция набора строк в списке аргументов лямбда существует в ретрансляции dotnet/csharplang
на GitHub, требуя, чтобы языковая команда рассматривала эти функции для будущей версии С#. Пожалуйста, добавьте свои голоса в этот поток, если вы тоже захотите увидеть поддержку этого.
Ответ 2
Вы должны указать имена кортежей (ну, значения ValueTuple имеют поля), в противном случае будут использоваться имена по умолчанию, как вы видели:
var list = new List<(int x, int y)>();
Теперь кортежи имеют красиво названные поля, которые вы можете использовать
list.Select(t => t.x * 2 + t.y / 2)
Не забудьте добавить пакет System.ValueTuple из NuGet и имейте в виду, что ValueTuples являются изменяемыми структурами.
Обновление: деконструкция, представленная в настоящее время только как назначение существующим переменным (деконструкция-присваивание) или вновь созданным локальным переменным (деконструкция-декларация). Алгоритм выбора подходящего функционального элемента такой же, как и раньше:
Каждый аргумент в списке аргументов соответствует параметру в объявлении члена функции, как описано в п. 7.5.1.1, и любой параметр, которому не соответствует никакой аргумент, является необязательным параметром.
Переменная Tuple является единственным аргументом. Он не может соответствовать нескольким параметрам в списке формальных параметров метода.
Ответ 3
Деконструкции в С# 7.0 поддерживают три формы:
- deconstruction-declaration (например,
(var x, var y) = e;
),
- деконструкция-назначение (например,
(x, y) = e;
),
- и deconstruction-foreach (например,
foreach(var(x, y) in e) ...
).
Были рассмотрены другие контексты, но, вероятно, они уменьшали полезность, и мы не могли завершить их в таймфрейме С# 7.0. Деконструкция в позиции let (let (x, y) = e ...
) и в лямбдах представляется хорошим кандидатом для будущего расширения.
Последнее обсуждается в https://github.com/dotnet/csharplang/issues/258
Прошу прокомментировать ваши отзывы и интерес, так как это поможет продвигать предложения.
Подробнее о том, что было включено в деконструкцию С# 7.0 в doc.
Ответ 4
Программа, в которой вы работаете, - это неспособность компилятора вывести тип в этом выражении:
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);
Он будет работать.