Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Примечание.
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Этот документ включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.
Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия отражены в соответствующих заметках с заседания по дизайну языка (LDM) .
Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .
Проблема чемпиона: https://github.com/dotnet/csharplang/issues/8637
Сводка
Разрешить объявлять лямбда-параметры с модификаторами, не требуя их имен типов. Например, (ref entry) =>
вместо (ref FileSystemEntry entry) =>
.
В качестве примера, возьмем этого делегата:
delegate bool TryParse<T>(string text, out T result);
Разрешить это упрощенное объявление параметров:
TryParse<int> parse1 = (text, out result) => Int32.TryParse(text, out result);
В настоящее время действительно лишь это:
TryParse<int> parse2 = (string text, out int result) => Int32.TryParse(text, out result);
Подробный дизайн
Грамматика
Никаких изменений. Последняя грамматика лямбда такова:
lambda_expression
: modifier* identifier '=>' (block | expression)
| attribute_list* modifier* type? lambda_parameter_list '=>' (block | expression)
;
lambda_parameter_list
: lambda_parameters (',' parameter_array)?
| parameter_array
;
lambda_parameter
: identifier
| attribute_list* modifier* type? identifier default_argument?
;
Эта грамматика уже считает modifiers* identifier
синтаксически законным.
Примечания
- Это не относится к лямбде без списка параметров.
ref x => x.ToString()
не будет законным. - Список лямбда-параметров по-прежнему не может смешивать
implicit_anonymous_function_parameter
иexplicit_anonymous_function_parameter
параметры. -
(ref readonly p) =>
,(scoped ref p) =>
, и(scoped ref readonly p) =>
будут разрешены, так же как и с явными параметрами, из-за:- улучшения структур низкого уровня в C# 11
- параметры
ref readonly
в C# 12
- Наличие или отсутствие типа не влияет на то, является ли модификатор обязательным или необязательным. Другими словами, если модификатор был необходим при наличии типа, он по-прежнему требуется и при его отсутствии. Аналогичным образом, если модификатор был необязательным с типом, он также необязателен и без него.
Семантика
https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/expressions#12192-anonymous-function-signatures обновляется следующим образом:
В lambda_parameter_list
все элементы lambda_parameter
должны либо иметь type
, либо не иметь type
. Первый является "явным типизированным списком параметров", а последний является "неявным типизированным списком параметров".
Параметры в неявном типизированном списке параметров не могут иметь default_argument
. Они могут иметь attribute_list
.
Следующее изменение требуется для anonymous function conversions:
[...]
Если F имеет явно или неявно типизированный список параметров, каждый параметр в D имеет одинаковый тип и модификаторы, что и соответствующий параметр в F, игнорируя модификаторы парам и значения по умолчанию.
Заметки и уточнения
scoped
и params
разрешены как явные модификаторы в лямбда-выражении без указания явного типа. Семантика остается одинаковой для обоих. В частности, ни то, ни другое не является частью определения, принятого в.
Если анонимная функция имеет explicit_anonymous_function_signature, набор совместимых типов делегатов и типов дерева выражений ограничен теми, которые имеют те же типы параметров и модификаторы в том же порядке.
Единственными модификаторами, ограничивающими совместимые типы делегатов, являются ref
, out
, in
и ref readonly
.
Например, в явно типизированной лямбде следующее в данный момент неоднозначно.
delegate void D<T>(scoped T t) where T : allows ref struct;
delegate void E<T>(T t) where T : allows ref struct;
class C
{
void M<T>() where T : allows ref struct
{
// error CS0121: The call is ambiguous between the following methods or properties: 'C.M1<T>(D<T>)' and 'C.M1<T>(E<T>)'
// despite the presence of the `scoped` keyword.
M1<T>((scoped T t) => { });
}
void M1<T>(D<T> d) where T : allows ref struct
{
}
void M1<T>(E<T> d) where T : allows ref struct
{
}
}
Это остается верным при использовании лямбд с неявно заданными типами.
delegate void D<T>(scoped T t) where T : allows ref struct;
delegate void E<T>(T t) where T : allows ref struct;
class C
{
void M<T>() where T : allows ref struct
{
// This will remain ambiguous. 'scoped' will not be used to restrict the set of delegates.
M1<T>((scoped t) => { });
}
void M1<T>(D<T> d) where T : allows ref struct
{
}
void M1<T>(E<T> d) where T : allows ref struct
{
}
}
Открытые вопросы
Следует ли
scoped
всегда быть модификатором в лямбда-коде в C# 14? Это важно для такого дела:M((scoped s = default) => { });
В этом случае это не подпадает под спецификацию "простого параметра лямбда", так как "простая лямбда" не может содержать инициализатор (
= default
). Таким образом,scoped
здесь рассматривается какtype
(как в C# 13). Мы хотим сохранить это? Или просто было бы проще иметь более общее правило, чтоscoped
всегда является модификатором и поэтому будет модификатором даже в случае недопустимого простого параметра?Перекомендация: сделайте это модификатором. Мы уже отговариваем людей от использования типов, которые полностью строчными буквами, И мы также сделали незаконным создание типа под названием
scoped
в C#. Таким образом, это может быть только какой-то случай ссылки на тип из другой библиотеки. Решение проблемы простое, если вы каким-то образом столкнулись с этой проблемой. Просто используйте@scoped
, чтобы сделать это имя типа вместо модификатора.Допустить
params
в простом параметре lambda? Работы с лямбда-функцией уже добавили поддержкуparams T[] values
в лямбда. Этот модификатор является необязательным, и лямбда и исходный делегат могут иметь несоответствие по этому модификатору (хотя мы предупреждаем, если делегат не имеет модификатора, а лямбда имеет). Если мы продолжаем разрешать это с помощью простого лямбда-параметра. например,M((params values) => { ... })
Перекомендация: Да. Разрешите это. Цель этой спецификации заключается в том, чтобы разрешить просто удалить тип из лямбда-параметра, сохраняя модификаторы. Это просто еще один случай этого. Это также просто вытекает из impl (как и вспомогательные атрибуты для этих параметров), поэтому это добавляет работы, если мы попытаемся это заблокировать.
Вывод 1.15.2025: Нет. Это всегда будет ошибкой. Для этого нет никаких полезных случаев, и никто не просит этого. Проще и безопаснее ограничить эту бессмысленную ситуацию с самого начала. Если представлены соответствующие варианты использования, мы можем пересмотреть.
Влияет ли 'scoped' на разрешение перегрузки? Например, если существует несколько перегрузок делегата, и одна из них имеет параметр с областью видимости, а другая — нет, повлияет ли наличие параметра с областью видимости на разрешение перегрузки.
Перекомендация: Нет. Не имейте разрешения перегрузки с учётом ограниченной области видимости. Это уже, как это работает с обычными явно типизированными лямбда-выражениями. Например:
delegate void D<T>(scoped T t) where T : allows ref struct; delegate void E<T>(T t) where T : allows ref struct; class C { void M<T>() where T : allows ref struct { M1<T>((scoped T t) => { }); } void M1<T>(D<T> d) where T : allows ref struct { } void M1<T>(E<T> d) where T : allows ref struct { } }
Это неоднозначно сегодня. Несмотря на то, что для
D<T>
установлен "скопирован" и есть "скопирован" в лямбда-параметре, мы это не разрешаем. Мы не считаем, что это должно измениться с неявно типизированными лямбда-выражениями.Вывод 15.01.2025: Приведенный выше вариант будет также справедлив для простых лямбас. 'Scoped' не будет влиять на разрешение перегрузки, в то время как
ref
иout
будут продолжать это делать.Разрешить лямбды '(scoped x) => ...'?
Рекомендация: Да. Если мы не разрешим это, мы можем оказаться в ситуациях, где пользователь может написать полную явно типизированную лямбда-версию, но не версию с неявной типизацией. Например:
delegate ReadOnlySpan<int> D(scoped ReadOnlySpan<int> x); class C { static void Main(string[] args) { D d = (scoped ReadOnlySpan<int> x) => throw null!; D d = (ReadOnlySpan<int> x) => throw null!; // error! 'scoped' is required } }
Удаление "scoped" приведет к ошибке (языку требуется соответствие в этом случае между лямбда-функцией и делегатом). Так как мы хотим, чтобы пользователь мог писать лямбда-коды, не указывая тип явным образом, это означает, что
(scoped x) => ...
необходимо разрешить.Вывод 15.01.2025: Мы позволим
(scoped x) => ...
лямбды.
C# feature specifications