Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
Заметка
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию 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