Integerwerte mit nativer Größe
Hinweis
Dieser Artikel ist eine Feature-Spezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.
Es kann einige Abweichungen zwischen der Feature-Spezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den entsprechenden Hinweisen zum Language Design Meeting (LDM) erfasst.
Weitere Informationen zum Prozess für die Aufnahme von Funktions-Speclets in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.
Champion Issue: https://github.com/dotnet/csharplang/issues/435
Zusammenfassung
Sprachunterstützung für signierte und nicht signierte ganzzahlige Typen in nativer Größe.
Die Motivation ist für Interop-Szenarien und für Low-Level-Bibliotheken.
Design
Die Bezeichner nint
und nuint
sind neue kontextbezogene Schlüsselwörter, die systemeigene signierte und nicht signierte ganzzahlige Typen darstellen.
Die Bezeichner werden nur als Schlüsselwörter behandelt, wenn die Namenssuche an diesem Programmspeicherort kein brauchbares Ergebnis findet.
nint x = 3;
_ = nint.Equals(x, 3);
Die Typen nint
und nuint
werden durch die zugrunde liegenden Typen System.IntPtr
und System.UIntPtr
dargestellt, wobei der Compiler zusätzliche Konvertierungen und Operationen für diese Typen als native Ints bereitstellt.
Konstanten
Konstantenausdrücke können vom Typ nint
oder nuint
sein.
Es gibt keine direkte Syntax für native Integer-Literale. Implizite oder explizite Umwandlungen anderer integraler Konstantenwerte können stattdessen verwendet werden: const nint i = (nint)42;
.
nint
Konstanten befinden sich im Bereich [ int.MinValue
, int.MaxValue
].
nuint
Konstanten befinden sich im Bereich [ uint.MinValue
, uint.MaxValue
].
Es gibt keine Felder MinValue
oder MaxValue
auf nint
oder nuint
, da diese Werte, mit Ausnahme von nuint.MinValue
, nicht als Konstanten ausgegeben werden können.
Die Konstantenfaltung wird für alle unären Operatoren { +
, -
, ~
} und binären Operatoren { +
, -
, *
, /
, %
, ==
, !=
, <
, <=
, >
, >=
, &
, |
, ^
, <<
, >>
} unterstützt.
Konstantenfaltungsoperationen werden mit Int32
und UInt32
Operanden statt mit nativen Ints ausgewertet, um ein konsistentes Verhalten unabhängig von der Compilerplattform sicherzustellen.
Wenn der Vorgang zu einem Konstantenwert in 32 Bit führt, wird die Konstantenfaltung zur Kompilierungszeit ausgeführt.
Andernfalls wird die Operation zur Laufzeit ausgeführt und nicht als Konstante betrachtet.
Konvertierungen
Es gibt eine Identitätskonvertierung zwischen nint
und IntPtr
und zwischen nuint
und UIntPtr
.
Es gibt eine Identitätsumwandlung zwischen zusammengesetzten Typen, die sich nur durch native Ints und zugrunde liegende Typen unterscheiden: Arrays, Nullable<>
, konstruierte Typen und Tupel.
Die folgenden Tabellen enthalten die Konversionen zwischen speziellen Typen.
(Die IL für jede Konvertierung enthält die Varianten für die Kontexte unchecked
und checked
, falls diese unterschiedlich sind.)
Allgemeine Hinweise zur untenstehenden Tabelle:
conv.u
ist eine Konvertierung mit Nullerweiterung zu einer nativen Ganzzahl, undconv.i
ist eine Konvertierung mit Vorzeichenerweiterung zu einer nativen Ganzzahl.checked
Kontexte für Erweiterungen und Einschränkungen sind:-
conv.ovf.*
fürsigned to *
-
conv.ovf.*.un
fürunsigned to *
-
unchecked
Rahmenbedingungen für Erweiterungen sind:-
conv.i*
fürsigned to *
(wobei * die Zielbreite ist) -
conv.u*
fürunsigned to *
(wobei * die Zielbreite ist)
-
unchecked
Kontexte für die Einschränkungen von sind:-
conv.i*
fürany to signed *
(wobei * die Zielbreite ist) -
conv.u*
fürany to unsigned *
(wobei * die Zielbreite ist)
-
Ein paar Beispiele:
-
sbyte to nint
undsbyte to nuint
verwendenconv.i
, währendbyte to nint
undbyte to nuint
conv.u
verwenden, da sie alle erweitern. -
nint to byte
undnuint to byte
verwendenconv.u1
, währendnint to sbyte
undnuint to sbyte
conv.i1
verwenden. Fürbyte
,sbyte
,short
undushort
ist der „Stapeltyp”int32
. Daher wirdconv.i1
effektiv „herabgestuft zu einem vorzeichenbehafteten Byte und dann zu int32 mit Vorzeichen erweitert“, währendconv.u1
effektiv „herabgestuft zu einem vorzeichenlosen Byte und dann zu int32 mit Nullen erweitert“ ist. checked void* to nint
verwendetconv.ovf.i.un
auf die gleiche Weise wiechecked void* to long
conv.ovf.i8.un
.
Operand | Ziel | Konvertierung | BY |
---|---|---|---|
object |
nint |
Auspacken | unbox |
void* |
nint |
PointerToVoid | nop / conv.ovf.i.un |
sbyte |
nint |
ImplicitNumeric | conv.i |
byte |
nint |
ImplicitNumeric | conv.u |
short |
nint |
ImplicitNumeric | conv.i |
ushort |
nint |
ImplicitNumeric | conv.u |
int |
nint |
ImplicitNumeric | conv.i |
uint |
nint |
ExplicitNumeric | conv.u / conv.ovf.i.un |
long |
nint |
ExplicitNumeric | conv.i / conv.ovf.i |
ulong |
nint |
ExplicitNumeric | conv.i / conv.ovf.i.un |
char |
nint |
ImplicitNumeric | conv.u |
float |
nint |
ExplicitNumeric | conv.i / conv.ovf.i |
double |
nint |
ExplicitNumeric | conv.i / conv.ovf.i |
decimal |
nint |
ExplicitNumeric | long decimal.op_Explicit(decimal) conv.i / ... conv.ovf.i |
IntPtr |
nint |
Identität | |
UIntPtr |
nint |
Keine | |
object |
nuint |
Auspacken | unbox |
void* |
nuint |
PointerToVoid | nop |
sbyte |
nuint |
ExplicitNumeric | conv.i / conv.ovf.u |
byte |
nuint |
ImplicitNumeric | conv.u |
short |
nuint |
ExplicitNumeric | conv.i / conv.ovf.u |
ushort |
nuint |
ImplicitNumeric | conv.u |
int |
nuint |
ExplicitNumeric | conv.i / conv.ovf.u |
uint |
nuint |
ImplicitNumeric | conv.u |
long |
nuint |
ExplicitNumeric | conv.u / conv.ovf.u |
ulong |
nuint |
ExplicitNumeric | conv.u / conv.ovf.u.un |
char |
nuint |
ImplicitNumeric | conv.u |
float |
nuint |
ExplicitNumeric | conv.u / conv.ovf.u |
double |
nuint |
ExplicitNumeric | conv.u / conv.ovf.u |
decimal |
nuint |
ExplicitNumeric | ulong decimal.op_Explicit(decimal) conv.u / ... conv.ovf.u.un |
IntPtr |
nuint |
Keine | |
UIntPtr |
nuint |
Identität | |
Enumeration | nint |
ExplicitEnumeration | |
Enumeration | nuint |
ExplicitEnumeration |
Operand | Ziel | Konvertierung | BY |
---|---|---|---|
nint |
object |
Boxing | box |
nint |
void* |
PointerToVoid | nop / conv.ovf.u |
nint |
nuint |
ExplicitNumeric | conv.u (kann weggelassen werden) / conv.ovf.u |
nint |
sbyte |
ExplicitNumeric | conv.i1 / conv.ovf.i1 |
nint |
byte |
ExplicitNumeric | conv.u1 / conv.ovf.u1 |
nint |
short |
ExplicitNumeric | conv.i2 / conv.ovf.i2 |
nint |
ushort |
ExplicitNumeric | conv.u2 / conv.ovf.u2 |
nint |
int |
ExplicitNumeric | conv.i4 / conv.ovf.i4 |
nint |
uint |
ExplicitNumeric | conv.u4 / conv.ovf.u4 |
nint |
long |
ImplicitNumeric | conv.i8 |
nint |
ulong |
ExplicitNumeric | conv.i8 / conv.ovf.u8 |
nint |
char |
ExplicitNumeric | conv.u2 / conv.ovf.u2 |
nint |
float |
ImplicitNumeric | conv.r4 |
nint |
double |
ImplicitNumeric | conv.r8 |
nint |
decimal |
ImplicitNumeric | conv.i8 decimal decimal.op_Implicit(long) |
nint |
IntPtr |
Identität | |
nint |
UIntPtr |
Keine | |
nint |
Enumeration | ExplicitEnumeration | |
nuint |
object |
Boxing | box |
nuint |
void* |
PointerToVoid | nop |
nuint |
nint |
ExplicitNumeric | conv.i (kann weggelassen werden) / conv.ovf.i.un |
nuint |
sbyte |
ExplicitNumeric | conv.i1 / conv.ovf.i1.un |
nuint |
byte |
ExplicitNumeric | conv.u1 / conv.ovf.u1.un |
nuint |
short |
ExplicitNumeric | conv.i2 / conv.ovf.i2.un |
nuint |
ushort |
ExplicitNumeric | conv.u2 / conv.ovf.u2.un |
nuint |
int |
ExplicitNumeric | conv.i4 / conv.ovf.i4.un |
nuint |
uint |
ExplicitNumeric | conv.u4 / conv.ovf.u4.un |
nuint |
long |
ExplicitNumeric | conv.u8 / conv.ovf.i8.un |
nuint |
ulong |
ImplicitNumeric | conv.u8 |
nuint |
char |
ExplicitNumeric | conv.u2 / conv.ovf.u2.un |
nuint |
float |
ImplicitNumeric | conv.r.un conv.r4 |
nuint |
double |
ImplicitNumeric | conv.r.un conv.r8 |
nuint |
decimal |
ImplicitNumeric | conv.u8 decimal decimal.op_Implicit(ulong) |
nuint |
IntPtr |
Keine | |
nuint |
UIntPtr |
Identität | |
nuint |
Enumeration | ExplicitEnumeration |
Die Umwandlung von A
in Nullable<B>
ist:
- eine implizite nullable Konvertierung, wenn eine Identitätskonvertierung oder implizite Konvertierung von
A
inB
vorhanden ist; - eine explizite nullable Konvertierung, wenn eine explizite Konvertierung von
A
inB
möglich ist; - andernfalls ungültig.
Die Umwandlung von Nullable<A>
in B
ist:
- eine explizite nullable Konvertierung, wenn eine Identitätskonvertierung oder eine implizite oder explizite numerische Konvertierung von
A
nachB
vorhanden ist; - andernfalls ungültig.
Die Umwandlung von Nullable<A>
in Nullable<B>
ist:
- eine Identitätskonvertierung, wenn eine Identitätskonvertierung von
A
inB
erfolgt; - eine explizite nullfähige Konvertierung, wenn es eine implizite oder explizite numerische Konvertierung von
A
nachB
gibt; - andernfalls ungültig.
Operatoren
Die vordefinierten Operatoren sind wie folgt.
Diese Operatoren werden basierend auf den normalen Regeln für implizite Konvertierungen bei der Überladungsauflösung berücksichtigt, wenn mindestens einer der Operanden vom Typ nint
oder nuint
ist.
(Die IL für jeden Operator enthält die Varianten für die Kontexte unchecked
und checked
, falls diese unterschiedlich sind.)
Unär | Signatur des Bedieners | BY |
---|---|---|
+ |
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 |
Binär | Signatur des Bedieners | BY |
---|---|---|
+ |
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 |
Für einige binäre Operatoren unterstützen die IL-Operatoren zusätzliche Operandentypen (siehe ECMA-335 III.1.5 Operandentypentabelle). Das Set der von C# unterstützten Operandentypen ist jedoch der Einfachheit halber und aus Gründen der Konsistenz mit den in der Sprache vorhandenen Operatoren begrenzt.
Angehobene Versionen der Operatoren, bei denen die Argumente und Rückgabetypen nint?
und nuint?
sind, werden unterstützt.
Zusammengesetzte Zuordnungsvorgänge x op= y
, bei denen x
oder y
native Ints sind, folgen den gleichen Regeln wie bei anderen Grundtypen mit vordefinierten Operatoren.
Genauer gesagt wird der Ausdruck als x = (T)(x op y)
gebunden, wobei T
der Typ von x
ist und x
nur einmal ausgewertet wird.
Die Shift-Operatoren sollten die Anzahl der zu verschiebenden Bits maskieren – auf 5 Bit, wenn sizeof(nint)
4 ist, und auf 6 Bit, wenn sizeof(nint)
8 ist.
(siehe §12.11) in der C#-Spezifikation).
Der C#9-Compiler meldet Fehler beim Binden an vordefinierte native Ganzzahloperatoren beim Kompilieren mit einer früheren Sprachversion, ermöglicht jedoch die Verwendung vordefinierter Konvertierungen von und zu nativen Ganzzahlen.
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
}
}
Zeigerarithmetik
In C# gibt es keine vordefinierten Operatoren für das Addieren oder Subtrahieren von Zeigern mit nativen ganzen Zahlen als Offsets.
Stattdessen werden die Werte nint
und nuint
auf long
und ulong
höhergestuft, und für die Zeigerarithmetik werden vordefinierte Operatoren für diese Typen verwendet.
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)
Binäre numerische Höherstufungen
Die binären numerischen Höherstufungen informativer Text (siehe §12.4.7.3) in der C#-Spezifikation wird wie folgt aktualisiert:
- …
- Wenn ein Operand vom Typ
ulong
ist, wird der andere Operand in den Typulong
konvertiert. Andernfalls tritt ein Bindungszeitfehler auf, wenn der andere Operand von Typsbyte
,short
,int
,nint
oderlong
ist.- Andernfalls, wenn ein Operand vom Typ
nuint
ist, wird der andere Operand in den Typnuint
konvertiert, oder es tritt ein Bindungszeitfehler auf, wenn der andere Operand vom Typsbyte
,short
,int
,nint
oderlong
ist.- Andernfalls, wenn ein Operand vom Typ
long
ist, wird der andere Operand in den Typlong
konvertiert.- Andernfalls, wenn einer der beiden Operanden vom Typ
uint
und der andere Operand vom Typsbyte
,short
,nint
, oderint
ist, werden beide Operanden in den Typlong
konvertiert.- Andernfalls, wenn ein Operand vom Typ
uint
ist, wird der andere Operand in den Typuint
konvertiert.- Andernfalls, wenn ein Operand vom Typ
nint
ist, wird der andere Operand in den Typnint
konvertiert.- Andernfalls werden beide Operanden in den Typ
int
konvertiert.
Dynamisch
Die Konversionen und Operatoren werden vom Compiler synthetisiert und sind nicht Element der zugrundeliegenden IntPtr
und UIntPtr
Typen.
Daher sind diese Konvertierungen und Operatoren nicht verfügbar aus dem Laufzeitbinder für dynamic
.
nint x = 2;
nint y = x + x; // ok
dynamic d = x;
nint z = d + x; // RuntimeBinderException: '+' cannot be applied 'System.IntPtr' and 'System.IntPtr'
Typmember
Der einzige Konstruktor für nint
oder nuint
ist der parameterlose Konstruktor.
Die folgenden Members von System.IntPtr
und System.UIntPtr
werden explizit von nint
oder nuint
ausgeschlossen:
// 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();
Die übrigen Members von System.IntPtr
und System.UIntPtr
werden implizit in nint
und nuint
eingeschlossen. Für .NET Framework 4.7.2:
public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);
Schnittstellen, die von System.IntPtr
und System.UIntPtr
implementiert werden, sind implizit in nint
und nuint
enthalten, wobei Vorkommen der zugrunde liegenden Typen durch die entsprechenden systemeigenen ganzzahligen Typen ersetzt werden.
Wenn zum Beispiel IntPtr
ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>
implementiert, dann implementiert nint
ISerializable, IEquatable<nint>, IComparable<nint>
.
Überschreiben, Ausblenden und Implementieren
nint
und System.IntPtr
und nuint
und System.UIntPtr
gelten als gleichwertig für das Überschreiben, Ausblenden und Implementieren.
Überladungen können sich nicht allein durch nint
und System.IntPtr
und nuint
und System.UIntPtr
unterscheiden.
Unterschiede in Überschreibungen und Implementierungen können allein bei nint
und System.IntPtr
oder nuint
und System.UIntPtr
auftreten.
Methoden blenden andere Methoden aus, die sich entweder nur durch nint
und System.IntPtr
oder nur durch nuint
und System.UIntPtr
unterscheiden.
Verschiedenes
nint
- und nuint
-Ausdrücke, die als Arrayindizes verwendet werden, werden ohne Konvertierung ausgegeben.
static object GetItem(object[] array, nint index)
{
return array[index]; // ok
}
nint
und nuint
können nicht als enum
-Basistyp in C# verwendet werden.
enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}
Lese- und Schreibvorgänge sind für nint
und nuint
atomar.
Felder können mit volatile
für die Typen nint
und nuint
markiert werden.
ECMA-334 15.5.4 enthält jedoch keine enum
mit Basistyp System.IntPtr
oder System.UIntPtr
.
default(nint)
und new nint()
sind äquivalent zu (nint)0
; default(nuint)
und new nuint()
sind äquivalent zu (nuint)0
.
typeof(nint)
ist typeof(IntPtr)
; typeof(nuint)
ist typeof(UIntPtr)
.
sizeof(nint)
und sizeof(nuint)
werden unterstützt, müssen aber in einem unsicheren Kontext kompiliert werden (wie für sizeof(IntPtr)
und sizeof(UIntPtr)
erforderlich).
Die Werte sind keine Konstanten zur Kompilierungszeit.
sizeof(nint)
ist als sizeof(IntPtr)
und nicht als IntPtr.Size
implementiert; sizeof(nuint)
ist als sizeof(UIntPtr)
und nicht als UIntPtr.Size
implementiert.
Compilerdiagnosen für Typverweise, die nint
oder nuint
involvieren, berichten nint
oder nuint
statt IntPtr
oder UIntPtr
.
Metadaten
nint
und nuint
werden in den Metadaten als System.IntPtr
und System.UIntPtr
dargestellt.
Typverweise, die nint
oder nuint
enthalten, werden mit einem System.Runtime.CompilerServices.NativeIntegerAttribute
ausgegeben, um anzugeben, welche Teile des Typverweises systemeigene Ints sind.
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;
}
}
Die Codierung von Typbezügen mit NativeIntegerAttribute
wird in NativeIntegerAttribute.md behandelt.
Alternativen
Eine Alternative zum oben genannten Ansatz der „Typ-Löschung” besteht darin, neue Typen einzuführen: System.NativeInt
und System.NativeUInt
.
public readonly struct NativeInt
{
public IntPtr Value;
}
Unterschiedliche Typen würden eine von IntPtr
abweichende Überladung zulassen und ein unterschiedliches Parsen sowie ToString()
ermöglichen.
Aber es wäre mehr Arbeit für die CLR, diese Typen effizient zu behandeln, was den Hauptzweck des Features, die Effizienz, zunichtemachen würde.
Und die Interoperabilität mit vorhandenem nativen Int-Code, der IntPtr
verwendet, wäre schwieriger.
Eine weitere Alternative besteht darin, mehr native int-Unterstützung für IntPtr
in das Framework aufzunehmen, jedoch ohne spezielle Compiler-Unterstützung.
Alle neuen Konversionen und arithmetischen Operationen würden vom Compiler automatisch unterstützt werden.
Die Sprache würde jedoch keine Schlüsselwörter, Konstanten oder checked
-Operationen bereitstellen.
Designbesprechungen
- 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