Yerel boyutlu tamsayılar
Not
Bu makale bir özellik belirtimidir. Belirtim, özelliğin tasarım belgesi olarak görev alır. Önerilen belirtim değişikliklerini ve özelliğin tasarımı ve geliştirilmesi sırasında gereken bilgileri içerir. Bu makaleler, önerilen belirtim değişiklikleri son haline getirilene ve geçerli ECMA belirtimine dahil edilene kadar yayımlanır.
Özellik belirtimi ile tamamlanan uygulama arasında bazı tutarsızlıklar olabilir. Bu farklılıklar, ilgili dil tasarım toplantısı (LDM) notlarındakayıt altına alınmıştır.
Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek için
Özet
Yerel boyutlu imzalı ve imzasız tamsayı türleri için dil desteği.
Motivasyon birlikte çalışma senaryoları ve düşük düzeyli kitaplıklar içindir.
Tasarım
nint
ve nuint
tanımlayıcıları, yerel imzalı ve imzasız tamsayı türlerini temsil eden yeni bağlamsal anahtar sözcüklerdir.
Tanımlayıcılar yalnızca ad araması bu program konumunda uygun bir sonuç bulamadıklarında anahtar sözcük olarak değerlendirilir.
nint x = 3;
_ = nint.Equals(x, 3);
nint
ve nuint
türleri, derleyicinin bu türler için ek dönüştürmeler ve işlemler sağlayarak onları yerel tamsayılar olarak ön plana çıkardığı System.IntPtr
ve System.UIntPtr
temel türleri tarafından temsil edilir.
Sabit
Sabit ifadeler nint
veya nuint
türünde olabilir.
Yerel int değişmez değerleri için doğrudan söz dizimi yoktur. Bunun yerine diğer tamsayı sabit değerlerinin örtük veya açık atamaları kullanılabilir: const nint i = (nint)42;
.
nint
sabitleri [ int.MinValue
, int.MaxValue
] aralığındadır.
nuint
sabitleri [ uint.MinValue
, uint.MaxValue
] aralığındadır.
nint
veya nuint
MinValue
veya MaxValue
alanı yoktur çünkü nuint.MinValue
dışında bu değerler sabit olarak yayılamaz.
Sabit katlama, tüm tekli işleçler { +
, -
, ~
} ve ikili işleçler { +
, -
, *
, /
, %
, ==
, !=
, <
, <=
, >
, >=
, &
, |
, ^
, <<
, >>
} için desteklenir.
Sabit katlama işlemleri, derleyici platformundan bağımsız olarak tutarlı davranış sağlamak amacıyla yerel int türleri yerine Int32
ve UInt32
işlenenleri kullanılarak değerlendirilir.
İşlem 32 bit sabit değerle sonuçlanırsa, derleme zamanında sabit katlama gerçekleştirilir.
Aksi takdirde işlem çalışma zamanında yürütülür ve sabit olarak kabul edilmez.
Dönüşüm
nint
ile IntPtr
arasında ve nuint
ile UIntPtr
arasında bir kimlik dönüştürmesi vardır.
Yerel tamsayılar ve yalnızca temel alınan türlere göre farklılık gösteren bileşik türler arasında bir kimlik dönüşümü vardır: diziler, Nullable<>
, oluşturulan türler ve demetler.
Aşağıdaki tablolar özel türler arasındaki dönüştürmeleri kapsar.
(Her dönüştürme için IL, farklıysa unchecked
ve checked
bağlamları için varyantları içerir.)
Aşağıdaki tabloda genel notlar:
-
conv.u
, yerel tamsayıya sıfır uzatmalı bir dönüşümdür veconv.i
, yerel tamsayıya işaret uzatmalı bir dönüşümdür. - Hem genişletme hem de daraltma için
checked
bağlamları şunlardır:-
signed to *
içinconv.ovf.*
-
unsigned to *
içinconv.ovf.*.un
-
-
genişletme için
unchecked
bağlamlar şunlardır:-
signed to *
içinconv.i*
(burada * hedef genişliktir) -
unsigned to *
içinconv.u*
(burada * hedef genişliktir)
-
-
daraltma için
unchecked
bağlamlar şunlardır:-
any to signed *
içinconv.i*
(burada * hedef genişliktir) -
any to unsigned *
içinconv.u*
(burada * hedef genişliktir)
-
Birkaç örnek alınıyor:
-
sbyte to nint
vesbyte to nuint
conv.i
kullanırken,byte to nint
vebyte to nuint
conv.u
kullanıyor çünkü bunların hepsi genişletme. -
nint to byte
venuint to byte
conv.u1
kullanırkennint to sbyte
venuint to sbyte
conv.i1
kullanır.byte
,sbyte
,short
veushort
için "yığın türü"int32
. Bu nedenleconv.i1
etkin bir şekilde "imzalı bir bayta indirilir ve ardından int32'ye kadar imzalanır",conv.u1
ise etkin bir şekilde "imzasız bir bayta indirilir ve sonra da sıfırdan int32'ye genişletilir". -
checked void* to nint
,conv.ovf.i.un
'i,checked void* to long
'ninconv.ovf.i8.un
'ü kullandığı şekilde kullanır.
Işlenen | Hedef | Dönüşüm | Illinois |
---|---|---|---|
object |
nint |
Kutudan çıkarma | unbox |
void* |
nint |
PointerToVoid | nop / conv.ovf.i.un |
sbyte |
nint |
Gizli Sayısal | conv.i |
byte |
nint |
Örtük Sayısal | conv.u |
short |
nint |
Örtük Sayısal | conv.i |
ushort |
nint |
Dolaylı Sayısal | conv.u |
int |
nint |
Örtük Numerik | conv.i |
uint |
nint |
Açık Sayısal | conv.u / conv.ovf.i.un |
long |
nint |
Açıkça Belirtilmiş Sayısal | conv.i / conv.ovf.i |
ulong |
nint |
Açık Sayısal | conv.i / conv.ovf.i.un |
char |
nint |
Gizli Sayısal | conv.u |
float |
nint |
Açık Sayısal | conv.i / conv.ovf.i |
double |
nint |
Açık Sayısal | conv.i / conv.ovf.i |
decimal |
nint |
Açıkça Belirtilmiş Sayısal | long decimal.op_Explicit(decimal) conv.i / ... conv.ovf.i |
IntPtr |
nint |
Kimlik | |
UIntPtr |
nint |
Hiç kimse | |
object |
nuint |
Kutu Açma | unbox |
void* |
nuint |
PointerToVoid | Nop |
sbyte |
nuint |
Açık Sayısal | conv.i / conv.ovf.u |
byte |
nuint |
Örtük Sayısal Değer | conv.u |
short |
nuint |
Açık Sayısal | conv.i / conv.ovf.u |
ushort |
nuint |
Gizli Sayısal | conv.u |
int |
nuint |
Açık Sayısal Değer | conv.i / conv.ovf.u |
uint |
nuint |
Gizli Sayısal | conv.u |
long |
nuint |
Açık Sayısal | conv.u / conv.ovf.u |
ulong |
nuint |
Açık Sayısal Değer | conv.u / conv.ovf.u.un |
char |
nuint |
Örtük Sayısal | conv.u |
float |
nuint |
Açıkça Belirtilmiş Sayısal | conv.u / conv.ovf.u |
double |
nuint |
Açık Sayısal | conv.u / conv.ovf.u |
decimal |
nuint |
Açık Sayısal | ulong decimal.op_Explicit(decimal) conv.u / ... conv.ovf.u.un |
IntPtr |
nuint |
Hiç kimse | |
UIntPtr |
nuint |
Kimlik | |
Sayım | nint |
Açık Belirtim | |
Numaralandırma | nuint |
Açık Sayım |
İşleç | Hedef | Dönüşüm | Illinois |
---|---|---|---|
nint |
object |
Boks | box |
nint |
void* |
Boş İşaretçi | nop / conv.ovf.u |
nint |
nuint |
Açık Sayısal Veri |
conv.u (atlanabilir) / conv.ovf.u |
nint |
sbyte |
Açıkça Tanımlanmış Sayısal | conv.i1 / conv.ovf.i1 |
nint |
byte |
Açık Sayısal İfade | conv.u1 / conv.ovf.u1 |
nint |
short |
Açık Sayısal | conv.i2 / conv.ovf.i2 |
nint |
ushort |
Açık Sayısal | conv.u2 / conv.ovf.u2 |
nint |
int |
Açık Sayısal | conv.i4 / conv.ovf.i4 |
nint |
uint |
Açık Sayısal | conv.u4 / conv.ovf.u4 |
nint |
long |
Örtük Sayısal Değer | conv.i8 |
nint |
ulong |
Açık Sayısal | conv.i8 / conv.ovf.u8 |
nint |
char |
Açık Sayısal | conv.u2 / conv.ovf.u2 |
nint |
float |
Gizli Sayısal | conv.r4 |
nint |
double |
Örtük Numerik | conv.r8 |
nint |
decimal |
Örtük Sayısal | conv.i8 decimal decimal.op_Implicit(long) |
nint |
IntPtr |
Kimlik | |
nint |
UIntPtr |
Hiç kimse | |
nint |
Sayım | Açık Sayım | |
nuint |
object |
Boks | box |
nuint |
void* |
Boş İşaretçi | Nop |
nuint |
nint |
Açık Sayısal |
conv.i (atlanabilir) / conv.ovf.i.un |
nuint |
sbyte |
Açıkça Belirtilmiş Sayısal | conv.i1 / conv.ovf.i1.un |
nuint |
byte |
Explicit Numeric | conv.u1 / conv.ovf.u1.un |
nuint |
short |
Açıkça Tanımlanmış Sayısal | conv.i2 / conv.ovf.i2.un |
nuint |
ushort |
Açık Sayısal | conv.u2 / conv.ovf.u2.un |
nuint |
int |
Açık Sayısal | conv.i4 / conv.ovf.i4.un |
nuint |
uint |
Açıkça Belirtilmiş Sayısal | conv.u4 / conv.ovf.u4.un |
nuint |
long |
Açık Sayısal | conv.u8 / conv.ovf.i8.un |
nuint |
ulong |
Örtük Sayısal | conv.u8 |
nuint |
char |
Açıkça Tanımlı Sayısal | conv.u2 / conv.ovf.u2.un |
nuint |
float |
Örtük Sayısal | conv.r.un conv.r4 |
nuint |
double |
Gizli Sayısal | conv.r.un conv.r8 |
nuint |
decimal |
Örtük Sayısal | conv.u8 decimal decimal.op_Implicit(ulong) |
nuint |
IntPtr |
Hiç kimse | |
nuint |
UIntPtr |
Kimlik | |
nuint |
Numaralandırma | Açıkça Belirtilen Sıralama |
A
'den Nullable<B>
dönüştürme:
-
A
'danB
'e bir kimlik dönüşümü veya örtük dönüşüm varsa, örtük null atanabilir dönüşüm; -
A
'denB
'e açık bir dönüştürme varsa, açık bir boş değer atanabilir dönüştürme yapılabilir; - aksi takdirde geçersiz.
Nullable<A>
'den B
dönüştürme:
- Kimlik veya örtük ya da açık sayısal dönüşüm varsa,
A
'denB
'e açık null dönüşümü yapın; - aksi takdirde geçersiz.
Nullable<A>
'den Nullable<B>
dönüştürme:
-
A
'denB
'e bir kimlik dönüşümü varsa; -
A
'denB
'ye örtük veya açık bir sayısal dönüşüm varsa, açık şekilde null atanabilir bir dönüştürme yapılabilir. - aksi takdirde geçersiz.
Işleç
Önceden tanımlanmış işleçler aşağıdaki gibidir.
İşlenenlerden en az biri
(Her işlecin IL'i, unchecked
ve farklıysa checked
bağlamları için varyantları içerir.)
Tekli | İşleç İmzası | Illinois |
---|---|---|
+ |
nint operator +(nint value) |
nop |
+ |
nuint operator +(nuint value) |
nop |
- |
nint operator -(nint value) |
neg |
~ |
nint operator ~(nint value) |
not |
~ |
nuint operator ~(nuint value) |
not |
İkili | İşleç İmzası | Illinois |
---|---|---|
+ |
nint operator +(nint left, nint right) |
add / add.ovf |
+ |
nuint operator +(nuint left, nuint right) |
add / add.ovf.un |
- |
nint operator -(nint left, nint right) |
sub / sub.ovf |
- |
nuint operator -(nuint left, nuint right) |
sub / sub.ovf.un |
* |
nint operator *(nint left, nint right) |
mul / mul.ovf |
* |
nuint operator *(nuint left, nuint right) |
mul / mul.ovf.un |
/ |
nint operator /(nint left, nint right) |
div |
/ |
nuint operator /(nuint left, nuint right) |
div.un |
% |
nint operator %(nint left, nint right) |
rem |
% |
nuint operator %(nuint left, nuint right) |
rem.un |
== |
bool operator ==(nint left, nint right) |
beq / ceq |
== |
bool operator ==(nuint left, nuint right) |
beq / ceq |
!= |
bool operator !=(nint left, nint right) |
bne |
!= |
bool operator !=(nuint left, nuint right) |
bne |
< |
bool operator <(nint left, nint right) |
blt / clt |
< |
bool operator <(nuint left, nuint right) |
blt.un / clt.un |
<= |
bool operator <=(nint left, nint right) |
ble |
<= |
bool operator <=(nuint left, nuint right) |
ble.un |
> |
bool operator >(nint left, nint right) |
bgt / cgt |
> |
bool operator >(nuint left, nuint right) |
bgt.un / cgt.un |
>= |
bool operator >=(nint left, nint right) |
bge |
>= |
bool operator >=(nuint left, nuint right) |
bge.un |
& |
nint operator &(nint left, nint right) |
and |
& |
nuint operator &(nuint left, nuint right) |
and |
| |
nint operator |(nint left, nint right) |
or |
| |
nuint operator |(nuint left, nuint right) |
or |
^ |
nint operator ^(nint left, nint right) |
xor |
^ |
nuint operator ^(nuint left, nuint right) |
xor |
<< |
nint operator <<(nint left, int right) |
shl |
<< |
nuint operator <<(nuint left, int right) |
shl |
>> |
nint operator >>(nint left, int right) |
shr |
>> |
nuint operator >>(nuint left, int right) |
shr.un |
Bazı ikili işleçler için IL işleçleri ek işlenen türlerini destekler (bkz. ECMA-335 III.1.5 İşlenen türü tablosu). Ancak C# tarafından desteklenen operand türleri kümesi, basitlik ve dildeki mevcut operatörlerle tutarlılık açısından sınırlıdır.
Bağımsız değişkenlerin ve dönüş türlerinin nint?
ve nuint?
olduğu işleçlerin yükseltilmiş sürümleri desteklenir.
Bileşik atama işlemleri, x
veya y
yerel int'ler olduğu x op= y
önceden tanımlanmış işleçlere sahip diğer temel türlerle aynı kuralları izler.
Özellikle, ifade T
'in x
türünde olduğu ve x
'ün yalnızca bir kez değerlendirildiği durumlarda x = (T)(x op y)
olarak bağlanır.
Kaydırma işleçleri, sizeof(nint)
4 ise kaydırılacak bit sayısını 5 bit olarak, sizeof(nint)
8 ise 6 bit olarak maskelemelidir.
(C# belirtiminde §12.11) bölümüne bakın).
C#9 derleyicisi, önceki bir dil sürümüyle derlenirken önceden tanımlanmış yerel tamsayı işleçlerine bağlama hatalarını bildirir, ancak yerel tamsayılara ve yerel tamsayılardan önceden tanımlanmış dönüştürmelerin kullanılmasına izin verir.
csc -langversion:9 -t:library A.cs
public class A
{
public static nint F;
}
csc -langversion:8 -r:A.dll B.cs
class B : A
{
static void Main()
{
F = F + 1; // error: nint operator+ not available with -langversion:8
F = (System.IntPtr)F + 1; // ok
}
}
İşaretçi aritmetiği
Yerel tamsayı uzaklıklarına sahip işaretçi ekleme veya çıkarma işlemleri için C# dilinde önceden tanımlanmış işleç yoktur.
Bunun yerine, nint
ve nuint
değerleri long
ve ulong
olarak yükseltilir ve işaretçi aritmetiği bu türler için önceden tanımlanmış işleçler kullanır.
static T* AddLeftS(nint x, T* y) => x + y; // T* operator +(long left, T* right)
static T* AddLeftU(nuint x, T* y) => x + y; // T* operator +(ulong left, T* right)
static T* AddRightS(T* x, nint y) => x + y; // T* operator +(T* left, long right)
static T* AddRightU(T* x, nuint y) => x + y; // T* operator +(T* left, ulong right)
static T* SubRightS(T* x, nint y) => x - y; // T* operator -(T* left, long right)
static T* SubRightU(T* x, nuint y) => x - y; // T* operator -(T* left, ulong right)
İkili sayısal yükseltmeler
ikili sayısal yükseltmeleri bilgilendirici metin (bkz. C# belirtimindeki §12.4.7.3) aşağıdaki gibi güncelleştirilir:
- …
- Aksi takdirde, işlenenlerden biri
ulong
türündeyse, diğer işlenenulong
türüne dönüştürülür veya diğer işlenensbyte
,short
,int
,nint
veyalong
türündeyse bağlama zamanı hatası oluşur.- Aksi takdirde, işlenenlerden biri
nuint
türündeyse, diğer işlenennuint
türüne dönüştürülür veya diğer işlenensbyte
,short
,int
,nint
veyalong
türündeyse bağlama zamanı hatası oluşur.- Aksi halde, operantlardan biri
long
türündeyse, diğer operantlong
türüne dönüştürülür.- Aksi takdirde, işlenenlerden biri
uint
türündeyse ve diğer işlenensbyte
,short
,nint
, veyaint
türündeyse, her iki işlenen delong
türüne dönüştürülür.- Aksi takdirde, işlenenlerden biri
uint
türündeyse, diğer işlenenuint
türüne dönüştürülür.- Aksi takdirde, işlenenlerden biri
nint
türündeyse, diğer işlenennint
türüne dönüştürülür.- Aksi takdirde, her iki işlenen de
int
türüne dönüştürülür.
Dinamik
Dönüştürmeler ve işleçler derleyici tarafından sentezlenir ve temel IntPtr
ve UIntPtr
türlerinin bir parçası değildir.
Sonuç olarak bu dönüştürmeler ve işleçler dynamic
için çalışma zamanı bağlayıcısından kullanılamaz .
nint x = 2;
nint y = x + x; // ok
dynamic d = x;
nint z = d + x; // RuntimeBinderException: '+' cannot be applied 'System.IntPtr' and 'System.IntPtr'
Tür üyeleri
nint
veya nuint
için tek oluşturucu parametresiz oluşturucudur.
Aşağıdaki System.IntPtr
ve System.UIntPtr
üyeleri,nint
veya nuint
'ten açıkça dışındadır:
// constructors
// arithmetic operators
// implicit and explicit conversions
public static readonly IntPtr Zero; // use 0 instead
public static int Size { get; } // use sizeof() instead
public static IntPtr Add(IntPtr pointer, int offset);
public static IntPtr Subtract(IntPtr pointer, int offset);
public int ToInt32();
public long ToInt64();
public void* ToPointer();
System.IntPtr
ve System.UIntPtr
'nin kalan üyeleri, nint
ve nuint
'e örtük olarak eklenir. .NET Framework 4.7.2 için:
public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);
System.IntPtr
ve System.UIntPtr
tarafından uygulanan arabirimler, nint
ve nuint
içerisine örtülü olarak dahildir ve temel türlerin kullanımları karşılık gelen yerel tamsayı türleriyle değiştirilir.
Örneğin, IntPtr
ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>
uygularsa nint
ISerializable, IEquatable<nint>, IComparable<nint>
uygular.
Geçersiz kılma, saklama ve uygulama
nint
ve System.IntPtr
ile nuint
ve System.UIntPtr
geçersiz kılma, gizleme ve uygulama için eşdeğer kabul edilir.
Aşırı yüklemeler yalnızca nint
ve System.IntPtr
ile nuint
ve System.UIntPtr
farklılık gösteremez.
Geçersiz kılmalar ve uygulamalar, yalnızca nint
ve System.IntPtr
veya nuint
ve System.UIntPtr
ile sınırlı olarak farklılık gösterebilir.
Yöntemler, yalnızca nint
ve System.IntPtr
veya nuint
ve System.UIntPtr
ile farklılık gösteren diğer yöntemleri gizler.
Çeşitli
dizi dizinleri olarak kullanılan nint
ve nuint
ifadeleri dönüştürme olmadan yayılır.
static object GetItem(object[] array, nint index)
{
return array[index]; // ok
}
nint
ve nuint
, C# dilinden enum
temel türü olarak kullanılamaz.
enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}
Okuma ve yazma işlemleri nint
ve nuint
için atomiktir.
Alanlar nint
ve nuint
türleri için volatile
olarak işaretlenebilir.
ECMA-334 15.5.4, temel tür System.IntPtr
veya System.UIntPtr
olan enum
içermez.
default(nint)
ve new nint()
(nint)0
eşdeğerdir; default(nuint)
ve new nuint()
(nuint)0
eşdeğerdir.
typeof(nint)
, typeof(IntPtr)
'dir; typeof(nuint)
, typeof(UIntPtr)
'tür.
sizeof(nint)
ve sizeof(nuint)
desteklenir, ancak güvenli olmayan bir bağlamda derleme gerektirir (sizeof(IntPtr)
ve sizeof(UIntPtr)
için gerektiği gibi).
Değerler derleme zamanı sabitleri değildir.
sizeof(nint)
IntPtr.Size
yerine sizeof(IntPtr)
olarak uygulanır; sizeof(nuint)
UIntPtr.Size
yerine sizeof(UIntPtr)
olarak uygulanır.
Tür başvurularını içeren derleyici tanılamaları, IntPtr
veya UIntPtr
yerine nint
veya nuint
'e ilişkin olarak nint
veya nuint
'ü raporlar.
Meta veriler
nint
ve nuint
meta verilerde System.IntPtr
ve System.UIntPtr
olarak gösterilir.
nint
veya nuint
içeren tür başvuruları, tür başvurusunun hangi bölümlerinin yerel int olduğunu belirtmek için bir System.Runtime.CompilerServices.NativeIntegerAttribute
ile birlikte gönderilir.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Event |
AttributeTargets.Field |
AttributeTargets.GenericParameter |
AttributeTargets.Parameter |
AttributeTargets.Property |
AttributeTargets.ReturnValue,
AllowMultiple = false,
Inherited = false)]
public sealed class NativeIntegerAttribute : Attribute
{
public NativeIntegerAttribute()
{
TransformFlags = new[] { true };
}
public NativeIntegerAttribute(bool[] flags)
{
TransformFlags = flags;
}
public readonly bool[] TransformFlags;
}
}
NativeIntegerAttribute
ile tür başvurularının kodlaması NativeIntegerAttribute.mdkapsamındadır.
Alternatif
Yukarıdaki "tür silme" yaklaşımının bir alternatifi de yeni türleri tanıtmaktır: System.NativeInt
ve System.NativeUInt
.
public readonly struct NativeInt
{
public IntPtr Value;
}
Ayrı türler, IntPtr
'dan farklı olarak aşırı yüklemeye izin verir ve ayrıca ayrıştırmayı ToString()
'den farklı hale getirebilir.
Ancak CLR'nin bu türleri verimli bir şekilde işlemesi için daha fazla çalışma olacaktır ve bu da özelliğin birincil amacı olan verimliliği yener.
Ayrıca IntPtr
kullanan mevcut yerel int koduyla birlikte çalışmak daha zor olacaktır.
Başka bir alternatif, çerçevedeki IntPtr
için daha yerel int desteği eklemektir ancak belirli bir derleyici desteği olmadan.
Tüm yeni dönüştürmeler ve aritmetik işlemler derleyici tarafından otomatik olarak desteklenir.
Ancak dil anahtar sözcükler, sabitler veya checked
işlemleri sağlamaz.
Tasarım toplantıları
- https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-05-26.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-06-13.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-07-05.md#native-int-and-intptr-operators
- https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-10-23.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-03-25.md
C# feature specifications