인터페이스의 정적 추상 멤버
메모
이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.
기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 관련 언어 디자인 회의(LDM) 노트에서 이러한 차이가 기록됩니다.
사양문서에서 기능 사양을 C# 언어 표준으로 채택하는 프로세스에 대해 자세히 알아볼 수 있습니다.
요약
인터페이스는 구현하는 클래스 및 구조체에 의해 명시적 또는 암시적으로 구현되어야 하는 추상적인 정적 멤버를 지정할 수 있습니다. 인터페이스에 의해 제한되는 형식 매개 변수에서 멤버에 액세스할 수 있습니다.
동기
현재 정적 멤버를 추상화하고 이러한 정적 멤버를 정의하는 형식에 적용되는 일반화된 코드를 작성할 수 있는 방법은 없습니다. 이는 정적 형식, 특히 연산자에서만 존재할
이 기능을 사용하면 지정된 연산자의 존재를 지정하는 인터페이스 제약 조건으로 표현되는 숫자 형식에 대한 제네릭 알고리즘을 사용할 수 있습니다. 따라서 이러한 연산자를 기준으로 알고리즘을 표현할 수 있습니다.
// Interface specifies static properties and operators
interface IAddable<T> where T : IAddable<T>
{
static abstract T Zero { get; }
static abstract T operator +(T t1, T t2);
}
// Classes and structs (including built-ins) can implement interface
struct Int32 : …, IAddable<Int32>
{
static Int32 IAddable.operator +(Int32 x, Int32 y) => x + y; // Explicit
public static int Zero => 0; // Implicit
}
// Generic algorithms can use static members on T
public static T AddAll<T>(T[] ts) where T : IAddable<T>
{
T result = T.Zero; // Call static operator
foreach (T t in ts) { result += t; } // Use `+`
return result;
}
// Generic method can be applied to built-in and user-defined types
int sixtyThree = AddAll(new [] { 1, 2, 4, 8, 16, 32 });
구문
인터페이스 멤버
이 기능을 사용하면 정적 인터페이스 멤버를 가상으로 선언할 수 있습니다.
C# 11 이전 규칙
C# 11 이전에는 인터페이스의 인스턴스 멤버가 암시적으로 추상(또는 기본 구현이 있는 경우 가상)이지만 필요에 따라 abstract
(또는 virtual
) 한정자를 가질 수 있습니다. 가상이 아닌 인스턴스 멤버는 반드시 sealed
키워드로 명시적으로 표시해야 합니다.
현재 정적 인터페이스 멤버는 암시적으로 가상이 아니며 abstract
, virtual
또는 sealed
한정자를 허용하지 않습니다.
제안
추상 정적 멤버
필드 이외의 정적 인터페이스 멤버도 abstract
한정자를 가질 수 있습니다. 추상 정적 멤버는 본문을 가질 수 없습니다(또는 속성의 경우 접근자가 본문을 가질 수 없음).
interface I<T> where T : I<T>
{
static abstract void M();
static abstract T P { get; set; }
static abstract event Action E;
static abstract T operator +(T l, T r);
static abstract bool operator ==(T l, T r);
static abstract bool operator !=(T l, T r);
static abstract implicit operator T(string s);
static abstract explicit operator string(T t);
}
가상 정적 멤버
필드 이외의 정적 인터페이스 멤버도 virtual
한정자를 가질 수 있습니다. 가상 정적 멤버는 본문을 포함해야 합니다.
interface I<T> where T : I<T>
{
static virtual void M() {}
static virtual T P { get; set; }
static virtual event Action E;
static virtual T operator +(T l, T r) { throw new NotImplementedException(); }
}
명시적으로 가상이 아닌 정적 멤버
가상이 아닌 인스턴스 멤버를 사용하는 대칭의 경우 기본적으로 가상이 아닌 경우에도 정적 멤버(필드 제외)는 선택적 sealed
한정자를 허용해야 합니다.
interface I0
{
static sealed void M() => Console.WriteLine("Default behavior");
static sealed int f = 0;
static sealed int P1 { get; set; }
static sealed int P2 { get => f; set => f = value; }
static sealed event Action E1;
static sealed event Action E2 { add => E1 += value; remove => E1 -= value; }
static sealed I0 operator +(I0 l, I0 r) => l;
}
인터페이스 멤버 구현
오늘의 규칙
클래스 및 구조체는 암시적 또는 명시적으로 인터페이스의 추상 인스턴스 멤버를 구현할 수 있습니다. 암시적으로 구현된 인터페이스 멤버는 인터페이스 멤버를 구현하기 위해 "발생"하는 클래스 또는 구조체의 일반(가상 또는 가상이 아닌) 멤버 선언입니다. 멤버는 기본 클래스에서 상속될 수도 있으므로 클래스 선언에도 없을 수 있습니다.
명시적으로 구현된 인터페이스 멤버는 정규화된 이름을 사용하여 해당 인터페이스 멤버를 식별합니다. 구현은 클래스 또는 구조체에서 멤버로 직접 액세스할 수 있는 것이 아니라 인터페이스를 통해서만 액세스할 수 있습니다.
제안
정적 추상 인터페이스 멤버의 암시적 구현을 용이하게 하기 위해 클래스 및 구조체에는 새 구문이 필요하지 않습니다. 기존 정적 멤버 선언은 이 목적을 수행합니다.
정적 추상 인터페이스 멤버의 명시적 구현은 static
한정자와 함께 정규화된 이름을 사용합니다.
class C : I<C>
{
string _s;
public C(string s) => _s = s;
static void I<C>.M() => Console.WriteLine("Implementation");
static C I<C>.P { get; set; }
static event Action I<C>.E // event declaration must use field accessor syntax
{
add { ... }
remove { ... }
}
static C I<C>.operator +(C l, C r) => new C($"{l._s} {r._s}");
static bool I<C>.operator ==(C l, C r) => l._s == r._s;
static bool I<C>.operator !=(C l, C r) => l._s != r._s;
static implicit I<C>.operator C(string s) => new C(s);
static explicit I<C>.operator string(C c) => c._s;
}
의미론
연산자 제한 사항
현재 모든 단항 및 이진 연산자 선언에는 피연산자 중 하나 이상이 T
또는 T?
형식이어야 하는 몇 가지 요구 사항이 있으며, 여기서 T
는 이 형식을 둘러싸고 있는 외부 형식의 인스턴스 형식입니다.
이러한 요구 사항을 완화하여 제한된 피연산자가 "바깥쪽 형식의 인스턴스 형식"으로 간주되는 형식 매개 변수가 될 수 있도록 해야 합니다.
형식 매개 변수 T
가 "포함하는 형식의 인스턴스 형식"으로 간주되려면 다음 요구 사항을 충족해야 합니다.
-
T
연산자 선언이 발생하는 인터페이스의 직접 형식 매개 변수입니다. -
T
은 사양에서 "인스턴스 형식"이라고 부르는 것에 의해, 즉 형식 인수로 사용되는 자체 형식 매개 변수가 있는 주변 인터페이스에 의해 직접 제한됩니다.
같음 연산자 및 변환
인터페이스에서는 ==
및 !=
연산자의 추상/가상 선언과 암시적 및 명시적 변환 연산자의 추상/가상 선언이 허용됩니다. 파생 인터페이스도 그것들을 구현할 수 있습니다.
==
및 !=
연산자의 경우, 이전 섹션에서 정의한 대로 적어도 하나의 매개 변수 형식이 "바깥쪽 형식의 인스턴스 형식"으로 간주되는 형식 매개 변수여야 합니다.
정적 추상 멤버를 구현하기
클래스 또는 구조체의 정적 멤버 선언이 정적 추상 인터페이스 멤버를 구현하는 것으로 간주되는 경우와 정적 추상 인터페이스 멤버를 구현할 때 적용되는 요구 사항에 대한 규칙은 인스턴스 멤버와 동일합니다.
TBD: 아직 생각하지 않은 추가 또는 다른 규칙이 여기에 필요할 수 있습니다.
형식 인수로서의 인터페이스
https://github.com/dotnet/csharplang/issues/5955에 의해 제기된 문제를 논의하고 인터페이스를 타입 인수(https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md#type-hole-in-static-abstracts)로 사용하는 것에 대한 제한을 추가하기로 결정했습니다. 다음은 https://github.com/dotnet/csharplang/issues/5955 제안하고 LDM에서 승인한 제한 사항입니다.
인터페이스에 가장 구체적인 구현이 없는 정적 추상/가상 멤버를 포함하거나 상속하는 인터페이스는 형식 인수로 사용할 수 없습니다. 모든 정적 추상/가상 멤버에 가장 구체적인 구현이 있는 경우 인터페이스를 형식 인수로 사용할 수 있습니다.
정적 추상 인터페이스 멤버 액세스
M
인터페이스 T
의해 제한되고 T.M
T
액세스 가능한 정적 추상 멤버인 경우 식 I
사용하여 형식 매개 변수 M
I
정적 추상 인터페이스 멤버에 액세스할 수 있습니다.
T M<T>() where T : I<T>
{
T.M();
T t = T.P;
T.E += () => { };
return t + T.P;
}
런타임에 사용되는 실제 멤버 구현은 타입 인자로 제공된 실제 타입에 존재하는 구현입니다.
C c = M<C>(); // The static members of C get called
쿼리 식이 구문 변화로 정의되므로, C#에서는 사용하는 쿼리 연산자의 정적 멤버가 있는 한 형식을 쿼리 소스로 실제로 사용할 수 있습니다. 즉, 구문이 맞으면 허용합니다. 이 동작은 원래 LINQ에서 의도적이거나 중요하지 않다고 생각하며 형식 매개 변수에서 이를 지원하기 위해 작업을 수행하고 싶지 않습니다. 시나리오가 있는 경우 이에 대해 듣고 나중에 이를 적용하도록 선택할 수 있습니다.
분산 안전 §18.2.3.2
분산 안전 규칙은 정적 추상 멤버의 서명에 적용되어야 합니다. https://github.com/dotnet/csharplang/blob/main/proposals/variance-safety-for-static-interface-members.md#variance-safety에 제안된 추가는 조정해야 합니다.
이러한 제한은 정적 멤버 선언 내의 형식 발생에는 적용되지 않습니다.
-에
이러한 제한은 비추상 정적 멤버의 선언 내에 있는 형식의 발생에는 적용되지 않습니다.
§10.5.4 사용자 정의 암시적 변환
다음 항목들
-
S
,S₀
및T₀
형식을 확인합니다.-
E
에 형식이 있을 경우, 그 형식을S
로 지정하십시오. -
S
또는T
이 nullable 값 형식인 경우,Sᵢ
와Tᵢ
는 각각 해당 기본 형식이 되고, 그렇지 않으면Sᵢ
와Tᵢ
는 각각S
및T
이 됩니다. -
Sᵢ
또는Tᵢ
이 형식 매개변수인 경우S₀
와T₀
을 각각 유효 기본 클래스로 하고, 그렇지 않으면S₀
와T₀
를 각각Sₓ
과Tᵢ
로 합니다.
-
- 사용자 정의 변환 연산자가 고려될 형식 집합(
D
)을 찾습니다. 이 집합은S0
(S0
클래스 또는 구조체인 경우),S0
기본 클래스(S0
클래스인 경우) 및T0
(T0
클래스 또는 구조체인 경우)로 구성됩니다. - 적용 가능한 사용자 정의 변환 연산자 및 리프팅된 변환 연산자의 집합을 찾으십시오,
U
. 이 집합은D
을 포함하는 형식에서S
에 의해 포함되는 형식으로 변환하는,T
클래스 또는 구조체에 의해 선언된 사용자 정의 및 리프팅된 암시적 변환 연산자로 구성됩니다.U
비어 있으면 변환이 정의되지 않고 컴파일 시간 오류가 발생합니다.
는 다음과 같이 조정됩니다.
-
S
,S₀
및T₀
형식을 확인합니다.-
E
에 형식이 있을 경우, 그 형식을S
로 지정하십시오. -
S
또는T
이 nullable 값 형식인 경우,Sᵢ
와Tᵢ
는 각각 해당 기본 형식이 되고, 그렇지 않으면Sᵢ
와Tᵢ
는 각각S
및T
이 됩니다. -
Sᵢ
또는Tᵢ
이 형식 매개변수인 경우S₀
와T₀
을 각각 유효 기본 클래스로 하고, 그렇지 않으면S₀
와T₀
를 각각Sₓ
과Tᵢ
로 합니다.
-
- 적용 가능한 사용자 정의 변환 연산자 및 리프팅된 변환 연산자의 집합을 찾으십시오,
U
.- 사용자 정의 변환 연산자가 고려될 형식 집합(
D1
)을 찾습니다. 이 집합은S0
(S0
클래스 또는 구조체인 경우),S0
기본 클래스(S0
클래스인 경우) 및T0
(T0
클래스 또는 구조체인 경우)로 구성됩니다. - 적용 가능한 사용자 정의 변환 연산자 및 리프팅된 변환 연산자의 집합을 찾으십시오,
U1
. 이 집합은D1
을 포함하는 형식에서S
에 의해 포함되는 형식으로 변환하는,T
클래스 또는 구조체에 의해 선언된 사용자 정의 및 리프팅된 암시적 변환 연산자로 구성됩니다. -
U1
가 비어 있지 않으면,U
은U1
입니다. 그렇지 않으면- 사용자 정의 변환 연산자가 고려될 형식 집합(
D2
)을 찾습니다. 이 집합은Sᵢ
유효 인터페이스 집합 및 해당 기본 인터페이스(Sᵢ
형식 매개 변수인 경우) 및Tᵢ
유효 인터페이스 집합(Tᵢ
형식 매개 변수인 경우)로 구성됩니다. - 적용 가능한 사용자 정의 변환 연산자 및 리프팅된 변환 연산자의 집합을 찾으십시오,
U2
. 이 집합은D2
을 포함하는 형식에서S
에 포함된 형식으로 변환하는 사용자 정의 및 리프트된 암시적 변환 연산자를T
인터페이스에서 선언하여 구성됩니다. -
U2
이 비어 있지 않다면,U
은U2
입니다.
- 사용자 정의 변환 연산자가 고려될 형식 집합(
- 사용자 정의 변환 연산자가 고려될 형식 집합(
-
U
비어 있으면 변환이 정의되지 않고 컴파일 시간 오류가 발생합니다.
§10.3.9 사용자 정의 명시적 변환
다음 항목들
-
S
,S₀
및T₀
형식을 확인합니다.-
E
에 형식이 있을 경우, 그 형식을S
로 지정하십시오. -
S
또는T
이 nullable 값 형식인 경우,Sᵢ
와Tᵢ
는 각각 해당 기본 형식이 되고, 그렇지 않으면Sᵢ
와Tᵢ
는 각각S
및T
이 됩니다. -
Sᵢ
또는Tᵢ
이 형식 매개변수인 경우S₀
와T₀
을 각각 유효 기본 클래스로 하고, 그렇지 않으면S₀
와T₀
를 각각Sᵢ
과Tᵢ
로 합니다.
-
- 사용자 정의 변환 연산자가 고려될 형식 집합(
D
)을 찾습니다. 이 집합은S0
(S0
클래스 또는 구조체인 경우),S0
기본 클래스(S0
클래스인 경우),T0
(T0
클래스 또는 구조체인 경우) 및T0
기본 클래스(T0
클래스인 경우)로 구성됩니다. - 적용 가능한 사용자 정의 변환 연산자 및 리프팅된 변환 연산자의 집합을 찾으십시오,
U
. 이 집합은D
을 포함하거나 포괄하는 형식에서S
를 포함하거나 포괄하는 형식으로 변환하도록T
클래스 또는 구조체에 의해 선언된 사용자 정의 및 리프팅된 암시적 또는 명시적 변환 연산자로 구성됩니다.U
비어 있으면 변환이 정의되지 않고 컴파일 시간 오류가 발생합니다.
는 다음과 같이 조정됩니다.
-
S
,S₀
및T₀
형식을 확인합니다.-
E
에 형식이 있을 경우, 그 형식을S
로 지정하십시오. -
S
또는T
이 nullable 값 형식인 경우,Sᵢ
와Tᵢ
는 각각 해당 기본 형식이 되고, 그렇지 않으면Sᵢ
와Tᵢ
는 각각S
및T
이 됩니다. -
Sᵢ
또는Tᵢ
이 형식 매개변수인 경우S₀
와T₀
을 각각 유효 기본 클래스로 하고, 그렇지 않으면S₀
와T₀
를 각각Sᵢ
과Tᵢ
로 합니다.
-
- 적용 가능한 사용자 정의 변환 연산자 및 리프팅된 변환 연산자의 집합을 찾으십시오,
U
.- 사용자 정의 변환 연산자가 고려될 형식 집합(
D1
)을 찾습니다. 이 집합은S0
(S0
클래스 또는 구조체인 경우),S0
기본 클래스(S0
클래스인 경우),T0
(T0
클래스 또는 구조체인 경우) 및T0
기본 클래스(T0
클래스인 경우)로 구성됩니다. - 적용 가능한 사용자 정의 변환 연산자 및 리프팅된 변환 연산자의 집합을 찾으십시오,
U1
. 이 집합은D1
을 포함하거나 포괄하는 형식에서S
를 포함하거나 포괄하는 형식으로 변환하도록T
클래스 또는 구조체에 의해 선언된 사용자 정의 및 리프팅된 암시적 또는 명시적 변환 연산자로 구성됩니다. -
U1
가 비어 있지 않으면,U
은U1
입니다. 그렇지 않으면- 사용자 정의 변환 연산자가 고려될 형식 집합(
D2
)을 찾습니다. 이 집합은Sᵢ
유효 인터페이스 집합 및 해당 기본 인터페이스(Sᵢ
형식 매개 변수인 경우) 및Tᵢ
유효 인터페이스 집합 및 해당 기본 인터페이스(Tᵢ
형식 매개 변수인 경우)로 구성됩니다. - 적용 가능한 사용자 정의 변환 연산자 및 리프팅된 변환 연산자의 집합을 찾으십시오,
U2
. 이 집합은D2
을 포함하거나 포괄되는 형식에서S
를 포함하거나 포괄되는 형식으로 변환하는T
인터페이스에 의해 선언된 사용자 정의 및 향상된 암시적 또는 명시적 변환 연산자로 구성됩니다. -
U2
이 비어 있지 않다면,U
은U2
입니다.
- 사용자 정의 변환 연산자가 고려될 형식 집합(
- 사용자 정의 변환 연산자가 고려될 형식 집합(
-
U
비어 있으면 변환이 정의되지 않고 컴파일 시간 오류가 발생합니다.
기본 구현
이 제안의 추가 기능은 인스턴스 가상/추상 멤버와 마찬가지로 인터페이스의 정적 가상 멤버가 기본 구현을 가질 수 있도록 하는 것입니다.
여기서 한 가지 복잡성은 기본 구현이 다른 정적 가상 멤버를 "가상"으로 호출하려고 한다는 것입니다. 인터페이스에서 정적 가상 멤버를 직접 호출하도록 허용하려면 현재 정적 메서드가 실제로 호출된 "자체" 형식을 나타내는 숨겨진 형식 매개 변수를 전달해야 합니다. 이것은 복잡하고, 비싸고, 잠재적으로 혼란스러워 보입니다.
정적 가상 멤버가 형식 매개 변수에서 호출될
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md#default-implementations-of-abstract-statics에서는 https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/default-interface-methods.md에 설정된 규칙을 따르고 확장하여 정적 멤버의 기본 구현을 지원하기로 결정했습니다.
패턴 일치
사용자는 다음 코드를 보고 상수 패턴이 인라인으로 작성된 경우와 동일하게 "True"가 출력될 것으로 예상할 수 있습니다.
M(1.0);
static void M<T>(T t) where T : INumberBase<T>
{
Console.WriteLine(t is 1); // Error. Cannot use a numeric constant
Console.WriteLine((t is int i) && (i is 1));
}
패턴의 입력 형식이 double
이 아니므로 상수 1
패턴은 먼저 들어오는 T
를 int
과 비교하여 검사합니다. 이는 직관적이지 않으므로 향후 C# 버전이 INumberBase<T>
파생된 형식에 대한 숫자 일치에 대한 더 나은 처리를 추가할 때까지 차단됩니다. 이를 위해, 우리는 모든 "숫자"가 파생되는 형식으로 INumberBase<T>
을 명시적으로 인정하고, 우리가 패턴을 나타낼 수 없는 숫자 형식에 대해 숫자 상수 패턴을 일치시키려고 할 때(예: INumberBase<T>
에 제한된 형식 매개 변수 또는 INumberBase<T>
를 상속하는 사용자 정의 숫자 형식), 패턴을 차단할 것입니다.
공식적으로 상수 패턴에 대한 패턴 호환 정의에 예외를 추가합니다.
상수 패턴은 상수 값에 대해 식의 값을 테스트합니다. 상수는 리터럴, 선언된
const
변수의 이름, 또는 열거형 상수와 같은 상수 표현식일 수 있습니다. 입력 값이 열린 형식이 아닌 경우 상수 식은 일치하는 식의 형식으로 암시적으로 변환됩니다. 입력 값의 형식이 상수 식의 형식과 패턴 호환않으면 패턴 일치 작업은 오류입니다. 매칭되는 상수 표현식이 숫자 값인 경우, 입력 값이 System.Numerics.INumberBase<T>
에서 상속된 형식이고, 상수 표현식을 입력 값의 형식으로 변환할 수 없는 경우, 패턴 일치 작업이 오류로 간주됩니다.
관계형 패턴에 대해서도 유사한 예외를 추가합니다.
입력이 적절한 기본 제공 이진 관계형 연산자가 정의된 형식인 경우 입력을 왼쪽 피연산자로 적용할 수 있고 지정된 상수가 오른쪽 피연산자로 지정되면 해당 연산자의 평가가 관계형 패턴의 의미로 사용됩니다. 그렇지 않으면 명시적 nullable 또는 unboxing 변환을 사용하여 입력을 식 형식으로 변환합니다. 이러한 변환이 없으면 컴파일 시간 오류입니다. 입력 형식이 제약이 있는 형식 매개 변수에 국한되거나
System.Numerics.INumberBase<T>
을 상속하는 형식이며 입력 형식에 맞는 적절한 기본 제공 이진 관계형 연산자가 정의되지 않은 경우 컴파일 시간 오류입니다. 변환이 실패할 경우 패턴이 일치하지 않는 것으로 간주됩니다. 변환이 성공하면 패턴 일치 작업의 결과는 e가 변환된 입력인 e OP v 식을 계산한 결과이며, OP는 관계형 연산자이고 v는 상수 식입니다.
단점
- "정적 추상"은 새로운 개념이며 C#의 개념적 부담에 의미 있게 추가될 것입니다.
- 값싸게 구축할 수 있는 기능이 아닙니다. 우리는 그만한 가치가 있는지 확인해야 합니다.
대안
구조적 제약 조건
다른 방법은 형식 매개 변수에 특정 연산자의 존재가 명시적으로 요구되는 "구조적 제약 조건"을 직접 갖는 것입니다. 그 단점은 : - 이것은 매번 작성해야합니다. 명명된 제약 조건을 갖는 것이 더 나은 것 같습니다. - 제안된 기능은 인터페이스 제약 조건의 기존 개념을 활용하는 반면, 완전히 새로운 종류의 제약 조건입니다. - 다른 종류의 정적 멤버에는 (쉽지 않게) 적용되지 않고, 연산자에서만 작동합니다.
해결되지 않은 질문
정적 추상 인터페이스 및 정적 클래스
자세한 내용은 https://github.com/dotnet/csharplang/issues/5783 및 https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md#static-abstract-interfaces-and-static-classes 참조하세요.
디자인 회의
- https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-02-08.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-05.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-29.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-06.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-06-06.md
C# feature specifications