다음을 통해 공유


람다 개선 사항

메모

이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.

기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련 언어 디자인 모임(LDM) 노트에서 기록됩니다.

사양문서에서 기능 사양서를 C# 언어 표준으로 채택하는 과정에 대해 자세히 알아볼 수 있습니다.

챔피언 이슈: https://github.com/dotnet/csharplang/issues/4934

요약

제안된 변경 내용:

  1. 특성을 가진 람다를 허용합니다.
  2. 명시적 반환 형식의 람다 허용
  3. 람다 및 메서드 그룹에 대한 자연 대리자 형식 유추

동기

람다의 특성에 대한 지원은 메서드 및 로컬 함수와 패리티를 제공합니다.

명시적 반환 형식에 대한 지원은 명시적 형식을 지정할 수 있는 람다 매개 변수와 대칭을 제공합니다. 명시적 반환형을 허용하면, 현재 람다 본문을 서명을 결정하기 위해 바인딩해야 하는 중첩된 람다의 오버로드 해석에서 컴파일러 성능을 제어할 수 있습니다.

람다 식 및 메서드 그룹에 대한 자연 형식은 var 선언의 이니셜라이저를 포함하여 명시적 대리자 형식 없이 람다 및 메서드 그룹을 사용할 수 있는 더 많은 시나리오를 허용합니다.

람다와 메서드 그룹에 대한 명시적 대리자 형식 요구는 고객에게 문제점으로 작용해왔으며, 최근 MapAction작업으로 인해 ASP.NET의 발전을 저해하게 되었습니다.

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 기반 시나리오를 대상으로 합니다. Delegate 인스턴스와 연결된 MethodInfo를 검사하여 컴파일러가 내보낸 명시적 특성 및 기본 매개 변수와 같은 추가 메타데이터를 포함한 람다 식 또는 로컬 함수의 서명을 결정하는 것이 유효해야 합니다. 이렇게 하면 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는 메서드 시그니처를 나타냅니다: 매개 변수의 형식과 ref 종류, 반환 값의 형식과 ref 종류. 시그니처가 동일한 경우, 익명 함수 식과 메서드 그룹 모두 동일한 function_type를 갖습니다.

Function_types 몇 가지 특정 컨텍스트에서만 사용됩니다.

  • 암시적 및 명시적 변환
  • 메서드 형식 유추(§12.6.3) 및 가장 일반적인 형식(§12.6.3.15)
  • var 초기화자

function_type 컴파일 시간에만 존재합니다. function_types 원본 또는 메타데이터에 표시되지 않습니다.

변환

function_typeF에서는 암시적으로 function_type로 변환이 이루어집니다.

  • 매개 변수 및 반환 형식의 F이(가) G의 매개 변수 및 반환 형식으로 변환 가능하다면, 기능 유형G
  • System.MulticastDelegateSystem.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 대리자 형식 또는 식 트리 형식이경우 명시적 매개 변수 형식 유추EiTi만들어지고 EiTi 명시적 반환 형식 유추가 만들어집니다.
  • 그렇지 않으면 Ei 형식 U 있고 xi 값 매개 변수인 경우 하한 유추UTi.
  • 그렇지 않으면 Ei 형식 U 있고 xiref 또는 out 매개 변수인 경우 정확한 유추UTi.
  • 그렇지 않으면 이 인수에 대한 유추가 수행되지 않습니다.

명시적 반환 형식 유추

E명시적 반환 형식 유추 형식 T 다음과 같은 방식으로

  • E 명시적 반환 형식 Ur 있는 무명 함수이고 T 반환 형식이 Vr 대리자 형식 또는 식 트리 형식인 경우 UrVr정확한 유추(§12.6.3.9)가 만들어집니다.

수리

수정(§12.6.3.12)은 다른 변환이 함수 유형 변환보다 선호되도록 합니다. (람다 식 및 메서드 그룹 식은 하한에만 영향을 주므로 하한에만 function_types 처리해야 합니다.)

