Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
Заметка
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.
Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия зафиксированы в соответствующих заседаниях по проектированию языка (LDM).
Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .
Вопрос чемпиона: https://github.com/dotnet/csharplang/issues/4934
Сводка
Предлагаемые изменения:
- Разрешить лямбда-выражения с атрибутами
- Разрешить лямбда-выражения с явным типом возвращаемого значения
- Вывод естественного типа делегата для лямбда-групп и групп методов
Мотивация
Поддержка атрибутов для лямбда-кодов обеспечивает паритетность с методами и локальными функциями.
Поддержка явных типов возвращаемых данных обеспечивает симметрию с лямбда-параметрами, где можно указать явные типы. Разрешение явных типов возвращаемых значений также обеспечивает контроль над производительностью компилятора в вложенных лямбда-выражениях, где разрешение перегрузки должно привязать тело лямбда-выражения для определения сигнатуры.
Естественный тип для лямбда-выражений и групп методов позволит использовать больше сценариев, где лямбда-выражения и группы методов могут использоваться без явного типа делегата, включая в качестве инициализаторов в объявлениях var.
Требование явных типов делегатов для лямбда-выражений и групп методов стало камнем преткновения для клиентов и препятствием для прогресса в ASP.NET в связи с недавней работой над MapAction.
ASP.NET MapAction без предлагаемых изменений (MapAction() принимает аргумент System.Delegate):
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction((Func<Todo>)GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction((Func<Todo, Todo>)PostTodo);
ASP.NET MapAction с естественными типами для групп методов:
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction(GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction(PostTodo);
ASP.NET MapAction с атрибутами и естественными типами для лямбда-выражений.
app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name"));
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);
Атрибуты
Атрибуты могут быть добавлены в лямбда-выражения и лямбда-параметры. Чтобы избежать неоднозначности атрибутов метода и атрибутов параметров, лямбда-выражение с атрибутами должно использовать список параметров с скобками. Типы параметров не требуются.
f = [A] () => { }; // [A] lambda
f = [return:A] x => x; // syntax error at '=>'
f = [return:A] (x) => x; // [A] lambda
f = [A] static x => x; // syntax error at '=>'
f = ([A] x) => x; // [A] x
f = ([A] ref int x) => x; // [A] x
Можно указать несколько атрибутов, разделенных запятыми в одном списке атрибутов или в виде отдельных списков атрибутов.
var f = [A1, A2][A3] () => { }; // ok
var g = ([A1][A2, A3] int x) => x; // ok
Атрибуты не поддерживаются для анонимных методов объявленных с помощью синтаксиса delegate { }.
f = [A] delegate { return 1; }; // syntax error at 'delegate'
f = delegate ([A] int x) { return x; }; // syntax error at '['
Парсер будет изучать инициализатор коллекции с назначением элемента, чтобы отличить его от инициализатора коллекции с лямбда-выражением.
var y = new C { [A] = x }; // ok: y[A] = x
var z = new C { [A] x => x }; // ok: z[0] = [A] x => x
Средство синтаксического анализа будет рассматривать ?[ как начало условного доступа к элементу.
x = b ? [A]; // ok
y = b ? [A] () => { } : z; // syntax error at '('
Атрибуты лямбда-выражения или лямбда-параметров будут заноситься в метаданные метода, который сопоставлен с лямбда-выражением.
Как правило, клиентам не следует полагаться на то, как лямбда-выражения и локальные функции отображаются из исходного кода в метаданные. Как лямбда-коды и локальные функции могут выдаваться и изменяются между версиями компилятора.
Предлагаемые здесь изменения предназначены для сценария, управляемого Delegate.
Чтобы подтвердить правильность проверки MethodInfo, связанной с экземпляром Delegate, следует определить подпись лямбда-выражения или локальной функции, включая любые явные атрибуты и дополнительные метаданные, сгенерированные компилятором, такие как параметры по умолчанию.
Это позволяет таким командам, как ASP.NET сделать доступными те же действия для лямбда-кодов и локальных функций, что и обычные методы.
Явный тип возвращаемого значения
Явный тип возвращаемого значения может быть указан перед параметрами, заключёнными в круглые скобки.
f = T () => default; // ok
f = short x => 1; // syntax error at '=>'
f = ref int (ref int x) => ref x; // ok
f = static void (_) => { }; // ok
f = async async (async async) => async; // ok?
Средство синтаксического анализа будет смотреть вперед, чтобы отличить вызов метода T() от лямбда-выражения T () => e.
Явные типы возвращаемых данных не поддерживаются для анонимных методов, объявленных с помощью синтаксиса delegate { }.
f = delegate int { return 1; }; // syntax error
f = delegate int (int x) { return x; }; // syntax error
Вывод типа метода должен сделать точное вывод из явного лямбда-возвращаемого типа.
static void F<T>(Func<T, T> f) { ... }
F(int (i) => i); // Func<int, int>
Преобразования ковариантности не допускаются из типа возвращаемого значения лямбда-выражения к типу возвращаемого значения делегата (аналогично текущему поведению для типов параметров).
Func<object> f1 = string () => null; // error
Func<object?> f2 = object () => x; // warning
Средство синтаксического анализа позволяет лямбда-выражениям с ref возвращать типы в выражениях без дополнительных скобок.
d = ref int () => x; // d = (ref int () => x)
F(ref int () => x); // F((ref int () => x))
var нельзя использовать в качестве явного возвращаемого типа для лямбда-выражений.
class var { }
d = var (var v) => v; // error: contextual keyword 'var' cannot be used as explicit lambda return type
d = @var (var v) => v; // ok
d = ref var (ref var v) => ref v; // error: contextual keyword 'var' cannot be used as explicit lambda return type
d = ref @var (ref var v) => ref v; // ok
Тип естественной функции
Выражение анонимной функции (§12.19) (выражение лямбда-выражения или анонимного метода ) имеет естественный тип, если типы параметров являются явными, а возвращаемый тип — явным или может быть выведен (см. §12.6.3.13).
Группа методов имеет естественный тип, если все методы-кандидаты имеют общую сигнатуру. (Если группа методов может включать методы расширения, кандидаты включают содержащий тип и все области методов расширения.)
Естественный тип анонимного выражения функции или группы методов — это function_type. function_type представляет сигнатуру метода: типы параметров и виды ссылок, а также тип возврата и вид ссылки. Анонимные выражения функций или группы методов с той же сигнатурой имеют одинаковые function_type.
Function_types используются только в нескольких конкретных контекстах:
- неявные и явные преобразования
- Вывод типа метода (§12.6.3) и лучший распространенный тип (§12.6.3.15)
- инициализаторы
var
function_type существует только во время компиляции: function_types не отображаются в исходном коде или метаданных.
Преобразования
Из function_typeF есть неявные преобразования function_type:
- Для function_type
G, если параметры и возвращаемые типыFможно преобразовать по ковариантности в параметры и тип возвращаемого значенияG - К
System.MulticastDelegateили к базовым классам или интерфейсамSystem.MulticastDelegate -
System.Linq.Expressions.ExpressionилиSystem.Linq.Expressions.LambdaExpression
Анонимные выражения функций и группы методов уже имеют преобразования из выражений в типы делегатов и типы деревьев выражений (см. анонимные преобразования функций §10.7 и преобразования групп методов §10.8). Эти преобразования достаточно для преобразования в строго типизированные типы делегатов и типы дерева выражений. Приведенные выше преобразования function_type добавляют преобразования из типа только в базовые типы: System.MulticastDelegate, System.Linq.Expressions.Expressionи т. д.
Нет преобразований в function_type из типа, отличного от function_type. Отсутствуют явные преобразования для function_types, поскольку нельзя ссылаться на function_types в исходном коде.
Преобразование в System.MulticastDelegate или базовый тип или интерфейс реализует анонимную функцию или группу методов в качестве экземпляра соответствующего типа делегата.
Преобразование в System.Linq.Expressions.Expression<TDelegate> или базовый тип реализует лямбда-выражение как дерево выражений с соответствующим типом делегата.
Delegate d = delegate (object obj) { }; // Action<object>
Expression e = () => ""; // Expression<Func<string>>
object o = "".Clone; // Func<object>
Function_type преобразования не являются неявными или явными стандартными преобразованиями §10.4 и не учитываются при определении того, применяется ли определяемый пользователем оператор преобразования к анонимной функции или группе методов. Из оценки определяемых пользователем преобразований §10.5.3:
Для применимого оператора преобразования необходимо выполнить стандартное преобразование (§10.4) из исходного типа в тип операнда оператора, и необходимо выполнить стандартное преобразование из типа результата оператора в целевой тип.
class C
{
public static implicit operator C(Delegate d) { ... }
}
C c;
c = () => 1; // error: cannot convert lambda expression to type 'C'
c = (C)(() => 2); // error: cannot convert lambda expression to type 'C'
Сообщается предупреждение о неявном преобразовании группы методов в object, так как преобразование является допустимым, но, возможно, непреднамеренным.
Random r = new Random();
object obj;
obj = r.NextDouble; // warning: Converting method group to 'object'. Did you intend to invoke the method?
obj = (object)r.NextDouble; // ok
Вывод типов
Существующие правила вывода типов в основном не изменяются (см. §12.6.3). Однако существует несколько изменений, ниже к конкретным этапам вывода типов.
Первый этап
Первый этап (§12.6.3.2) позволяет анонимной функции привязаться к Ti, даже если Ti не является делегатом или типом дерева выражений (возможно, параметр типа ограничен System.Delegate, например).
Для каждого аргумента метода
Ei:
- Если
Eiявляется анонимной функцией иTiявляется типом делегата или типом дерева выражений, явного вывода типа параметра производится изEiвTi, а явный вывод типа возврата производится изEiнаTi.- В противном случае, если
Eiимеет типUиxiявляется параметром значения, то вывод нижней границы выполняется изUкTi.- В противном случае, если
Eiимеет типUиxiявляется параметромrefилиout, то точное вывод выполняется отUдоTi.- В противном случае для этого аргумента никакого логического вывода не делается.
Явное выведение типа возврата
вывод типа явного вывода типа выполняется из выражения
EтипаTследующим образом:
- Если
Eявляется анонимной функцией с явным типом возвратаUrиTявляется типом делегата или деревом выражений с типом возвратаVr, то точный вывод (§12.6.3.9) выполняется изUrнаVr.
Закрепление
Исправление (§12.6.3.12) гарантирует, что другие преобразования предпочтительнее преобразований типа функции . (Лямбда-выражения и выражения групп методов способствуют только нижним границам, поэтому обработка function_types необходима только для нижних границ.)
Переменная типа
Xiс набором границ исправлена следующим образом:
- Набор типов кандидатов
Ujначинается как набор всех типов в наборе границ дляXi, где типы функций игнорируются в нижних границах, если нет типов функций.- Затем мы рассмотрим каждый привязанный
Xi, в свою очередь: для каждого точно привязанногоUXiвсех типовUjкоторые не идентичныUудаляются из набора кандидатов. Для каждой нижней границыUXiвсех типовUj, к которым не не неявное преобразование изUудаляются из набора кандидатов. Для каждой верхней границыUXiвсех типовUj, из которых не неявное преобразование вUудаляются из набора кандидатов.- Если среди оставшихся типов кандидатов
Ujсуществует уникальный типV, из которого возможно неявное преобразование во все остальные типы кандидатов, тоXiзакреплён заV.- В противном случае вывод типа завершается ошибкой.
Лучший распространенный тип
Лучший распространенный тип (§12.6.3.15) определяется с точки зрения вывода типа, поэтому приведенные выше изменения вывода типов применяются к лучшему общему типу.
var fs = new[] { (string s) => s.Length, (string s) => int.Parse(s) }; // Func<string, int>[]
var
Анонимные функции и группы методов с типами функций можно использовать в качестве инициализаторов в объявлениях var.
var f1 = () => default; // error: cannot infer type
var f2 = x => x; // error: cannot infer type
var f3 = () => 1; // System.Func<int>
var f4 = string () => null; // System.Func<string>
var f5 = delegate (object o) { }; // System.Action<object>
static void F1() { }
static void F1<T>(this T t) { }
static void F2(this string s) { }
var f6 = F1; // error: multiple methods
var f7 = "".F1; // error: the delegate type could not be inferred
var f8 = F2; // System.Action<string>
Типы функций не используются при присваивании для отбрасываемых переменных.
d = () => 0; // ok
_ = () => 1; // error
Типы делегатов
Тип делегата для анонимной функции или группы методов с типами параметров P1, ..., Pn и типом возврата R:
- Если какой-либо параметр или возвращаемое значение не передаются по значению, или если более 16 параметров, или если любой из типов параметров или возвращаемое значение недопустимого типа аргумента (например,
(int* p) => { }), тогда делегат — это синтезированныйinternalанонимный тип делегата с подписью, которая соответствует анонимной функции или группе методов, с именами параметровarg1, ..., argnилиarg, если параметр один. - Если
Rравноvoid, то тип делегата —System.Action<P1, ..., Pn>; - В противном случае тип делегата
System.Func<P1, ..., Pn, R>.
Компилятор может позволить большему количеству сигнатур привязываться к типам System.Action<> и System.Func<> в будущем (если типы ref struct разрешены в качестве аргументов типов, например).
modopt() или modreq() в сигнатуре группы методов игнорируются в соответствующем типе делегата.
Если для двух анонимных функций или групп методов в одной компиляции требуются синтезированные типы делегатов с одинаковыми типами параметров и модификаторами и одинаковыми типами возвращаемых и модификаторов, компилятор будет использовать тот же синтезированный тип делегата.
Разрешение перегрузки
Обновленный член функции (§12.6.4.3) теперь предпочитает тех членов, в которых ни одно из преобразований и ни один из аргументов типа не выводятся из лямбда-выражений или групп методов.
Более подходящий член функции
... Учитывая список аргументов
Aс набором выражений аргументов{E1, E2, ..., En}и двумя применимыми элементами функцииMpиMqс типами параметров{P1, P2, ..., Pn}и{Q1, Q2, ..., Qn},Mpопределяется как более подходящий элемент функции, чемMq, если
- для каждого аргумента неявное преобразование из
ExвPxне является преобразованием типа функциии
Mpявляется не универсальным методом илиMpявляется универсальным методом с параметрами типа{X1, X2, ..., Xp}и для каждого параметра типаXiаргумент типа выводится из выражения или типа, отличного от function_type, и- по крайней мере для одного аргумента неявное преобразование из
ExвQxявляется преобразованием типа функцииилиMqявляется универсальным методом с параметрами типа{Y1, Y2, ..., Yq}и по крайней мере для одного параметра типаYiаргумент типа выводится из типа функцииили- для каждого аргумента неявное преобразование из
ExвQxне лучше, чем неявное преобразование изExвPx, а для хотя бы одного аргумента преобразование изExвPxлучше, чем преобразование изExвQx.
Более предпочтительное преобразование выражения (§12.6.4.5) обновлено, чтобы предпочитать преобразования, не подразумевающие использование выводимых типов из лямбда-выражений или групп методов.
Улучшенное преобразование из выражения
Учитывая неявное преобразование
C1, которое преобразуется из выраженияEв типT1, а неявное преобразованиеC2, которое преобразуется из выраженияEв типT2,C1— это лучшее преобразование, чемC2, если:
C1не является преобразованием типа функции иC2является преобразованием типа функцииилиEявляется неконстантным interpolated_string_expression,C1является implicit_string_handler_conversion,T1является applicable_interpolated_string_handler_type, иC2не является implicit_string_handler_conversion, илиEточно не соответствуетT2, и истинно хотя бы одно из следующих условий:
Синтаксис
lambda_expression
: modifier* identifier '=>' (block | expression)
| attribute_list* modifier* type? lambda_parameters '=>' (block | expression)
;
lambda_parameters
: lambda_parameter
| '(' (lambda_parameter (',' lambda_parameter)*)? ')'
;
lambda_parameter
: identifier
| attribute_list* modifier* type? identifier equals_value_clause?
;
Открытые проблемы
Следует ли поддерживать значения по умолчанию для лямбда-параметров выражения для полноты?
Следует ли System.Diagnostics.ConditionalAttribute быть запрещён для лямбда-выражений, так как есть несколько сценариев, где лямбда-выражение может использоваться условно?
([Conditional("DEBUG")] static (x, y) => Assert(x == y))(a, b); // ok?
Должен ли function_type быть доступен из API компилятора, помимо результирующего типа делегата?
В настоящее время используемый тип делегата предполагает использование System.Action<> или System.Func<>, если параметры и возвращаемые типы являются допустимыми аргументами типа и, если параметров не более 16, и если ожидаемый тип Action<> или Func<> отсутствует, то сообщается об ошибке. Вместо этого следует ли компилятору использовать System.Action<> или System.Func<> независимо от арасти? И если ожидаемый тип отсутствует, синтезировать тип делегата в противном случае?
C# feature specifications