Inline-Arrays
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 sind in den entsprechenden Anmerkungen zum Language Design Meeting (LDM) festgehalten.
Mehr über den Prozess der Adaption von Funktionen in den C#-Sprachstandard erfahren Sie in dem Artikel über die Spezifikationen.
Champion Issue: https://github.com/dotnet/csharplang/issues/7431
Zusammenfassung
Bietet einen allgemeinen und sicheren Mechanismus für die Verwendung von Struct-Typen unter Verwendung der Funktion InlineArrayAttribute. Bietet einen allgemeinen und sicheren Mechanismus für die Deklaration von Inline-Arrays in C#-Klassen, Structs und Interfaces.
Hinweis: In früheren Versionen dieser Spezifikation wurden die Begriffe"ref-safe-to-escape" und"safe-to-escape" verwendet, die in der Spezifikation der Funktion Span safety eingeführt wurden. Das ECMA-Standardkomitee änderte die Bezeichnungen in „ref-safe-context" bzw. „safe-context". Die Werte von safe-context wurden dahingehend verfeinert, dass "declaration-block", "function-member" und "caller-context" einheitlich verwendet werden. Die Speclets hatten unterschiedliche Formulierungen für diese Begriffe verwendet und auch"safe-to-return" als Synonym für"caller-context" benutzt. Diese Spezifikation wurde aktualisiert, um die Begriffe im C# 7.3-Standard zu verwenden.
Motivation
Dieser Vorschlag plant, die vielen Beschränkungen von https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers anzugehen. Insbesondere soll es die Möglichkeit bieten:
- Zugriff auf Elemente von Struct-Typen mit Hilfe der Funktion InlineArrayAttribute;
- die Deklaration von Inline-Arrays für verwaltete und nicht verwaltete Typen in einem
struct
,class
oderinterface
.
Und bieten eine Überprüfung der Sprachsicherheit für diese Typen.
Detaillierter Entwurf
Vor kurzem hat Runtime die Funktion InlineArrayAttribute hinzugefügt. Kurz gesagt, ein Benutzer kann einen Strukturtyp wie den folgenden deklarieren:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private object _element0;
}
Runtime bietet ein spezielles Typ-Layout für den Typ Buffer
:
- Die Größe des Typs wird erweitert, um 10 Elemente (die Zahl stammt aus dem InlineArray-Attribut) des Typs
object
aufzunehmen (der Typ ergibt sich aus dem Typ des einzigen Instanzfelds in der Struktur, in diesem Beispiel_element0
). - Das erste Element wird am Instanzfeld und am Anfang der Struct ausgerichtet.
- Die Elemente werden nacheinander im In-Memory angeordnet, als ob sie Elemente eines Arrays wären.
Runtime bietet ein reguläres GC-Tracking für alle Elemente in der Struct.
Dieser Vorschlag wird solche Typen als "Inline-Array-Typen" bezeichnen.
Auf Elemente eines Inline-Array-Typs kann über Zeiger oder über Span-Instanzen zugegriffen werden, die von System.Runtime.InteropServices.MemoryMarshal.CreateSpan/System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan-APIs zurückgegeben werden. Allerdings bieten weder der Zeiger-Ansatz noch die APIs von Haus aus eine Typ- und Bereichsüberprüfung.
Die Sprache wird einen type-safe/ref-safe Weg für den Zugriff auf die Elemente von Inline-Array-Typen bieten. Der Zugriff erfolgt Span-basiert. Dies beschränkt die Unterstützung auf Inline-Array-Typen mit Elementtypen, die als Typargument verwendet werden können. Ein Zeigertyp kann zum Beispiel nicht als Elementtyp verwendet werden. Andere Beispiele die Span-Typen.
Abrufen von Instanzen von Span-Typen für einen Inline-Array-Typ
Da es eine Garantie dafür gibt, dass das erste Element in einem Inline-Array-Typ am Anfang des Typs ausgerichtet ist (keine Lücke), verwendet der Compiler den folgenden Code, um einen Span
-Wert zu erhalten:
MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);
Und den folgenden Code, um einen ReadOnlySpan
Wert zu erhalten:
MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);
Um die IL-Größe an den Verwendungsstellen zu reduzieren, sollte der Compiler in der Lage sein, zwei generische wiederverwendbare Hilfsfunktionen zum Detailtyp der privaten Implementierung hinzuzufügen und sie für alle Verwendungsstellen im selben Programm zu verwenden.
public static System.Span<TElement> InlineArrayAsSpan<TBuffer, TElement>(ref TBuffer buffer, int size) where TBuffer : struct
{
return MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);
}
public static System.ReadOnlySpan<TElement> InlineArrayAsReadOnlySpan<TBuffer, TElement>(in TBuffer buffer, int size) where TBuffer : struct
{
return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);
}
Elementzugriff
Der Elementzugriff wird erweitert, um die Unterstützung von Inline-Array-Elementzugriffen zu ermöglichen.
Ein Elementzugriff besteht aus einem primary_no_array_creation_expression, gefolgt von einem"[
"-Token, gefolgt von einer argument_list, gefolgt von einem"]
"-Token. Die argument_list besteht aus einem oder mehreren Argumenten, die durch Kommas getrennt sind.
element_access
: primary_no_array_creation_expression '[' argument_list ']'
;
Die argument_list eines element_access darf keine ref
- oder out
-Argumente enthalten.
Ein element_access ist dynamisch gebunden (§11.3.3), wenn mindestens eine der folgenden Bedingungen erfüllt ist:
- Der primary_no_array_creation_expression hat den Compile-Time-Typ
dynamic
. - Mindestens ein Ausdruck der argument_list hat den Compile-Time-Typ
dynamic
und der primary_no_array_creation_expression hat keinen Array-Typ, und der primary_no_array_creation_expression hat keinen Inline-Array-Typ oder es gibt mehr als ein Element in der Argumentliste.
In diesem Fall klassifiziert der Compiler den element_access als einen Wert vom Typ dynamic
. Die folgenden Regeln zur Bestimmung der Bedeutung von element_access werden dann zur Laufzeit angewendet, wobei der Laufzeittyp anstelle des Compile-Time-Typs der Ausdrücke primary_no_array_creation_expression und argument_list verwendet wird, die den Compile-Time-Typ dynamic
haben. Wenn der primary_no_array_creation_expression nicht den Compile-Time-Typ dynamic
hat, dann wird der Zugriff auf das Element einer begrenzten Compile-Time-Prüfung unterzogen, wie in §11.6.5 beschrieben.
Wenn der primary_no_array_creation_expression eines element_access ein Wert eines array_type ist, ist der element_access ein Array-Zugriff (§12.8.12.2). Wenn der primary_no_array_creation_expression eines element_access eine Variable oder ein Wert eines Inline-Array-Typs ist und die argument_list aus einem einzigen Argument besteht, ist der element_access ein Inline-Array-Elementzugriff. Andernfalls ist der primary_no_array_creation_expression eine Variable oder ein Wert eines Class-, Struct- oder Interface-Typs, der ein oder mehrere Indexer-Mitglieder hat. In diesem Fall ist der element_access ein Indexer-Zugriff (§12.8.12.3).
Inline-Array-Elementzugriff
Für einen Inline-Array-Elementzugriff muss die primary_no_array_creation_expression des element_access eine Variable oder ein Wert eines Inline-Array-Typs sein. Außerdem darf die argument_list eines Inline-Array-Elementzugriffs keine benannten Argumente enthalten. Die argument_list muss einen einzigen Ausdruck enthalten, und der Ausdruck muss
- vom Typ
int
, oder - implizit konvertierbar in
int
, oder - implizit konvertierbar in
System.Index
, oder - implizit konvertierbar in
System.Range
sein.
Wenn der Typ des Ausdrucks int ist
Wenn primary_no_array_creation_expression eine schreibbare Variable ist, ist das Ergebnis der Auswertung eines Zugriffs auf ein Inline-Array-Element eine schreibbare Variable, die dem Aufruf von public ref T this[int index] { get; }
mit diesem Integer-Wert auf einer Instanz von System.Span<T>
entspricht, die von der Methode System.Span<T> InlineArrayAsSpan
auf primary_no_array_creation_expression zurückgegeben wird. Für die Ref-Safety-Analyse ist der ref-safe-context/safe-context des Zugriffs gleichbedeutend mit einem Aufruf einer Methode mit der Signatur static ref T GetItem(ref InlineArrayType array)
.
Die resultierende Variable wird nur dann als beweglich betrachtet, wenn primary_no_array_creation_expression verschiebbar ist.
Wenn primary_no_array_creation_expression eine Readonly-Variable ist, ist das Ergebnis der Auswertung eines Zugriffs auf ein Inline-Array-Element eine Readonly-Variable, die dem Aufruf von public ref readonly T this[int index] { get; }
mit diesem Integer-Wert auf eine Instanz von System.ReadOnlySpan<T>
entspricht, die von der Methode System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
auf primary_no_array_creation_expression zurückgegeben wird. Für die Ref-Safety-Analyse ist der ref-safe-context/safe-context des Zugriffs gleichbedeutend mit einem Aufruf einer Methode mit der Signatur static ref readonly T GetItem(in InlineArrayType array)
.
Die resultierende Variable wird nur dann als beweglich betrachtet, wenn primary_no_array_creation_expression verschiebbar ist.
Wenn primary_no_array_creation_expression ein Wert ist, ist das Ergebnis der Auswertung eines Zugriffs auf ein Inline-Array-Element ein Wert, der dem Aufruf von public ref readonly T this[int index] { get; }
mit diesem Integer-Wert auf eine Instanz von System.ReadOnlySpan<T>
entspricht, die von der Methode System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
auf primary_no_array_creation_expression zurückgegeben wird. Für die Ref-Safety-Analyse ist der ref-safe-context/safe-context des Zugriffs gleichbedeutend mit einem Aufruf einer Methode mit der Signatur static T GetItem(InlineArrayType array)
.
Zum Beispiel:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
}
void M1(Buffer10<int> x)
{
ref int a = ref x[0]; // Ok, equivalent to `ref int a = ref InlineArrayAsSpan<Buffer10<int>, int>(ref x, 10)[0]`
}
void M2(in Buffer10<int> x)
{
ref readonly int a = ref x[0]; // Ok, equivalent to `ref readonly int a = ref InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10)[0]`
ref int b = ref x[0]; // An error, `x` is a readonly variable => `x[0]` is a readonly variable
}
Buffer10<int> GetBuffer() => default;
void M3()
{
int a = GetBuffer()[0]; // Ok, equivalent to `int a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(GetBuffer(), 10)[0]`
ref readonly int b = ref GetBuffer()[0]; // An error, `GetBuffer()[0]` is a value
ref int c = ref GetBuffer()[0]; // An error, `GetBuffer()[0]` is a value
}
Die Indizierung in ein Inline-Array mit einem konstanten Ausdruck außerhalb der deklarierten Inline-Array-Grenzen ist ein Compile-Time-Fehler.
Wenn der Ausdruck implizit in int
umgewandelt werden kann
Der Ausdruck wird in int umgewandelt und dann wird der Zugriff auf das Element wie im Abschnitt Wenn der Ausdruckstyp int ist beschrieben interpretiert.
Wenn der Ausdruck implizit umwandelbar in System.Index
ist
Der Ausdruck wird in System.Index
konvertiert, der dann in einen int-basierten Indexwert transformiert wird, wie unter https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support beschrieben, unter der Annahme, dass die Länge der Sammlung zur Compile-Time bekannt ist und gleich der Anzahl der Elemente im Inline-Array-Typ des primary_no_array_creation_expression ist. Dann wird der Zugriff auf die Elemente wie im Abschnitt Wenn der Typ des Ausdrucks int ist beschrieben interpretiert.
Wenn der Ausdruck implizit umwandelbar in System.Range
ist
Wenn primary_no_array_creation_expression eine schreibbare Variable ist, ist das Ergebnis der Auswertung eines Inline-Array-Elementzugriffs ein Wert, der dem Aufruf von public Span<T> Slice (int start, int length)
auf eine Instanz von System.Span<T>
entspricht, die von der Methode System.Span<T> InlineArrayAsSpan
auf primary_no_array_creation_expression zurückgegeben wird.
Für die Ref-Safety-Analyse ist der ref-safe-context/safe-context des Zugriffs gleichbedeutend mit einem Aufruf einer Methode mit der Signatur static System.Span<T> GetSlice(ref InlineArrayType array)
.
Wenn primary_no_array_creation_expression eine Readonly-Variable ist, ist das Ergebnis der Auswertung eines Inline-Array-Elementzugriffs ein Wert, der dem Aufruf von public ReadOnlySpan<T> Slice (int start, int length)
auf eine Instanz von System.ReadOnlySpan<T>
entspricht, die von der Methode System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
auf primary_no_array_creation_expression zurückgegeben wird.
Für die Ref-Safety-Analyse ist der ref-safe-context/safe-context des Zugriffs gleichbedeutend mit einem Aufruf einer Methode mit der Signatur static System.ReadOnlySpan<T> GetSlice(in InlineArrayType array)
.
Wenn primary_no_array_creation_expression ein Wert ist, wird ein Fehler geliefert.
Die Argumente für den Slice
-Methodenaufruf werden aus dem nach System.Range
konvertierten Indexausdruck berechnet, wie unter https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support beschrieben, unter der Annahme, dass die Länge der Sammlung zur Compile-Time bekannt ist und der Anzahl der Elemente im Inline-Array-Typ des primary_no_array_creation_expression entspricht.
Der Compiler kann den Slice
-Aufruf weglassen, wenn bekannt ist, dass start
zur Kompilierungszeit 0 ist und length
kleiner oder gleich der Anzahl der Elemente im Inline-Array-Typ ist. Der Compiler kann auch einen Fehler liefern, wenn zur Compile-Time bekannt ist, dass das Slicing die Grenzen des Inline-Arrays verlässt.
Zum Beispiel:
void M1(Buffer10<int> x)
{
System.Span<int> a = x[..]; // Ok, equivalent to `System.Span<int> a = InlineArrayAsSpan<Buffer10<int>, int>(ref x, 10).Slice(0, 10)`
}
void M2(in Buffer10<int> x)
{
System.ReadOnlySpan<int> a = x[..]; // Ok, equivalent to `System.ReadOnlySpan<int> a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10).Slice(0, 10)`
System.Span<int> b = x[..]; // An error, System.ReadOnlySpan<int> cannot be converted to System.Span<int>
}
Buffer10<int> GetBuffer() => default;
void M3()
{
_ = GetBuffer()[..]; // An error, `GetBuffer()` is a value
}
Konvertierungen
Es wird eine neue Konvertierung, eine Inline-Array-Konvertierung, von Ausdruck hinzugefügt. Die Inline-Array-Konvertierung ist eine Standardkonvertierung.
Es gibt eine implizite Konvertierung von Ausdrücken eines Inline-Array-Typs in die folgenden Typen:
System.Span<T>
System.ReadOnlySpan<T>
Die Konvertierung einer Readonly-Variable nach System.Span<T>
oder die Konvertierung eines Wertes in einen der beiden Typen ist jedoch ein Fehler.
Zum Beispiel:
void M1(Buffer10<int> x)
{
System.ReadOnlySpan<int> a = x; // Ok, equivalent to `System.ReadOnlySpan<int> a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10)`
System.Span<int> b = x; // Ok, equivalent to `System.Span<int> b = InlineArrayAsSpan<Buffer10<int>, int>(ref x, 10)`
}
void M2(in Buffer10<int> x)
{
System.ReadOnlySpan<int> a = x; // Ok, equivalent to `System.ReadOnlySpan<int> a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10)`
System.Span<int> b = x; // An error, readonly mismatch
}
Buffer10<int> GetBuffer() => default;
void M3()
{
System.ReadOnlySpan<int> a = GetBuffer(); // An error, ref-safety
System.Span<int> b = GetBuffer(); // An error, ref-safety
}
Zum Zweck der Ref-Safety-Analyse ist der safe-context der Konvertierung äquivalent zu safe-context für einen Aufruf einer Methode mit der Signatur static System.Span<T> Convert(ref InlineArrayType array)
, oder static System.ReadOnlySpan<T> Convert(in InlineArrayType array)
.
Listenmuster
Listenmuster werden für Instanzen von Inline-Array-Typen nicht unterstützt.
Definitive Zuweisungsprüfung
Reguläre definitive Zuweisungsregeln sind auf Variablen anwendbar, die einen Inline-Array-Typ haben.
Sammlung von Literalen
Eine Instanz eines Inline-Array-Typs ist ein gültiger Ausdruck in einem spread_element.
Die folgende Funktion wurde in C# 12 nicht ausgeliefert. Sie bleibt ein offener Vorschlag. Der Code in diesem Beispiel generiert CS9174:
Ein Inline-Array-Typ ist ein gültiger konstruierbarer Sammlung-Zieltyp für einen Sammlungsausdruck. Zum Beispiel:
Buffer10<int> b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // initializes user-defined inline array
Die Länge des Sammlungsliterales muss mit der Länge des Ziel-Inline-Array-Typs übereinstimmen. Ist die Länge des Literals zur Compile-Time bekannt und stimmt sie nicht mit der Ziellänge überein, wird ein Fehler geliefert. Andernfalls wird zur Laufzeit eine Ausnahme ausgelöst, sobald die Abweichung festgestellt wird. Der genaue Ausnahmetyp ist noch zu bestimmen. Einige Kandidaten sind: System.NotSupportedException, System.InvalidOperationException.
Validierung der InlineArrayAttribute-Anwendungen
Der Compiler wird die folgenden Aspekte der InlineArrayAttribute-Anwendungen überprüfen:
- Der Zieltyp ist ein non-record-Struct
- Der Zieltyp hat nur ein Feld
- Angegebene Länge > 0
- Das Ziel-Struct hat kein explizites Layout angegeben
Inline-Array-Elemente in einem Objekt-Initialisierer
Standardmäßig wird die Initialisierung von Elementen über initializer_target der Form '[' argument_list ']'
nicht unterstützt (siehe https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128173-object-initializers):
static C M2() => new C() { F = {[0] = 111} }; // error CS1913: Member '[0]' cannot be initialized. It is not a field or property.
class C
{
public Buffer10<int> F;
}
Wenn der Inlinearraytyp jedoch explizit einen geeigneten Indexer definiert, wird der Objektinitialisierer ihn verwenden.
static C M2() => new C() { F = {[0] = 111} }; // Ok, indexer is invoked
class C
{
public Buffer10<int> F;
}
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
public T this[int i]
{
get => this[i];
set => this[i] = value;
}
}
Die foreach-Anweisung
Die foreach-Anweisung wird angepasst, um die Verwendung eines Inline-Array-Typs als Sammlung in einer foreach-Anweisung zuzulassen.
Zum Beispiel:
foreach (var a in getBufferAsValue())
{
WriteLine(a);
}
foreach (var b in getBufferAsWritableVariable())
{
WriteLine(b);
}
foreach (var c in getBufferAsReadonlyVariable())
{
WriteLine(c);
}
Buffer10<int> getBufferAsValue() => default;
ref Buffer10<int> getBufferAsWritableVariable() => default;
ref readonly Buffer10<int> getBufferAsReadonlyVariable() => default;
entspricht:
Buffer10<int> temp = getBufferAsValue();
foreach (var a in (System.ReadOnlySpan<int>)temp)
{
WriteLine(a);
}
foreach (var b in (System.Span<int>)getBufferAsWritableVariable())
{
WriteLine(b);
}
foreach (var c in (System.ReadOnlySpan<int>)getBufferAsReadonlyVariable())
{
WriteLine(c);
}
Wir werden foreach
über Inline-Arrays unterstützen, auch wenn es in async
-Methoden aufgrund der Einbeziehung der Span-Typen in die Übersetzung als eingeschränkt beginnt.
Offene Fragen zum Design
Alternativen
Syntax für Inline-Arrays
Die Grammatik bei https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general wird wie folgt angepasst:
array_type
: non_array_type rank_specifier+
;
rank_specifier
: '[' ','* ']'
+ | '[' constant_expression ']'
;
Der Typ des constant_expression muss implizit in den Typ int
konvertierbar sein, und der Wert muss eine positive ganze Zahl ungleich Null sein.
Das entsprechende Element des https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#1721-general-Abschnitts wird wie folgt angepasst.
Die Grammatikproduktionen für Array-Typen finden Sie in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general.
Ein Array-Typ wird als non_array_type geschrieben, gefolgt von einem oder mehreren rank_specifiers.
Ein non_array_type ist jeder Typ, der nicht selbst ein array_type ist.
Der Rang eines Array-Typs wird durch den ganz links stehenden rank_specifier im array_type angegeben: Ein rank_specifier zeigt an, dass das Array ein Array mit dem Rang eins plus der Anzahl der",
" Token im rank_specifier ist.
Der Elementtyp eines Array-Typs ist der Typ, der sich aus der Löschung des ganz linken rank_specifier ergibt:
- Ein Array-Typ der Form
T[ constant_expression ]
ist ein anonymer Inline-Array-Typ mit einer Länge, die durch constant_expression angegeben wird, und einem non-array-ElementtypT
. - Ein Array-Typ der Form
T[ constant_expression ][R₁]...[Rₓ]
ist ein anonymer Inline-Array-Typ mit der Länge constant_expression und einem ElementtypT[R₁]...[Rₓ]
. - Ein Array-Typ der Form
T[R]
(wobei R kein constant_expression ist) ist ein regulärer Array-Typ mit RangR
und einem non-array-Element-TypT
. - Ein Array-Typ der Form
T[R][R₁]...[Rₓ]
(wobei R kein constant_expression ist) ist ein regulärer Array-Typ mit dem RangR
und einem ElementtypT[R₁]...[Rₓ]
.
In der Tat werden die rank_specifier von links nach rechts vor dem letzten non-array-Elementtyp gelesen.
Beispiel: Der Typ in
int[][,,][,]
ist ein eindimensionales Array von dreidimensionalen Arrays von zweidimensionalen Arrays vonint
. Ende des Beispiels
Zur Laufzeit kann der Wert eines regulären Arraytyps entweder null
oder ein Verweis auf eine Instanz dieses Arraytyps sein.
Anmerkung: Den Regeln von https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariance folgend, kann der Wert auch eine Referenz auf einen kovarianten Array-Typ sein. Hinweisende
Ein anonymer Inline-Array-Typ ist ein vom Compiler synthetisierter Inline-Array-Typ mit interner Zugreibarkeit. Der Elementtyp muss ein Typ sein, der als Typargument verwendet werden kann. Im Gegensatz zu einem explizit deklarierten Inline-Array-Typ kann auf einen anonymen Inline-Array-Typ nicht anhand des Namens verwiesen werden; er kann nur durch array_type Syntax referenziert werden. Im Kontext desselben Programms verweisen zwei array_type, die Inline-Arrays desselben Elementtyps und derselben Länge bezeichnen, auf denselben anonymen Inline-Array-Typ.
Neben der internen Zugreibarkeit verhindert der Compiler die Verwendung von APIs, die anonyme Inline-Array-Typen über Assembly-Grenzen hinweg verwenden, indem er einen angepassten Modifikator (genauer Typ TBD) auf eine anonyme Inline-Array-Typ-Referenz in der Signatur anwendet.
Ausdrücke zur Array-Erstellung
Ausdrücke zur Array-Erstellung
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
In der aktuellen Grammatik hat die Verwendung einer constant_expression anstelle der expression_list bereits die Bedeutung, einen regulären eindimensionalen Array-Typ der angegebenen Länge zuzuweisen. Daher wird array_creation_expression weiterhin eine Zuweisung eines regulären Arrays darstellen.
Die neue Form des rank_specifier kann jedoch verwendet werden, um einen anonymen Inline-Array-Typ in den Elementtyp des zugewiesenen Arrays einzubinden.
Die folgenden Ausdrücke erstellen z.B. ein reguläres Array der Länge 2 mit einem Elementtyp eines anonymen Inline-Arrays mit dem Elementtyp int und der Länge 5:
new int[2][5];
new int[][5] {default, default};
new [] {default(int[5]), default(int[5])};
Array initializers (Arrayinitialisierer)
Array-Initialisierungen wurden in C# 12 nicht implementiert. Dieser Abschnitt bleibt ein aktiver Vorschlag.
Der Abschnitt Array-Initialisierer wird angepasst, um die Möglichkeit zu bieten, array_initializer zur Initialisierung von Inline-Array-Typen zu verwenden (keine Änderungen an der Grammatik erforderlich).
array_initializer
: '{' variable_initializer_list? '}'
| '{' variable_initializer_list ',' '}'
;
variable_initializer_list
: variable_initializer (',' variable_initializer)*
;
variable_initializer
: expression
| array_initializer
;
Die Länge des Inline-Arrays muss explizit durch den Zieltyp angegeben werden.
Zum Beispiel:
int[5] a = {1, 2, 3, 4, 5}; // initializes anonymous inline array of length 5
Buffer10<int> b = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // initializes user-defined inline array
var c = new int[][] {{11, 12}, {21, 22}, {31, 32}}; // An error for the nested array initializer
var d = new int[][2] {{11, 12}, {21, 22}, {31, 32}}; // An error for the nested array initializer
Detaillierter Entwurf (Option 2)
Beachten Sie, dass für den Zweck dieses Vorschlags der Begriff "Puffer mit fester Größe" auf das vorgeschlagene Feature "sicherer Puffer mit fester Größe" verweist und nicht auf einen Puffer, der bei https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffersbeschrieben wird.
In diesem Design erhalten Puffertypen mit fester Größe keine allgemeine Sonderbehandlung durch die Sprache. Es gibt eine spezielle Syntax für die Deklaration von Elementen, die Puffer mit fester Größe darstellen, und neue Regeln für die Verwendung dieser Elemente. Aus Sicht der Sprache handelt es sich nicht um Felder.
Die Grammatik für variable_declarator in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields wird erweitert, um die Möglichkeit zu bieten, die Größe des Puffers anzugeben:
field_declaration
: attributes? field_modifier* type variable_declarators ';'
;
field_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'readonly'
| 'volatile'
| unsafe_modifier // unsafe code support
;
variable_declarators
: variable_declarator (',' variable_declarator)*
;
variable_declarator
: identifier ('=' variable_initializer)?
+ | fixed_size_buffer_declarator
;
fixed_size_buffer_declarator
: identifier '[' constant_expression ']'
;
Ein fixed_size_buffer_declarator führt einen Puffer mit fester Größe eines bestimmten Elementtyps ein.
Der Typ des Pufferelements ist der Typ, der in field_declaration
angegeben ist. Ein Deklarator für einen Puffer fester Größe führt ein neues Element ein und besteht aus einem Bezeichner, der das Element benennt, gefolgt von einem konstanten Ausdruck, der in [
- und ]
-Token eingeschlossen ist. Der Constant-Ausdruck gibt die Anzahl der Elemente in dem Element an, das durch diesen Pufferdeklarator mit fester Größe eingeführt wird. Der Typ des Constant-Ausdrucks muss implizit in den Typ int
konvertierbar sein und der Wert muss eine positive ganze Zahl ungleich Null sein.
Die Elemente eines Puffers mit fester Größe werden nacheinander im In-Memory angeordnet, als ob sie Elemente eines Arrays wären.
Eine Felddeklaration mit einem Pufferdeklarator fester Größe in einer Schnittstelle muss den Modifikator static
haben.
Je nach Situation (Details werden weiter unten angegeben) wird ein Zugriff auf ein Mitglied des Puffers mit fester Größe entweder als Wert (niemals als Variable) von System.ReadOnlySpan<S>
oder System.Span<S>
klassifiziert, wobei S der Elementtyp des Puffers mit fester Größe ist. Beide Typen bieten Indexer, die einen Verweis auf ein bestimmtes Element mit entsprechender"Readonly-ness" zurückgeben, was eine direkte Zuweisung zu den Elementen verhindert, wenn die Sprachregeln dies nicht zulassen.
Dadurch wird das Set von Typen, die als Elementtyp eines Puffers mit fester Größe verwendet werden können, auf Typen beschränkt, die als Typargumente verwendet werden können. Ein Zeigertyp kann zum Beispiel nicht als Elementtyp verwendet werden.
Die resultierende Span-Instanz hat eine Länge, die der für den Puffer mit fester Größe angegebenen Größe entspricht. Die Indizierung des Spans mit einem Constant-Ausdruck außerhalb der deklarierten Grenzen des Puffers mit fester Größe ist ein Compile-Time-Fehler.
Der safe-context des Wertes entspricht dem safe-context des Containers, so wie es der Fall wäre, wenn der Zugriff auf die Sicherungsdaten als Feld erfolgt.
Puffer mit fester Größe in Ausdrücken
Die Mitgliedssuche eines Puffers mit fester Größe funktionieren genau wie die Mitgliedssuche eines Feldes.
Ein Puffer mit fester Größe kann in einem Ausdruck mit einem einfachen Namen oder einem Mitgliedzugriff referenziert werden.
Wenn ein Instanz-Puffermitglied mit fester Größe als einfacher Name referenziert wird, ist die Wirkung dieselbe wie ein Mitgliedszugriff der Form this.I
, wobei I
das Puffermitglied mit fester Größe ist. Wenn ein statisches Puffermitglied mit fester Größe als einfacher Name referenziert wird, ist die Wirkung die gleiche wie ein Mitgliedszugriff der Form E.I
, wobei I
das Puffermitglied mit fester Größe und E
der deklarierende Typ ist.
Non-readonly-Puffer mit fester Größe
Wenn bei einem Zugriff der Form E.I
E
von einem Struct-Typ ist und eine Mitgliedssuche von I
in diesem Struct-Typ ein Non-readonly-Instanz-Mitglied mit fester Größe identifiziert, dann wird E.I
wie folgt ausgewertet und klassifiziert:
- Wenn
E
als Wert klassifiziert wird, dann kannE.I
nur als primary_no_array_creation_expression eines Elementzugriffs mit einem Index vom TypSystem.Index
oder von einem implizit in int konvertierbaren Typ verwendet werden. Das Ergebnis des Zugriffs auf das Element ist ein Element eines Mitglieds mit fester Größe an der angegebenen Position, das als Wert klassifiziert ist. - Andernfalls, wenn
E
als Readonly-Variable klassifiziert ist und das Ergebnis des Ausdrucks als Wert vom TypSystem.ReadOnlySpan<S>
klassifiziert wird, wobei S der Elementtyp vonI
ist. Der Wert kann verwendet werden, um auf die Elemente des Mitglieds zuzugreifen. - Andernfalls wird
E
als schreibbare Variable klassifiziert und das Ergebnis des Ausdrucks wird als Wert des TypsSystem.Span<S>
klassifiziert, wobei S der Elementtyp vonI
ist. Der Wert kann verwendet werden, um auf die Elemente des Mitglieds zuzugreifen.
In einem Mitgliedszugriff der Form E.I
, wenn E
von einem Klassentyp ist und eine Mitgliedssuche von I
in diesem Klassentyp ein Non-readonly-Instanz-Mitglied mit fester Größe identifiziert, dann wird E.I
ausgewertet und als ein Wert vom Typ System.Span<S>
klassifiziert, wobei S der Elementtyp von I
ist.
Wenn in einem Mitgliedszugriff der Form E.I
die Mitgliedssuche von I
ein non-readonly statisches Element mit fester Größe identifiziert, dann wird E.I
ausgewertet und als ein Wert vom Typ System.Span<S>
klassifiziert, wobei S der Elementtyp von I
ist.
Readonly-Puffer mit fester Größe
Wenn eine Felddeklaration einen readonly
-Modifikator enthält, ist das durch den fixed_size_buffer_declarator eingeführte Element ein Readonly-Puffer mit fester Größe.
Direkte Zuweisungen an Elemente eines Puffers mit fester Größe können nur in einem Instanz-Konstruktor, init-Mitglied oder statischen Konstruktor desselben Typs erfolgen.
Insbesondere sind direkte Zuweisungen an ein Element eines Readonly-Puffer mit fester Größe nur in den folgenden Kontexten erlaubt:
- Für ein Instanzmitglied in den Instanzkonstruktoren oder init member des Typs, der die Memberdeklaration enthält; für ein statisches Mitglied im statischen Konstruktor des Typs, der die Mitgliedsdeklaration enthält. Dies sind auch die einzigen Kontexte, in denen es zulässig ist, ein Element des Readonly-Puffers mit fester Größe als
out
oderref
Parameter zu übergeben.
Der Versuch, einem Element eines Readonly-Puffers mit fester Größe zuzuweisen oder es als out
- oder ref
-Parameter in einem anderen Kontext zu übergeben, ist ein Compile-Time-Fehler.
Dies wird wie folgt erreicht.
Ein Mitgliedszugriff für einen Readonly-Puffer mit fester Größe wird wie folgt bewertet und klassifiziert:
- In einem Mitgliedszugriff der Form
E.I
, wennE
von einem Struct-Typ ist undE
als Wert klassifiziert ist, dann kannE.I
nur als primary_no_array_creation_expression eines Elementzugriffs mit Index vom TypSystem.Index
oder von einem implizit in int konvertierbaren Typ verwendet werden. Das Ergebnis des Zugriffs auf das Element ist ein Element mit fester Größe an der angegebenen Position, das als Wert klassifiziert ist. - Wenn der Zugriff in einem Kontext erfolgt, in dem direkte Zuordnungen zu einem Element eines schreibgeschützten Puffers mit fester Größe zulässig sind, wird das Ergebnis des Ausdrucks als Wert vom Typ
System.Span<S>
klassifiziert, wobei S der Elementtyp des Puffers mit fester Größe ist. Der Wert kann verwendet werden, um auf die Elemente des Mitglieds zuzugreifen. - Andernfalls wird der Ausdruck als ein Wert des Typs
System.ReadOnlySpan<S>
klassifiziert, wobei S der Elementtyp des Puffers mit fester Größe ist. Der Wert kann verwendet werden, um auf die Elemente des Mitglieds zuzugreifen.
Definitive Zuweisungsprüfung
Puffer fester Größe unterliegen nicht der definitiven Zuweisungsprüfung, und Elemente von Puffern fester Größe werden für Zwecke der definitiven Zuweisungsprüfung von Strukturtypvariablen ignoriert.
Wenn ein Pufferelement mit fester Größe statisch ist oder die äußere Strukturvariable, die ein Pufferelement mit fester Größe enthält, eine statische Variable, eine Instanzvariable einer Klasseninstanz oder ein Arrayelement ist, werden die Elemente des Puffers mit fester Größe automatisch mit ihren Standardwerten initialisiert. In allen anderen Fällen ist der anfängliche Inhalt eines Puffers mit fester Größe undefiniert.
Metadaten
Metadaten emittieren und Code generieren
Für die Kodierung von Metadaten verlässt sich der Compiler auf das kürzlich hinzugefügte System.Runtime.CompilerServices.InlineArrayAttribute
.
Puffer mit fester Größe wie der folgende Pseudocode:
// Not valid C#
public partial class C
{
public int buffer1[10];
public readonly int buffer2[10];
}
werden als Felder eines speziell ausgezeichneten Struct-Typs emittiert.
Äquivalenter C# Code wäre dann:
public partial class C
{
public Buffer10<int> buffer1;
public readonly Buffer10<int> buffer2;
}
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
[UnscopedRef]
public System.Span<T> AsSpan()
{
return System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref _element0, 10);
}
[UnscopedRef]
public readonly System.ReadOnlySpan<T> AsReadOnlySpan()
{
return System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan(
ref System.Runtime.CompilerServices.Unsafe.AsRef(in _element0), 10);
}
}
Die tatsächlichen Namenskonventionen für den Typ und seine Mitglieder sind TBD. Das Framework wird wahrscheinlich ein Set von vordefinierten "Puffer"-Typen enthalten, die ein begrenztes Set von Puffergrößen abdecken. Wenn ein vordefinierter Typ nicht existiert, wird der Compiler ihn in dem zu erstellenden Modul synthetisieren. Die Namen der generierten Typen werden "sprechend" sein, um die Verwendung durch andere Sprachen zu unterstützen.
Ein Code, der für einen Zugriff wie:
public partial class C
{
void M1(int val)
{
buffer1[1] = val;
}
int M2()
{
return buffer2[1];
}
}
wird gleichbedeutend sein mit:
public partial class C
{
void M1(int val)
{
buffer.AsSpan()[1] = val;
}
int M2()
{
return buffer2.AsReadOnlySpan()[1];
}
}
Metadata import
Wenn der Compiler eine Felddeklaration vom Typ T importiert und die folgenden Bedingungen erfüllt sind:
- T ist ein Struct-Typ, der mit dem Attribut
InlineArray
ausgezeichnet ist, und - Das erste Instanzfeld, das innerhalb von T deklariert wird, hat den Typ F, und
- Es gibt ein
public System.Span<F> AsSpan()
innerhalb von T, und - Es gibt ein
public readonly System.ReadOnlySpan<T> AsReadOnlySpan()
oderpublic System.ReadOnlySpan<T> AsReadOnlySpan()
innerhalb von T.
wird das Feld als C#-Puffer mit fester Größe und dem Elementtyp F behandelt. Andernfalls wird das Feld wie ein normales Feld vom Typ T behandelt.
Methoden- oder Eigenschaftsgruppen ähnlicher Ansatz in der Sprache
Ein Gedanke ist, diese Mitglieder eher wie Methodengruppen zu behandeln, d.h. sie sind nicht automatisch ein Wert an und für sich, können aber bei Bedarf zu einem gemacht werden. Das würde folgendermaßen funktionieren:
- Sichere Zugriffe auf Puffer mit fester Größe haben eine eigene Klassifizierung (ähnlich wie z. B. Methodengruppen und Lambdas).
- Sie können direkt als Sprachvorgang indiziert werden (nicht über Span-Typen), um eine Variable zu erzeugen (die Readonly-Variable, wenn sich der Puffer in einem Readonly-Kontext befindet, genau wie Felder eines Structs)
- Sie verfügen über implizite Konvertierungen vom Ausdruck in
Span<T>
undReadOnlySpan<T>
, aber die Verwendung des ersteren ist ein Fehler, wenn sie sich in einem Readonly-Kontext befinden. - Ihr natürlicher Typ ist
ReadOnlySpan<T>
, das ist also das, was sie beitragen, wenn sie an der Typinferenz teilnehmen (z. B. var, best-common-type oder generic)
C/C++ Puffer mit fester Größe
C/C++ hat eine andere Vorstellung von Puffern fester Größe. So gibt es zum Beispiel den Begriff eines "Null-Längen-Puffers mit fester Größe", der häufig als Mittel verwendet wird, um darauf hinzuweisen, dass die Daten eine "variable Länge" haben. Es ist kein Ziel dieses Vorschlags, die Interoperabilität mit diesem System zu ermöglichen.
LDM-Treffen
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-03.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-10.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-01.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#inline-arrays
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-17.md#inline-arrays
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#inline-arrays-as-record-structs
C# feature specifications