비고
이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.
기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련 LDM(언어 디자인 모임) 노트에서 캡처됩니다.
C# 언어 표준에 기능 사양을 채택하는 프로세스에 대해 더 알아보려면 사양에 대한 문서를 참조하십시오.
챔피언 발행호: https://github.com/dotnet/csharplang/issues/8697
선언
문법
class_body
: '{' class_member_declaration* '}' ';'?
| ';'
;
class_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| finalizer_declaration
| static_constructor_declaration
| type_declaration
| extension_declaration // add
;
extension_declaration // add
: 'extension' type_parameter_list? '(' receiver_parameter ')' type_parameter_constraints_clause* extension_body
;
extension_body // add
: '{' extension_member_declaration* '}' ';'?
;
extension_member_declaration // add
: method_declaration
| property_declaration
| operator_declaration
;
receiver_parameter // add
: attributes? parameter_modifiers? type identifier?
;
확장 선언은 제네릭이 아닌 중첩되지 않은 정적 클래스에서만 선언되어야 합니다.
형식의 이름을 extension
지정하는 것은 오류입니다.
범위 지정 규칙
확장 선언의 형식 매개 변수 및 수신기 매개 변수는 확장 선언 본문 내의 범위에 있습니다. 식 내에서를 제외하고 정적 멤버 내에서 수신기 매개 변수를 참조하는 것은 오류입니다 nameof
. 멤버가 확장 선언의 형식 매개 변수 또는 수신기 매개 변수와 동일한 이름으로 형식 매개 변수 또는 매개 변수(멤버 본문 내에서 직접 지역 변수 및 로컬 함수)를 선언하는 것은 오류입니다.
public static class E
{
extension<T>(T[] ts)
{
public bool M1(T t) => ts.Contains(t); // `T` and `ts` are in scope
public static bool M2(T t) => ts.Contains(t); // Error: Cannot refer to `ts` from static context
public void M3(int T, string ts) { } // Error: Cannot reuse names `T` and `ts`
public void M4<T, ts>(string s) { } // Error: Cannot reuse names `T` and `ts`
}
}
멤버 자체가 포함된 확장 선언의 타입 매개변수 또는 리시버 매개변수와 같은 이름을 갖고 있는 것은 오류가 아닙니다. 멤버 이름은 확장 선언 내에서 단순 이름 조회에서 직접 찾을 수 없습니다. 따라서 조회는 멤버가 아닌 해당 이름의 형식 매개 변수 또는 수신기 매개 변수를 찾습니다.
멤버는 바깥쪽 정적 클래스에서 직접 선언되는 정적 메서드를 발생시키고 간단한 이름 조회를 통해 찾을 수 있습니다. 그러나 동일한 이름의 확장 선언 형식 매개 변수 또는 수신기 매개 변수가 먼저 발견됩니다.
public static class E
{
extension<T>(T[] ts)
{
public void T() { M(ts); } // Generated static method M<T>(T[]) is found
public void M() { T(ts); } // Error: T is a type parameter
}
}
정적 클래스를 확장 컨테이너로
확장은 현재 확장 메서드와 마찬가지로 최상위 비 제네릭 정적 클래스 내에서 선언되므로 클래식 확장 메서드 및 비 확장 정적 멤버와 공존할 수 있습니다.
public static class Enumerable
{
// New extension declaration
extension(IEnumerable source) { ... }
// Classic extension method
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
// Non-extension member
public static IEnumerable<int> Range(int start, int count) { ... }
}
확장 선언
확장 선언은 익명이며 연결된 형식 매개 변수 및 제약 조건과 확장 멤버 선언 집합이 포함된 수신기 사양 을 제공합니다. 수신기 사양은 매개 변수 형식이거나 정적 확장 멤버만 선언된 경우 형식일 수 있습니다.
public static class Enumerable
{
extension(IEnumerable source) // extension members for IEnumerable
{
public bool IsEmpty { get { ... } }
}
extension<TSource>(IEnumerable<TSource> source) // extension members for IEnumerable<TSource>
{
public IEnumerable<T> Where(Func<TSource, bool> predicate) { ... }
public IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) { ... }
}
extension<TElement>(IEnumerable<TElement>) // static extension members for IEnumerable<TElement>
where TElement : INumber<TElement>
{
public static IEnumerable<TElement> operator +(IEnumerable<TElement> first, IEnumerable<TElement> second) { ... }
}
}
수신기 사양의 형식을 수신기 형식 이라고 하며 매개 변수 이름(있는 경우)을 수신기 매개 변수라고 합니다.
수신기 매개 변수의 이름이 지정되면 수신기 형식이 정적이지 않을 수 있습니다.
수신자 매개 변수는 명명되지 않은 경우 한정자를 가질 수 없으며, scoped
경우를 제외하고는 아래에 나열된 참조 한정자만 가질 수 있습니다.
수신기 매개 변수는 클래식 확장 메서드의 첫 번째 매개 변수와 동일한 제한을 적용합니다.
이 특성은 [EnumeratorCancellation]
수신기 매개 변수에 배치되는 경우 무시됩니다.
확장 멤버
확장 멤버 선언은 클래스 및 구조체 선언의 해당 인스턴스 및 정적 멤버와 구문적으로 동일합니다(생성자 제외). 인스턴스 멤버는 수신기 매개 변수 이름을 가진 수신기를 참조합니다.
public static class Enumerable
{
extension(IEnumerable source)
{
// 'source' refers to receiver
public bool IsEmpty => !source.GetEnumerator().MoveNext();
}
}
수신기 매개 변수를 지정하지 않은 확장 선언의 경우 인스턴스 확장 멤버를 지정하는 것은 오류입니다.
public static class Enumerable
{
extension(IEnumerable) // No parameter name
{
public bool IsEmpty => true; // Error: instance extension member not allowed
}
}
확장 선언의 멤버에 다음 한정자를 지정하는 것은 오류입니다: abstract
, virtual
, override
, new
, sealed
, partial
, 및 protected
(및 관련 접근성 한정자).
확장 선언의 속성에는 init
접근자를 가져서는 안 됩니다.
수신기 매개 변수가 명명되지 않은 경우 인스턴스 멤버는 허용되지 않습니다.
특성으로 확장 멤버를 데코레이트하는 것은 오류입니다 [ModuleInitializer]
.
역류
기본적으로 수신기는 다른 매개 변수와 마찬가지로 값으로 인스턴스 확장 멤버에 전달됩니다.
그러나 매개 변수 형식의 확장 선언 수신기는 수신기 형식을 값 형식으로 알려져 있는 한 지정할 ref
ref readonly
수 있습니다in
.
지정된 경우 ref
인스턴스 멤버 또는 해당 접근자 중 하나를 선언 readonly
하여 수신기를 변경할 수 없습니다.
public static class Bits
{
extension(ref ulong bits) // receiver is passed by ref
{
public bool this[int index]
{
set => bits = value ? bits | Mask(index) : bits & ~Mask(index); // mutates receiver
readonly get => (bits & Mask(index)) != 0; // cannot mutate receiver
}
}
static ulong Mask(int index) => 1ul << index;
}
Null 허용 여부 및 특성
수신기 형식은 nullable 참조 형식이거나 포함할 수 있으며 매개 변수 형식의 수신기 사양은 다음과 같은 특성을 지정할 수 있습니다.
public static class NullableExtensions
{
extension(string? text)
{
public string AsNotNull => text is null ? "" : text;
}
extension([NotNullWhen(false)] string? text)
{
public bool IsNullOrEmpty => text is null or [];
}
extension<T> ([NotNull] T t) where T : class?
{
public void ThrowIfNull() => ArgumentNullException.ThrowIfNull(t);
}
}
클래식 확장 메서드와의 호환성
인스턴스 확장 메서드는 클래식 확장 메서드에 의해 생성된 아티팩트와 일치하는 아티팩트가 생성됩니다.
특히 생성된 정적 메서드에는 선언된 확장 메서드의 특성, 한정자 및 이름뿐만 아니라 확장 선언과 메서드 선언에서 연결된 형식 매개 변수 목록, 매개 변수 목록 및 제약 조건 목록이 해당 순서대로 포함됩니다.
public static class Enumerable
{
extension<TSource>(IEnumerable<TSource> source) // Generate compatible extension methods
{
public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
public IEnumerable<TSource> Select<TResult>(Func<TSource, TResult> selector) { ... }
}
}
생성:
[Extension]
public static class Enumerable
{
[Extension]
public static IEnumerable<TSource> Where<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate) { ... }
[Extension]
public static IEnumerable<TSource> Select<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector) { ... }
}
운영자
확장 연산자는 명시적 피연산자 형식을 가지고 있지만 확장 선언 내에서 선언해야 합니다.
public static class Enumerable
{
extension<TElement>(IEnumerable<TElement>) where TElement : INumber<TElement>
{
public static IEnumerable<TElement> operator *(IEnumerable<TElement> vector, TElement scalar) { ... }
public static IEnumerable<TElement> operator *(TElement scalar, IEnumerable<TElement> vector) { ... }
}
}
이렇게 하면 형식 매개 변수를 선언하고 유추할 수 있으며, 일반 사용자 정의 연산자를 피연산자 형식 중 하나로 선언해야 하는 방법과 유사합니다.
확인
유추 가능성: 메서드가 아닌 각 확장 멤버에 대해 확장 블록의 모든 형식 매개 변수를 확장 및 멤버의 결합된 파미터 집합에 사용해야 합니다.
고유성: 지정된 바깥쪽 정적 클래스 내에서 동일한 수신기 형식(모듈로 ID 변환 및 형식 매개 변수 이름 대체)을 가진 확장 멤버 선언 집합은 클래스 또는 구조체 선언 내의 멤버와 유사한 단일 선언 공간으로 처리되며 고유성에 대한 동일한 규칙이 적용됩니다.
public static class MyExtensions
{
extension<T1>(IEnumerable<int>) // Error! T1 not inferrable
{
...
}
extension<T2>(IEnumerable<T2>)
{
public bool IsEmpty { get ... }
}
extension<T3>(IEnumerable<T3>?)
{
public bool IsEmpty { get ... } // Error! Duplicate declaration
}
}
이 고유성 규칙의 애플리케이션에는 동일한 정적 클래스 내의 클래식 확장 메서드가 포함됩니다.
확장 선언 this
내의 메서드와 비교하기 위해 매개 변수는 해당 수신기 형식에 언급된 형식 매개 변수와 함께 수신기 사양으로 처리되고 나머지 형식 매개 변수와 메서드 매개 변수는 메서드 서명에 사용됩니다.
public static class Enumerable
{
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
extension(IEnumerable source)
{
IEnumerable<TResult> Cast<TResult>() { ... } // Error! Duplicate declaration
}
}
소비
확장 멤버 조회를 시도하면 가져온 정적 클래스 내의 모든 확장 선언은 using
수신기 형식에 관계없이 멤버를 후보로 기여합니다. 해결 방법의 일부로만 호환되지 않는 수신기 유형이 삭제된 후보가 있습니다.
인수의 형식(실제 수신기를 포함)과 확장 선언 및 확장 멤버 선언에서의 형식 매개변수를 결합하여 전체 제네릭 형식 추론이 시도됩니다.
명시적 형식 인수가 제공되면 확장 선언 및 확장 멤버 선언의 형식 매개 변수를 대체하는 데 사용됩니다.
string[] strings = ...;
var query = strings.Select(s => s.Length); // extension invocation
var query2 = strings.Select<string, int>(s => s.Length); // ... with explicit full set of type arguments
var query3 = Enumerable.Select(strings, s => s.Length); // static method invocation
var query4 = Enumerable.Where<string, int>(strings, s => s.Length); // ... with explicit full set of type arguments
public static class Enumerable
{
extension<TSource>(IEnumerable<TSource> source)
{
public IEnumerable<TResult> Select<TResult>(Func<T, TResult> predicate) { ... }
}
}
클래식 확장 메서드와 마찬가지로 내보낸 구현 메서드를 정적으로 호출할 수 있습니다.
이렇게 하면 컴파일러가 동일한 이름과 매개 변수의 수를 가진 확장 멤버 간의 혼동을 해결할 수 있습니다.
object.M(); // ambiguous
E1.M();
new object().M2(); // ambiguous
E1.M2(new object());
_ = _new object().P; // ambiguous
_ = E1.get_P(new object());
static class E1
{
extension(object)
{
public static void M() { }
public void M2() { }
public int P => 42;
}
}
static class E2
{
extension(object)
{
public static void M() { }
public void M2() { }
public int P => 42;
}
}
정적 확장 메서드는 인스턴스 확장 메서드처럼 처리되며, 수신자 형식의 추가 인수를 고려합니다.
확장 속성은 단일 매개 변수(수신기 매개 변수) 및 단일 인수(실제 수신기 값)를 사용하여 확장 메서드처럼 확인됩니다.
오버로드 해석 우선순위 속성 (OverloadResolutionPriorityAttribute)
묶은 정적 클래스 내의 확장 멤버는 ORPA 값에 따라 우선 순위가 지정됩니다. 바깥쪽 정적 클래스는 ORPA 규칙이 고려하는 "포함 형식"으로 간주됩니다.
확장 속성에 있는 모든 ORPA 특성은 속성의 접근자에 대한 구현 메서드에 복사되므로 이러한 접근자가 명확성 구문을 통해 사용될 때 우선 순위가 적용됩니다.
진입점
확장 블록의 메서드는 진입점 후보로 한정되지 않습니다("7.1 애플리케이션 시작" 참조). 참고: 구현 방법은 여전히 후보일 수 있습니다.
낮추기
확장 선언에 대한 낮추기 전략은 언어 수준 결정이 아닙니다. 그러나 언어 의미 체계를 구현하는 것 외에도 특정 요구 사항을 충족해야 합니다.
- 생성된 형식, 멤버 및 메타데이터의 형식은 다른 컴파일러가 사용하고 생성할 수 있도록 모든 경우에 명확하게 지정해야 합니다.
- 생성된 아티팩트는 나중에 합리적인 수정이 이전 버전에 대해 컴파일된 소비자를 중단해서는 안 된다는 점에서 안정적이어야 합니다.
이러한 요구 사항은 구현이 진행됨에 따라 더 구체화되어야 하며 합리적인 구현 접근 방식을 허용하기 위해 코너 사례에서 손상되어야 할 수 있습니다.
선언에 대한 메타데이터
각 확장 선언은 표식 메서드 및 확장 멤버를 사용하여 확장 형식으로 내보내집니다.
각 확장 멤버에는 수정된 서명이 있는 최상위 정적 구현 메서드가 함께 제공됩니다.
확장 선언을 포함하고 있는 정적 클래스는 [Extension]
특성으로 지정됩니다.
확장 유형
원본의 각 확장 선언은 메타데이터(스켈레톤 형식이라고도 함)에서 확장 선언으로 내보내집니다.
- 이름은 말할 수 없으며 프로그램의 어휘 순서에 따라 결정됩니다.
이름은 다시 컴파일할 때 안정적으로 유지되지 않습니다. 아래에서는<>E__
인덱스 다음에 사용합니다. 예:<>E__2
. - 해당 형식 매개 변수는 원본(특성 포함)에서 선언된 매개 변수입니다.
- 접근성은 공용입니다.
- 플래그로
specialname
표시됩니다.
원본의 확장 선언에 있는 메서드/속성 선언은 메타데이터에서 확장 형식의 멤버로 표시됩니다.
원래 메서드의 서명은 유지 관리되지만(특성 포함) 본문은 .로 throw null
바뀝니다.
이러한 작업은 IL에서 참조해서는 안 됩니다.
참고: ref 어셈블리와 비슷합니다. 본문이 없는 것과 달리 본문을 사용하는 throw null
이유는 IL 확인을 실행하고 전달할 수 있기 때문입니다(따라서 메타데이터의 완전성 유효성 검사).
확장 마커 메서드는 수신기 매개 변수를 인코딩합니다.
- 그것은 프라이빗하고 정적이며
<Extension>$
라고 불립니다. - 확장 선언에 수신기 매개 변수의 특성, 참조, 형식 및 이름이 있습니다.
- 수신기 매개 변수가 이름을 지정하지 않으면 매개 변수 이름이 비어 있습니다.
참고: 메타데이터(전체 및 참조 어셈블리)를 통해 확장 선언 기호를 왕복할 수 있습니다.
참고: 원본에서 중복 확장 선언을 찾을 때 메타데이터에서 확장 형식을 하나만 내보내도록 선택할 수 있습니다.
구현들
원본의 확장 선언에서 메서드/속성 선언에 대한 메서드 본문은 최상위 정적 클래스에서 정적 구현 메서드로 내보내집니다.
- 구현 메서드의 이름은 원래 메서드와 같습니다.
- 원래 메서드의 형식 매개 변수(특성 포함) 앞에 확장 선언에서 파생된 형식 매개 변수가 추가됩니다.
- 원래 메서드와 동일한 접근성 및 특성이 있습니다.
- 정적 메서드를 구현하는 경우 매개 변수와 반환 형식이 동일합니다.
- 인스턴스 메서드를 구현하는 경우 원래 메서드의 서명에 앞에 추가된 매개 변수가 있습니다. 이 매개 변수의 특성, 참조, 형식 및 이름은 관련 확장 선언에 선언된 수신기 매개 변수에서 파생됩니다.
- 구현 메서드의 매개 변수는 확장 선언 대신 구현 메서드가 소유한 형식 매개 변수를 참조합니다.
- 원래 멤버가 인스턴스 일반 메서드인 경우 구현 메서드는 특성으로
[Extension]
표시됩니다.
다음은 그 예입니다.
static class IEnumerableExtensions
{
extension<T>(IEnumerable<T> source)
{
public void Method() { ... }
internal static int Property { get => ...; set => ...; }
public int Property2 { get => ...; set => ...; }
}
extension(IAsyncEnumerable<int> values)
{
public async Task<int> SumAsync() { ... }
}
public static void Method2() { ... }
}
은 다음으로 내보내집니다.
[Extension]
static class IEnumerableExtensions
{
public class <>E__1<T>
{
private static <Extension>$(IEnumerable<T> source) => throw null;
public void Method() => throw null;
internal static int Property { get => throw null; set => throw null; }
public int Property2 { get => throw null; set => throw null; }
}
public class <>E__2
{
private static <Extension>$(IAsyncEnumerable<int> values) => throw null;
public Task<int> SumAsync() => throw null;
}
// Implementation for Method
[Extension]
public static void Method<T>(IEnumerable<T> source) { ... }
// Implementation for Property
internal static int get_Property<T>() { ... }
internal static void set_Property<T>(int value) { ... }
// Implementation for Property2
public static int get_Property2<T>(IEnumerable<T> source) { ... }
public static void set_Property2<T>(IEnumerable<T> source, int value) { ... }
// Implementation for SumAsync
[Extension]
public static int SumAsync(IAsyncEnumerable<int> values) { ... }
public static void Method2() { ... }
}
확장 멤버가 원본에서 사용될 때마다 구현 메서드에 대한 참조로 내보내집니다.
예를 들어 호출 enumerableOfInt.Method()
은 정적 호출로 내보내질 수 있습니다 IEnumerableExtensions.Method<int>(enumerableOfInt)
.
XML 문서
확장 블록의 문서 주석은 말할 수 없는 명명된 형식에 대해 내보내집니다(확장 블록의 DocID는 <>E__0'1
아래 예제에 있습니다).
각각을 사용하여 <paramref>
<typeparamref>
확장 매개 변수 및 형식 매개 변수를 참조할 수 있습니다.)
참고: 확장 멤버에서 확장 매개 변수 또는 형식 매개 변수(포함 <param>
및 <typeparam>
)를 문서화할 수 없습니다.
xml 문서를 사용하는 도구는 적절한 경우 확장 블록에서 <param>
와 <typeparam>
을 확장 멤버로 복사할 책임이 있습니다 [예를 들어, 매개 변수 정보는 인스턴스 멤버에 대해서만 복사되어야 합니다].
구현 메서드에서 <inheritdoc>
가 내보내지고, 이는 cref
을 통한 관련 확장 멤버를 참조합니다. 예를 들어 getter에 대한 구현 메서드는 확장 속성의 설명서를 참조합니다.
확장 멤버에 문서 주석 <inheritdoc>
이 없으면 생략됩니다.
확장 블록 및 확장 멤버의 경우 다음과 같은 경우 현재 경고하지 않습니다.
- 확장 매개 변수는 문서화되어 있지만 확장 멤버의 매개 변수는 문서화되지 않습니다.
- 또는 그 반대의 경우도 마찬가지입니다.
- 또는 문서화되지 않은 형식 매개 변수가 있는 동등한 시나리오에서
예를 들어 다음 문서 주석은 다음과 같습니다.
/// <summary>Summary for E</summary>
static class E
{
/// <summary>Summary for extension block</summary>
/// <typeparam name="T">Description for T</typeparam>
/// <param name="t">Description for t</param>
extension<T>(T t)
{
/// <summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
/// <typeparam name="U">Description for U</typeparam>
/// <param name="u">Description for u</param>
public void M<U>(U u) => throw null!;
/// <summary>Summary for P</summary>
public int P => 0;
}
}
다음 xml을 생성합니다.
<?xml version="1.0"?>
<doc>
<assembly>
<name>Test</name>
</assembly>
<members>
<member name="T:E">
<summary>Summary for E</summary>
</member>
<member name="T:E.<>E__0`1">
<summary>Summary for extension block</summary>
<typeparam name="T">Description for T</typeparam>
<param name="t">Description for t</param>
</member>
<member name="M:E.<>E__0`1.M``1(``0)">
<summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
<typeparam name="U">Description for U</typeparam>
<param name="u">Description for u</param>
</member>
<member name="P:E.<>E__0`1.P">
<summary>Summary for P</summary>
</member>
<member name="M:E.M``2(``0,``1)">
<inheritdoc cref="M:E.<>E__0`1.M``1(``0)"/>
</member>
<member name="M:E.get_P``1(``0)">
<inheritdoc cref="P:E.<>E__0`1.P"/>
</member>
</members>
</doc>
CREF 참조
확장 블록을 중첩된 형식처럼 처리할 수 있습니다. 이 블록은 해당 서명으로 주소를 지정할 수 있습니다(단일 확장 매개 변수가 있는 메서드인 것처럼).
예: E.extension(ref int).M()
.
static class E
{
extension(ref int i)
{
void M() { } // can be addressed by cref="E.extension(ref int).M()"
}
extension(ref int i)
{
void M(int i2) { } // can be addressed by cref="E.extension(ref int).M(int)"
}
}
일치하는 모든 확장 블록을 검색하는 것으로 알려진 기능입니다.
확장 멤버에 대한 정규화되지 않은 참조를 허용하지 않으므로 cref도 이를 허용하지 않습니다.
구문은 다음과 같습니다.
member_cref
: conversion_operator_member_cref
| extension_member_cref // added
| indexer_member_cref
| name_member_cref
| operator_member_cref
;
extension_member_cref // added
: 'extension' type_argument_list? cref_parameter_list '.' member_cref
;
qualified_cref
: type '.' member_cref
;
cref
: member_cref
| qualified_cref
| type_cref
;
최상위(extension_member_cref
)에서 extension(int).M
을 사용하는 것이나, 다른 확장(E.extension(int).extension(string).M
)에 중첩되었을 때 오류입니다.
참고: E.extension(int)
는 E
형식에 있는 "extension"이라는 메서드를 참조하므로 이 확장 블록에 대한 cref를 허용하지 않습니다.
파괴적 변경
형식 및 별칭의 이름은 "extension"일 수 없습니다.
열려 있는 문제
-
extension
대extensions
키워드로 확인extension
, LDM 2025-03-24) -
허용하지(답변: 예, 허용되지 않음, LDM 2025-06-11)[ModuleInitializer]
않으려는지 확인합니다. -
확장 블록을 진입점 후보로 삭제해도 됨확인(답변: 예, 삭제, LDM 2025-06-11) -
LangVer 논리 확인(새로운 확장 건너뛰기, 선택한 경우 고려 및 보고)(답변: 인스턴스 확장 메서드를 제외한 경우 무조건 바인딩 수행 및 LangVer 오류 보고, LDM 2025-06-11) - 확장 멤버에 액세스할 때 수신기 요구 사항을 조정해야 하나요? (주석) 예를 들어 .
new Struct() { Property = 42 }
- 이식성 문제에 비추어 그룹화/충돌 규칙을 재검토합니다. https://github.com/dotnet/roslyn/issues/79043
nameof (이름)
클래식 및 새 확장 메서드처럼 nameof에서 확장 속성을 허용하지 않아야 하나요?(답변: 'nameof(EnclosingStaticClass.ExtensionMember)를 사용하려고 합니다. .NET 10에서 디자인이 필요할 가능성이 있습니다. LDM 2025-06-11)
패턴 기반 구문
메서드
새로운 확장 메서드는 어디에서 실행되어야 하나요?(답변: 클래식 확장 메서드가 재생되는 동일한 위치, LDM 2025-05-05) 여기에는 다음이 포함됩니다.-
GetEnumerator
/GetAsyncEnumerator
안으로foreach
-
Deconstruct
의 분해, 위치 패턴 매칭 및 foreach -
Add
컬렉션 이니셜라이저에서 -
GetPinnableReference
에서fixed
-
GetAwaiter
에서await
다음을 제외합니다.
-
Dispose
/DisposeAsync
inusing
및foreach
-
MoveNext
/MoveNextAsync
안으로foreach
-
Slice
암시적 인덱서 내의int
인덱서 및 목록 패턴일 가능성이 있는 인덱서? -
GetResult
에서await
속성 및 인덱서
확장 속성과 인덱서는 어디에서 사용되어야 하나요?(답변: LDM 2025-05-05 4부터 시작하겠습니다.)
다음을 포함할 것입니다.
- 개체 이니셜라이저:
new C() { ExtensionProperty = ... }
- dictionary initializer:
new C() { [0] = ... }
-
with
:x with { ExtensionProperty = ... }
- 속성 패턴:
x is { ExtensionProperty: ... }
제외할 내용은 다음과 같습니다.
-
Current
에서foreach
-
IsCompleted
에서await
-
Count
/Length
목록 패턴의 속성 및 인덱서 -
Count
/Length
암시적 인덱서의 속성 및 인덱서
위임자 반환 속성
이 셰이프의 확장 속성이 LINQ 쿼리에서만 실행되어 인스턴스 속성이 수행하는 작업과 일치해야 하는지 확인합니다.(답변: 의미가 있습니다. LDM 2025-04-06)
목록 및 확산 패턴
- 확장
Index
/Range
인덱서가 목록 패턴에서 재생되어야 하는지 확인
확장 속성이 Count
/Length
적용되는 위치를 다시 방문합니다.
컬렉션 식
- 확장
Add
이 작동합니다. - 확장
GetEnumerator
은 스프레드에 대해 작동합니다. - 확장
GetEnumerator
은 요소 형식의 결정에 영향을 주지 않습니다(인스턴스여야 합니다). - 정적
Create
확장 메서드는 승인된 create 메서드로 간주되어서는 안 됩니다. - 확장 가능한 속성이 컬렉션 표현식에 영향을 주나요?
params
수집품
- 확장은
Add
허용되는 형식에 영향을 주지 않습니다.params
사전 식
- 확장 인덱서가 사전 식에서 작동하지 않는지 확인합니다. 인덱서의 존재는 사전 형식을 정의하는 데 필수적인 부분입니다.
extern
-
이식성을. (답변: 승인됨, LDM 2025-06-23)extern
허용할 https://github.com/dotnet/roslyn/issues/78572 계획입니다
확장 유형에 대한 명명/번호 매기기 체계
문제
현재 번호 매기기 시스템은 공용 API의 유효성 검사 에 문제가 발생하여 공용 API가 참조 전용 어셈블리와 구현 어셈블리 간에 일치하도록 합니다.
다음 중 하나를 변경해야 하나요? (답변: 도구를 조정하고 번호 매기기 구현을 수정하겠습니다, LDM 2025-05-05)
- 도구 조정
- 일부 TBD(콘텐츠 기반 명명 체계) 사용
- 일부 구문을 통해 이름을 제어할 수 있도록 합니다.
LINQ에서는 새 제네릭 확장 캐스트 메서드가 여전히 작동할 수 없습니다.
문제
역할/확장의 이전 디자인에서는 메서드의 형식 인수만 명시적으로 지정할 수 있었습니다.
하지만 이제 클래식 확장 메서드에서 보이지 않는 전환에 초점을 맞추고 있으므로 모든 형식 인수를 명시적으로 지정해야 합니다.
LINQ에서 확장 캐스트 메서드 사용 문제를 해결하지 못합니다.
이 시나리오를 수용하기 위해 확장 기능을 변경해야 하나요? (답변: 아니요, 확장 해상도 설계, LDM 2025-05-05를 재검토하지 않습니다.)
확장 멤버에서 확장 매개 변수 제한
다음을 허용해야 하나요? (답변: 아니요, 나중에 추가할 수 있음)
static class E
{
extension<T>(T t)
{
public void M<U>(U u) where T : C<U> { } // error: 'E.extension<T>(T).M<U>(U)' does not define type parameter 'T'
}
}
public class C<T> { }
Null 허용 여부
-
현재 디자인 확인, 즉 최대 이식성/호환성(답변: 예, LDM 2025-04-17)
extension([System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool b)
{
public void AssertTrue() => throw null!;
}
extension([System.Diagnostics.CodeAnalysis.NotNullIfNotNull("o")] ref int? i)
{
public void M(object? o) => throw null!;
}
메타데이터
스켈레톤 메서드가(답변: 예, LDM 2025-04-17)NotSupportedException
를 throw 하거나, 현재처럼 다른 표준 예외를 처리해야 하나요?새 버전이 더 많은 정보를 추가하는 경우 메타데이터의 표식 메서드에서 둘 이상의 매개 변수를 허용해야 하나요?(답변: 우리는 엄격할 수 있습니다, LDM 2025-04-17)확장 표식 또는 말할 수 있는 구현 메서드를 특수 이름으로 표시해야 하나요?(답변: 마커 메서드는 특수 이름으로 표시되어야 하며 우리는 이를 확인해야 하지만, 구현 메서드는 확인할 필요가 없습니다. LDM 2025-04-17)내부에 인스턴스 확장 메서드가 없는 경우에도 정적 클래스에 특성을 추가(답변: 예, LDM 2025-03-10)[Extension]
해야 하나요?구현 getter 및 setter에도(답변: 아니요, LDM 2025-03-10)[Extension]
속성을 추가해야 하는지 확인해 주십시오.-
확장 형식이 특수 이름으로 표시되어야 하며 컴파일러가 메타데이터에 이 플래그를 필요로 하는지 확인합니다(이는 미리 보기에서의 호환성을 깨는 변경 사항입니다)(답변: 승인됨, LDM 2025-06-23) -
ref
확장 유형 이름에 포함되지 않아야 하는지 확인합니다(WG가 그룹화/충돌 규칙을 다시 검토한 후 추가 논의 필요, LDM 2025-06-23)
public static class E
{
extension(ref int)
{
public static void M()
}
}
다음과 같이 내보내집니다.
public static class E
{
public static class <>ExtensionTypeXYZ
{
.. marker method ...
void M()
}
}
그리고 E.extension(ref int).M
에 대한 타사 CREF 참조는 M:E.<>ExtensionTypeXYZ.M()
로 내보내집니다.
확장 매개 변수에 ref
가 제거되거나 추가된다면, CREF가 중단되는 것을 원치 않을 것입니다.
정적 팩터리 시나리오
정적 메서드에 대한 충돌 규칙은 무엇인가요?(답변: 바깥쪽 정적 형식, 이완 없음, LDM 2025-03-17에 기존 C# 규칙 사용)
조회
이제 말할 수 있는 구현 이름이 있으므로 인스턴스 메서드 호출을 해결하는 방법기본 메서드를 해당 구현 메서드에 사용하는 것이 좋습니다.정적 확장 메서드를 해결하는 방법(답변: 인스턴스 확장 메서드와 마찬가지로 LDM 2025-03-03)속성을 해결하는 방법(2025년 3월 3일에 대략적으로 답변되었으나 향상을 위해 후속 조치가 필요합니다).-
확장 매개 변수 및 형식 매개 변수에 대한 범위 지정 및 섀도 지정 규칙(답변: 확장 블록 범위, 섀도 허용 안 함, LDM 2025-03-10) ORPA는 새 확장 메서드에 어떻게 적용해야 하나요?(답변: 확장 블록을 투명하게 처리하고, ORPA에 대한 "포함 형식"은 바깥쪽 정적 클래스인 LDM 2025-04-17입니다)
public static class Extensions
{
extension(Type1)
{
[OverloadResolutionPriority(1)]
public void Overload(...)
}
extension(Type2)
{
public void Overload(...)
}
}
ORPA를 새 확장 속성에 적용해야 하나요?(답변: 예 및 ORPA를 구현 방법, LDM 2025-04-23에 복사해야 합니다.)
public static class Extensions
{
extension(int[] i)
{
public P { get => }
}
extension(ReadOnlySpan<int> r)
{
[OverloadResolutionPriority(1)]
public P { get => }
}
}
- 클래식 확장 확인 규칙을 다시 연결하려면 어떻게 해야 할까요? 우리가 할까요?
- 클래식 확장 메서드에 대한 표준을 업데이트하고 이를 사용하여 새 확장 메서드를 설명합니다.
- 클래식 확장 메서드에 대한 기존 언어를 유지하고 새 확장 메서드를 설명하는 데도 사용하지만 둘 다에 대해 알려진 사양 편차가 있습니다.
- 클래식 확장 메서드에 대한 기존 언어를 유지하지만 새 확장 메서드에 다른 언어를 사용하고 클래식 확장 메서드에 대해 알려진 사양 편차만 있나요?
-
속성 액세스에서 명시적 형식 인수를 허용하지 않는지 확인합니다(답변: 명시적 형식 인수가 있는 속성 액세스 없음, WG에서 설명)
string s = "ran";
_ = s.P<object>; // error
static class E
{
extension<T>(T t)
{
public int P => 0;
}
}
-
수신기가 형식인 경우에도 더 나은 규칙 적용을 원하는지 확인합니다(답변: 정적 확장 멤버를 확인할 때 형식 전용 확장 매개 변수를 고려해야 함, LDM 2025-06-23)
int.M();
static class E1
{
extension(int)
{
public static void M() { }
}
}
static class E2
{
extension(in int i)
{
public static void M() => throw null;
}
}
- 메서드와 속성이 모두 적용 가능한 경우 모호성이 있는지 확인합니다(답변: .NET 10, LDM 2025-06-23을 차단하는 것이 아니라 현재 상태보다 더 잘 할 수 있도록 제안을 디자인해야 함)
- 모든 멤버가 향상되기를 원하지 않는지 확인한 후, 우승할 멤버 유형을 결정합니다.
string s = null;
s.M(); // error
static class E
{
extension(string s)
{
public System.Action M => throw null;
}
extension(object o)
{
public string M() => throw null;
}
}
확장 선언 내에 암시적 수신기가 있나요?(답변: 아니요, 이전에 LDM에서 논의되었음)
static class E
{
extension(object o)
{
public void M()
{
M2();
}
public void M2() { }
}
}
형식 매개 변수에 대한 조회를 허용해야 하나요?(토론) (답변: 아니요, 피드백을 기다리겠습니다. LDM 2025-04-16)
접근성
확장 선언 내에서 접근성의 의미는 무엇인가요?(답변: 확장 선언은 접근성 범위로 간주되지 않으며, LDM 2025-03-17)정적 멤버에 대해서도 수신기 매개 변수에 "일관성 없는 접근성" 검사를 적용해야 하나요?(답변: 예, LDM 2025-04-17)
public static class Extensions
{
extension(PrivateType p)
{
// We report inconsistent accessibility error,
// because we generate a `public static void M(PrivateType p)` implementation in enclosing type
public void M() { }
public static void M2() { } // should we also report here, even though not technically necessary?
}
private class PrivateType { }
}
확장 선언 유효성 검사
메서드만 있는 형식 매개 변수 유효성 검사(유추 가능성: 모든 형식 매개 변수가 확장 매개 변수의 형식에 표시되어야 함)를 완화해야 하나요?(답변: 예, LDM 2025-04-06) 이렇게 하면 100% 클래식 확장 메서드를 포팅할 수 있습니다.
TResult M<TResult, TSource>(this TSource source)
이 있다면,extension<TResult, TSource>(TSource source) { TResult M() ... }
으로 포팅할 수 있습니다.확장에서 init 전용 접근자를 허용해야 하는지 확인합니다(답변: 지금은 허용하지 않음, LDM 2025-04-17)수신기 ref-ness의 유일한 차이를 허용(답변: 아니요, 명시된 규칙을 유지하십시오, LDM 2025-03-24)extension(int receiver) { public void M2() {} }
extension(ref int receiver) { public void M2() {} }
해야 하나요?우리는 이런(답변: 예, 사양 규칙 유지, LDM 2025-03-24)extension(object receiver) { public int P1 => 1; }
extension(object receiver) { public int P1 {set{}} }
충돌에 대해 불평해야하는가?구현 방법 간의 충돌이 아닌 기본 메서드 간의 충돌에 대해 불평해야 하나요?(답변: 예, 사양 규칙 유지, LDM 2025-03-24)
static class E
{
extension(object)
{
public void Method() { }
public static void Method() { }
}
}
현재 충돌 규칙은 1입니다. 클래스/구조체 규칙을 사용하여 유사한 확장 내에서 충돌이 없는지 확인합니다( 2). 다양한 확장 선언에서 구현 메서드 간에 충돌이 없는지 확인합니다.
우리는 여전히 규칙 첫 번째 부분이 필요합니까?(답변: 예, API 소비에 도움이 되므로 이 구조를 유지합니다, LDM 2025년 03월 24일)
XML 문서
확장 멤버에서 지원되는 수신기 매개 변수인가요(답변: LDM 2025-05-05에 따르면 확장 멤버에서는 확장 매개 변수에 대한 'yes paramref'가 허용됩니다.)paramref
? 잡음에서도? 출력에서 인코딩되는 방법은 무엇인가요? 아마도 표준 방식<paramref name="..."/>
은 인간에게 적합할 수 있지만, 일부 기존 도구들이 API의 매개변수 목록에서 해당 항목을 찾지 못하여 문제가 발생할 위험이 있습니다.문서 주석을 말할 수 있는 이름으로 구현 메서드에 복사해야 하나요?(답변: 복사 없음, LDM 2025-05-05)인스턴스 메서드의 경우 확장 컨테이너에서 수신기 매개 변수에 해당하는(답변: 복사 없음, LDM 2025-05-05)<param>
요소를 복사해야 합니까? 컨테이너에서 구현 메서드(<typeparam>
등)로 다른 항목을 복사해야 하나요?확장 매개 변수를 재정의로 확장 멤버에 허용해야 할까요(답변: 아니요, 지금은 LDM 2025-05-05)<param>
?- 확장 블록에 대한 요약이 어디에나 표시될까요?
CREF (시립사회복지기금)
-
구문 확인(답변: 제안이 양호, LDM 2025-06-09) 확장 블록((답변: 아니요, LDM 2025-06-09)E.extension(int)
)을 참조할 수 있어야 하나요?정규화되지 않은 구문을(답변: 예, LDM 2025-06-09)extension(int).Member
사용하여 멤버를 참조할 수 있어야 하나요?- XML 이스케이프를 방지하기 위해 말할 수 없는 이름에 다른 문자를 사용해야 하나요? (답변: WG에게 맡기다, LDM 2025-06-09)
기본 구조 및 구현 메서드에 대한 참조가 모두 가능한(답변: 예, LDM 2025-06-09)E.M
지 확인합니다. 대.E.extension(int).M
둘 다 필요한 것 같습니다(확장 속성 및 클래식 확장 메서드의 이식성).- 확장 메타데이터 이름이 문서 버전 관리와 관련된가요?
더 많은 멤버 종류에 대한 지원 추가
이 디자인을 모두 한 번에 구현할 필요는 없지만 한 번에 하나 또는 몇 가지 멤버 종류에 접근할 수 있습니다. 핵심 라이브러리의 알려진 시나리오에 따라 다음 순서로 작업해야 합니다.
- 속성 및 메서드(인스턴스 및 정적)
- 운영자
- 인덱서(인스턴스 및 정적, 이전 지점에서 기회적으로 수행할 수 있음)
- 다른 항목
다른 종류의 멤버에 대한 디자인을 얼마나 전면 로드하시겠습니까?
extension_member_declaration // add
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| finalizer_declaration
| static_constructor_declaration
| type_declaration
;
중첩 형식
확장 중첩 형식으로 진행하도록 선택하는 경우 이전 토론의 몇 가지 참고 사항은 다음과 같습니다.
- 두 확장 선언이 동일한 이름과 차수로 중첩된 확장 형식을 선언하는 경우 충돌이 있을 수 있습니다. 메타데이터에서 이를 나타내는 솔루션이 없습니다.
- 메타데이터에 대해 설명한 대략적인 방법은 다음과 같습니다.
- 원래 형식 매개 변수가 있고 멤버가 없는 스켈레톤 중첩 형식을 내보내겠습니다.
- 확장 선언에 있는 형식 매개 변수가 앞에 추가된 상태로 중첩된 구현 형식을 생성하고, 원본에서 보이는 그대로 모든 멤버 구현을 포함합니다 (형식 매개 변수에 대한 참조를 포함하여).
생성자
생성자는 일반적으로 C#의 인스턴스 멤버로 설명됩니다. 해당 본문은 키워드를 통해 새로 만든 값에 액세스할 수 있기 때문에 this
.
하지만 매개 변수로 전달할 이전 값이 없으므로 인스턴스 확장 멤버에 대한 매개 변수 기반 접근 방식에서는 작동하지 않습니다.
대신 확장 생성자는 정적 팩터리 메서드처럼 작동합니다.
수신기 매개 변수 이름에 의존하지 않는다는 점에서 정적 멤버로 간주됩니다.
그들의 몸은 명시적으로 생성 결과를 만들고 반환해야합니다.
멤버 자체는 여전히 생성자 구문을 사용하여 선언되지만 이니셜라이저를 this
가질 base
수 없으며 액세스 가능한 생성자가 있는 수신기 형식에 의존하지 않습니다.
또한 인터페이스 및 열거형 형식과 같이 자체 생성자가 없는 형식에 대해 확장 생성자를 선언할 수 있습니다.
public static class Enumerable
{
extension(IEnumerable<int>)
{
public static IEnumerable(int start, int count) => Range(start, count);
}
public static IEnumerable<int> Range(int start, int count) { ... }
}
허용:
var range = new IEnumerable<int>(1, 100);
더 짧은 양식
제안된 디자인은 수신기 사양의 멤버별 반복을 방지하지만, 결국 확장 멤버가 정적 클래스와 확장 선언 내에 두 겹으로 중첩됩니다. 정적 클래스에 하나의 확장 선언만 포함하거나 확장 선언에 하나의 멤버만 포함되는 것이 일반적일 수 있으며, 이러한 경우의 구문 약어를 허용하는 것이 타당해 보입니다.
정적 클래스 및 확장 선언 병합:
public static class EmptyExtensions : extension(IEnumerable source)
{
public bool IsEmpty => !source.GetEnumerator().MoveNext();
}
이렇게 하면 확장 멤버에 대한 컨테이너 자체의 이름이 지정된 "형식 기반" 접근 방식과 비슷하게 표시됩니다.
병합 확장 선언 및 확장 멤버:
public static class Bits
{
extension(ref ulong bits) public bool this[int index]
{
get => (bits & Mask(index)) != 0;
set => bits = value ? bits | Mask(index) : bits & ~Mask(index);
}
static ulong Mask(int index) => 1ul << index;
}
public static class Enumerable
{
extension<TSource>(IEnumerable<TSource> source) public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
}
이렇게 하면 각 확장 멤버에 고유한 수신기 사양이 포함된 "멤버 기반" 접근 방식과 비슷하게 표시됩니다.
C# feature specifications