메모
이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.
기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련 언어 디자인 모임(LDM) 노트에서 기록됩니다.
사양문서에서 기능 사양서를 C# 언어 표준으로 채택하는 과정에 대해 자세히 알아볼 수 있습니다.
챔피언 이슈: https://github.com/dotnet/csharplang/issues/4934
요약
제안된 변경 내용:
- 특성을 가진 람다를 허용합니다.
- 명시적 반환 형식의 람다 허용
- 람다 및 메서드 그룹에 대한 자연 대리자 형식 유추
동기
람다의 특성에 대한 지원은 메서드 및 로컬 함수와 패리티를 제공합니다.
명시적 반환 형식에 대한 지원은 명시적 형식을 지정할 수 있는 람다 매개 변수와 대칭을 제공합니다. 명시적 반환형을 허용하면, 현재 람다 본문을 서명을 결정하기 위해 바인딩해야 하는 중첩된 람다의 오버로드 해석에서 컴파일러 성능을 제어할 수 있습니다.
람다 식 및 메서드 그룹에 대한 자연 형식은 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.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
대리자 형식 또는 식 트리 형식인 경우Ur
Vr
정확한 유추(§12.6.3.9)가 만들어집니다.
수리
수정(§12.6.3.12)은 다른 변환이 함수 유형 변환보다 선호되도록 합니다. (람다 식 및 메서드 그룹 식은 하한에만 영향을 주므로 하한에만 function_types 처리해야 합니다.)
범위 집합이 있는 고정되지 않은 형식 변수
Xi
다음과 같이 고정됩니다.
Uj
후보 형식 집합은 함수 형식이 아닌 형식이경우 함수 형식이 하한에서 무시되는Xi
범위 집합의 모든 형식 집합으로 시작됩니다.- 그런 다음 각
Xi
경계를 차례로 검사합니다.Xi
의 정확한 경계U
에 대해U
와 동일하지 않은 모든Uj
유형은 후보 집합에서 제거됩니다.Xi
각 하한U
대해 있는 모든 형식이Uj
U
암시적 변환이 후보 집합에서 제거되지.Xi
모든 형식의 상한U
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}
과 매개 변수 형식{P1, P2, ..., Pn}
및{Q1, Q2, ..., Qn}
를 가진 두 개의 적용 가능한 함수 멤버Mp
및Mq
가 있을 때,Mp
는Mq
보다으로 더 나은 함수 멤버로 정의됩니다.
- 각 인수에 대한
Ex
Px
암시적 변환은 function_type_conversion아니며
Mp
은 비제네릭 메서드이거나,Mp
는 타입 매개 변수{X1, X2, ..., Xp}
가 있는 제네릭 메서드입니다. 각 타입 매개 변수Xi
에 대해, 타입 인수는 식이나 function_type이외의 형식에서 유추됩니다.- 하나 이상의 인수에서 ,
Ex
을Qx
로 암시적으로 변환하는 것이 function_type_conversion이거나,Mq
가 형식 매개변수{Y1, Y2, ..., Yq}
을 가진 제네릭 메서드이며, 최소 하나의 형식 매개변수Yi
에 대해 형식 인수가 function_type에서 유추되거나.- 각 인수에 대해
Ex
Qx
암시적 변환은Ex
Px
암시적 변환보다 낫지 않으며 하나 이상의 인수에 대해Ex
Px
변환이Ex
Qx
변환보다 낫습니다.
식에서 더 나은 변환(§12.6.4.5)은 람다 식 또는 메서드 그룹에서 유추된 형식을 포함하지 않는 변환을 선호하도록 업데이트됩니다.
표현식에서 더 나은 변환
식
E
형식T1
변환하는 암시적 변환C1
식E
형식T2
변환하는 암시적 변환C2
경우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?
결과 대리자 형식 외에도 컴파일러 API에서 function_type 사용할 수 있어야 하나요?
현재 유추된 대리자 형식은 매개 변수 및 반환 형식이 및의 유효한 형식 인수이고, 매개 변수가 16개 이하인 경우 System.Action<>
또는 System.Func<>
을 사용하며, 예상되는 Action<>
또는 Func<>
형식이 누락된 경우 오류가 보고됩니다. 대신 컴파일러가 System.Action<>
사용하거나 System.Func<>
여부와 관계없이 사용해야 하나요? 그리고 필요한 형식이 누락된 경우 대리자 형식을 합성하시겠습니까?
C# feature specifications