범위 집합이 있는 고정되지 않은 형식 변수 Xi 다음과 같이 고정됩니다.

  • Uj 후보 형식 집합은 함수 형식이 아닌 형식이경우 함수 형식이 하한에서 무시되는 Xi범위 집합의 모든 형식 집합으로 시작됩니다.
  • 그런 다음 각 Xi 경계를 차례로 검사합니다. Xi의 정확한 경계 U에 대해 U와 동일하지 않은 모든 Uj 유형은 후보 집합에서 제거됩니다. Xi 각 하한 U 대해 있는 모든 형식이 UjU 암시적 변환이 후보 집합에서 제거되지. Xi 모든 형식의 상한 UUjU 암시적 변환이 후보 집합에서 제거되지.
  • 나머지 후보 형식 중 Uj 다른 모든 후보 형식으로 암시적 변환이 있는 고유한 형식 V 있는 경우 XiV고정됩니다.
  • 그렇지 않으면 형식 유추가 실패합니다.

가장 일반적인 형식

가장 일반적인 형식(§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}과 매개 변수 형식 {P1, P2, ..., Pn}{Q1, Q2, ..., Qn}를 가진 두 개의 적용 가능한 함수 멤버 MpMq가 있을 때, MpMq보다으로 더 나은 함수 멤버로 정의됩니다.

  1. 각 인수에 대한 ExPx 암시적 변환은 function_type_conversion아니며
    • Mp은 비제네릭 메서드이거나, Mp는 타입 매개 변수 {X1, X2, ..., Xp}가 있는 제네릭 메서드입니다. 각 타입 매개 변수 Xi에 대해, 타입 인수는 식이나 function_type이외의 형식에서 유추됩니다.
    • 하나 이상의 인수에서 , ExQx로 암시적으로 변환하는 것이 function_type_conversion이거나, Mq가 형식 매개변수 {Y1, Y2, ..., Yq}을 가진 제네릭 메서드이며, 최소 하나의 형식 매개변수 Yi에 대해 형식 인수가 function_type에서 유추되거나.
  2. 각 인수에 대해 ExQx 암시적 변환은 ExPx암시적 변환보다 낫지 않으며 하나 이상의 인수에 대해 ExPx 변환이 ExQx변환보다 낫습니다.

식에서 더 나은 변환(§12.6.4.5)은 람다 식 또는 메서드 그룹에서 유추된 형식을 포함하지 않는 변환을 선호하도록 업데이트됩니다.

표현식에서 더 나은 변환

E 형식 T1변환하는 암시적 변환 C1E 형식 T2변환하는 암시적 변환 C2 경우 C1C2더 나은 변환입니다.

  1. C1함수_타입_변환 아니다. C2함수_타입_변환이며 또는
  2. E는 비상수 interpolated_string_expression이며, C1implicit_string_handler_conversion이고, T1applicable_interpolated_string_handler_type이며, C2implicit_string_handler_conversion이 아닙니다, 또는
  3. ET2과 정확히 일치하지 않으며, 다음 중 하나 이상이 적용됩니다.
    • ET1과 정확히 일치합니다 (§12.6.4.5)
    • T1 T2(§12.6.4.7)보다 더 나은 변환 대상입니다.

통사론

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?

결과 대리자 형식 외에도 컴파일러 API에서 function_type 사용할 수 있어야 하나요?

현재 유추된 대리자 형식은 매개 변수 및 반환 형식이 의 유효한 형식 인수이고, 매개 변수가 16개 이하인 경우 System.Action<> 또는 System.Func<>을 사용하며, 예상되는 Action<> 또는 Func<> 형식이 누락된 경우 오류가 보고됩니다. 대신 컴파일러가 System.Action<> 사용하거나 System.Func<> 여부와 관계없이 사용해야 하나요? 그리고 필요한 형식이 누락된 경우 대리자 형식을 합성하시겠습니까?