Udostępnij za pośrednictwem


Tablice wbudowane

Notatka

Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.

Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są przechwytywane w odpowiednich spotkania projektowego języka (LDM).

Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .

Problem z mistrzem: https://github.com/dotnet/csharplang/issues/7431

Streszczenie

Udostępnij mechanizm ogólnego przeznaczenia i zapewniający bezpieczeństwo, pozwalający na korzystanie z typów struktur z wykorzystaniem funkcji InlineArrayAttribute. Zapewnienie uniwersalnego zastosowania i bezpiecznego mechanizmu deklarowania tablic inline w klasach, strukturach i interfejsach języka C#.

Uwaga: Poprzednie wersje tej specyfikacji używały terminów "ref-safe-to-escape" i "safe-to-escape", które zostały wprowadzone w specyfikacji funkcji bezpieczeństwa Span . Komisja Standardowa ECMA zmieniła nazwy odpowiednio "ref-safe-context" i "bezpieczny kontekst". Wartości bezpiecznego kontekstu zostały uściślone w celu spójnego używania "bloku deklaracji", "elementu członkowskiego funkcji" i "kontekstu wywołującego". Speclety stosowały różne sformułowania dla tych terminów, a także używały "safe-to-return" jako synonimu "caller-context". Ten speclet został zaktualizowany, aby używać terminów w standardzie C# 7.3.

Motywacja

Wniosek ten ma na celu przezwyciężenie wielu ograniczeń https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers. W szczególności ma na celu umożliwienie:

  • uzyskiwanie dostępu do elementów typów struktur korzystających z funkcji InlineArrayAttribute;
  • deklaracja tablic wbudowanych dla typów zarządzanych i niezarządzanych w struct, classlub interface.

I zapewnić weryfikację bezpieczeństwa języka dla nich.

Szczegółowy projekt

Ostatnio podczas wykonywania dodano funkcję InlineArrayAttribute. Krótko mówiąc, użytkownik może zadeklarować typ struktury podobny do następującego:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
    private object _element0;
}

Środowisko uruchomieniowe udostępnia specjalny układ typu dla typu Buffer:

  • Rozmiar typu jest zwiększony, aby pomieścić 10 (liczba pochodzi z atrybutu InlineArray) elementów typu object (typ pochodzi z typu jedynego pola instancji w strukturze, _element0 w tym przykładzie).
  • Pierwszy element jest wyrównany z polem instancji oraz z początkiem struktury
  • Elementy są rozmieszczone sekwencyjnie w pamięci, tak jakby były elementami tablicy.

Środowisko uruchomieniowe zapewnia regularne śledzenie GC dla wszystkich elementów w strukturze.

Ta propozycja będzie odnosić się do typów takich jak "jednoliniowe typy tablic".

Dostęp do elementów typu tablicy wbudowanej można uzyskać za pośrednictwem wskaźników lub instancji zakresu zwracanych przez interfejsy API System.Runtime.InteropServices.MemoryMarshal.CreateSpan/System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan. Jednak ani podejście wskaźnikowe, ani interfejsy API nie zapewniają sprawdzania typu i zasięgu domyślnie.

Język zapewni bezpieczny/bezpieczny dla typu sposób uzyskiwania dostępu do elementów wbudowanych typów tablic. Dostęp będzie oparty na rozpiętości. Ogranicza to obsługę wbudowanych typów tablic z typami elementów, których można użyć jako argumentu typu. Na przykład typ wskaźnika nie może być używany jako typ elementu. Inne przykłady typów rozpiętości.

Uzyskiwanie instancji typów span dla wbudowanego typu tablicy

Ponieważ istnieje gwarancja, że pierwszy element w wbudowanym typie tablicy jest wyrównany na początku typu (bez luki), kompilator użyje następującego kodu, aby uzyskać wartość Span.

MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);

Poniższy kod umożliwiający uzyskanie wartości ReadOnlySpan:

MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);

Aby zmniejszyć rozmiar IL w miejscach użycia, kompilator powinien być w stanie dodać dwie ogólne funkcje pomocnicze do prywatnego typu szczegółów implementacji i używać ich we wszystkich miejscach użycia w tym samym programie.

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);
}

Dostęp do elementów

Dostęp do elementu zostanie rozszerzony, aby obsługiwać dostęp do elementów tablicy w wierszu.

element_access składa się z wyrażenia primary_no_array_creation_expression, po którym występuje token „[”, następnie lista argumentów argument_list, a na końcu token „]”. argument_list składa się z co najmniej jednego argumentus oddzielonego przecinkami.

element_access
    : primary_no_array_creation_expression '[' argument_list ']'
    ;

argument_listelement_access nie może zawierać argumentów ref ani out.

element_access jest dynamicznie powiązany (§11.3.3), jeśli co najmniej jeden z następujących warunków:

  • primary_no_array_creation_expression ma typ czasu kompilacji dynamic.
  • Co najmniej jedno wyrażenie argument_list ma typ czasu kompilacji dynamic, a primary_no_array_creation_expression nie ma typu tablicy, a primary_no_array_creation_expression nie ma wbudowanego typu tablicy lub istnieje więcej niż jeden element na liście argumentów.

W tym przypadku kompilator klasyfikuje element_access jako wartość typu dynamic. Poniższe reguły określające znaczenie element_access są następnie stosowane w czasie wykonywania przy użyciu typu czasu wykonywania zamiast typu czasu kompilacji tych z primary_no_array_creation_expression i argument_list wyrażeń, które mają typ czasu kompilacji dynamic. Jeśli primary_no_array_creation_expression nie ma typu czasu kompilacji dynamic, to dostęp do elementu podlega ograniczonej kontroli w czasie kompilacji zgodnie z opisem w §11.6.5.

Jeśli primary_no_array_creation_expressionelement_access jest wartością array_type, element_access jest dostępem do tablicy (§12.8.12.2). Jeśli primary_no_array_creation_expressionelement_access jest zmienną lub wartością typu wbudowanej tablicy, a argument_list składa się z jednego argumentu, element_access jest wbudowanym dostępem do elementu tablicy. W przeciwnym razie primary_no_array_creation_expression jest zmienną lub wartością klasy, struktury lub typu interfejsu, który ma co najmniej jeden element indeksujący, w takim przypadku element_access jest dostępem indeksatora (§12.8.12.3).

Dostęp do liniowych elementów tablicy

Aby uzyskać dostęp do wbudowanych elementów tablicy, primary_no_array_creation_expressionelement_access musi być zmienną lub wartością typu tablicy wbudowanej. Ponadto dostęp do argument_list wbudowanych elementów tablicy nie może zawierać nazwanych argumentów. argument_list musi zawierać pojedyncze wyrażenie, a wyrażenie musi być

  • typu intlub
  • niejawnie konwertowalny do intlub
  • implicitnie konwertowalny do System.Index, lub
  • Niejawnie konwertowalny do System.Range.
Gdy typ wyrażenia jest int

Jeśli primary_no_array_creation_expression jest zmienną zapisywalną, wynikiem oceny dostępu do wbudowanych elementów tablicy jest zapisywalna zmienna równoważna wywołaniu public ref T this[int index] { get; } z tą wartością całkowitą w wystąpieniu System.Span<T> zwróconym przez metodę System.Span<T> InlineArrayAsSpan w primary_no_array_creation_expression. W celu analizy bezpieczeństwa referencji ref-safe-context/bezpieczny kontekst dostępu są równoważne dla wywołania metody z sygnaturą static ref T GetItem(ref InlineArrayType array). Zmienna wynikowa jest uważana za wymienną, jeśli i tylko wtedy, gdy primary_no_array_creation_expression jest wymienna.

Jeśli primary_no_array_creation_expression jest zmienną readonly, proces oceny dostępu do wbudowanych elementów tablicy prowadzi do uzyskania zmiennej readonly, która jest równoważna wywołaniu public ref readonly T this[int index] { get; } z tą wartością liczbową na instancji System.ReadOnlySpan<T>, zwróconej przez metodę System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan na primary_no_array_creation_expression. Na potrzeby analizy bezpieczeństwa ref kontekst ref-bezpieczeństwa wraz z/oraz kontekst bezpieczny dostępu są równoważne wywołaniu metody z sygnaturą static ref readonly T GetItem(in InlineArrayType array). Zmienna wynikowa jest uważana za wymienną, jeśli i tylko wtedy, gdy primary_no_array_creation_expression jest wymienna.

Jeśli primary_no_array_creation_expression jest wartością, wynikiem oceny dostępu do elementu wbudowanej tablicy jest wartość równoważna wywołaniu public ref readonly T this[int index] { get; } z tą wartością całkowitą na instancji System.ReadOnlySpan<T>, zwróconej przez metodę System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan na primary_no_array_creation_expression. W celu analizy bezpieczeństwa referencji, konteksty bezpieczne ref-safe-context/oraz dostępu są równoważne wywołaniu metody z sygnaturą static T GetItem(InlineArrayType array).

Na przykład:

[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
}

Indeksowanie do tablicy wbudowanej z wyrażeniem stałym poza zadeklarowaną granicą tablicy wbudowanej jest błędem czasu kompilacji.

Gdy wyrażenie jest niejawnie konwertowane na int

Wyrażenie jest przekształcane na int, a następnie dostęp do elementu jest interpretowany zgodnie z opisem w sekcjach i, gdy typ wyrażenia jest int.

Gdy wyrażenie niejawnie konwertowalne na System.Index

Wyrażenie jest konwertowane na System.Index, które jest następnie przekształcane na wartość indeksu opartą na int, zgodnie z opisem w https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support, przy założeniu, że długość kolekcji jest znana w czasie kompilacji i jest równa liczbie elementów w typie tablicy wbudowanej dla primary_no_array_creation_expression. Następnie dostęp do elementu jest interpretowany zgodnie z opisem w sekcji Gdy typ wyrażenia jest int.

Gdy wyrażenie jest niejawnie konwertowane na System.Range

Jeśli primary_no_array_creation_expression jest zmienną zapisywalną, wynikiem oceny dostępu do elementu wbudowanej tablicy jest wartość równoważna wywołaniu public Span<T> Slice (int start, int length) na instancji System.Span<T> zwróconej przez metodę System.Span<T> InlineArrayAsSpan na primary_no_array_creation_expression. W celu analizy bezpieczeństwa ref kontekst bezpieczny ref-safe-context/ dostępu są równoważne wywołaniu metody o sygnaturze static System.Span<T> GetSlice(ref InlineArrayType array).

Jeśli primary_no_array_creation_expression jest zmienną readonly, wynikiem oceny dostępu do elementu tablicy wbudowanej jest wartość równoważna wywołaniu public ReadOnlySpan<T> Slice (int start, int length) w wystąpieniu System.ReadOnlySpan<T> zwróconym przez metodę System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan w primary_no_array_creation_expression. W celu analizy bezpieczeństwa referencji, kontekst ref-bezpieczeństwo /oraz kontekstu dostępu są równoważne analogicznym kontekstom przy wywołaniu metody o podpisie static System.ReadOnlySpan<T> GetSlice(in InlineArrayType array).

Jeśli primary_no_array_creation_expression jest wartością, zgłaszany jest błąd.

Argumenty wywołania metody Slice są obliczane na podstawie wyrażenia indeksu przekonwertowanego na System.Range zgodnie z opisem w https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support, przy założeniu, że długość kolekcji jest znana w czasie kompilacji i jest równa ilości elementów w wbudowanym typie tablicy primary_no_array_creation_expression.

Kompilator może pominąć wywołanie Slice, jeśli jest znane w czasie kompilacji, że start wynosi 0, a length jest mniejsza lub równa ilości elementów w wbudowanym typie tablicy. Kompilator może również zgłosić błąd, jeśli jest znany w czasie kompilacji, że fragmentowanie wykracza poza wbudowane granice tablicy.

Na przykład:

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
}

Konwersje

Zostanie dodana nowa konwersja tablicy inline z wyrażenia. Konwersja tablicy wbudowanej to konwersji standardowej.

Istnieje niejawna konwersja z wyrażenia typu tablicy wbudowanej na następujące typy:

  • System.Span<T>
  • System.ReadOnlySpan<T>

Jednak konwertowanie zmiennej tylko do odczytu na System.Span<T> lub konwertowanie wartości na oba te typy jest błędem.

Na przykład:

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
}

W celu analizy bezpieczeństwa ref bezpieczny kontekst konwersji jest odpowiednikiem bezpiecznego kontekstu wywołania metody z podpisem static System.Span<T> Convert(ref InlineArrayType array)lub static System.ReadOnlySpan<T> Convert(in InlineArrayType array).

Wzorce listy

Wzorce listy nie będą obsługiwane dla wystąpień wbudowanych typów tablic.

Sprawdzanie definitywnego przypisania

Zwykłe reguły przypisania określonego mają zastosowanie do zmiennych, które mają typ tablicy w wierszu.

Literały kolekcji

Instancja typu tablicy inline jest prawidłowym wyrażeniem w spread_element.

Poniższa funkcja nie została dostarczona w języku C# 12. Pozostaje otwarta propozycja. Kod w tym przykładzie generuje CS9174:

Wbudowany typ tablicy to prawidłowy konstruowalny typ docelowy kolekcji dla wyrażenia kolekcji . Na przykład:

Buffer10<int> b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // initializes user-defined inline array

Długość literału kolekcji musi być zgodna z długością docelowego typu tablicy wbudowanej. Jeśli długość literału jest znana w czasie kompilacji i nie jest zgodna z długością docelową, zgłaszany jest błąd. W przeciwnym razie wyjątek zostanie zgłoszony podczas wykonywania, gdy zostanie wykryta niezgodność. Dokładny typ wyjątku to TBD. Niektórzy kandydaci to: System.NotSupportedException, System.InvalidOperationException.

Walidacja aplikacji InlineArrayAttribute

Kompilator zweryfikuje następujące aspekty aplikacji InlineArrayAttribute:

  • Typ docelowy to struktura , która nie jest rekordem.
  • Typ docelowy ma tylko jedno pole
  • Określona długość > 0
  • Struktura docelowa nie ma określonego jawnego układu

Wbudowane elementy tablicy w inicjatorze obiektów

Domyślnie inicjowanie elementów nie będzie obsługiwane za pośrednictwem initializer_target formularza '[' argument_list ']' (zobacz 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;
}

Jeśli jednak typ tablicy wbudowanej jawnie definiuje odpowiedni indeksator, inicjator obiektów użyje go:

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;
    }
}

Instrukcja foreach

instrukcja foreach zostanie skorygowana, aby umożliwić użycie wbudowanego typu tablicy jako kolekcji w instrukcji foreach.

Na przykład:

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;

jest odpowiednikiem:

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);
}

Będziemy obsługiwać foreach na tablicach wbudowanych, nawet jeśli rozpoczyna się jako ograniczona w metodach async ze względu na włączenie typów span w procesie tłumaczenia.

Otwarte pytania projektowe

Alternatywy

Składnia typu tablicy w linii

Gramatyka w https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general zostanie skorygowana w następujący sposób:

array_type
    : non_array_type rank_specifier+
    ;

rank_specifier
    : '[' ','* ']'
+   | '[' constant_expression ']' 
    ;

Typ constant_expression musi być niejawnie konwertowalny na typ int, a wartość musi być niezerową dodatnią liczbą całkowitą.

Odpowiednia część sekcji https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#1721-general zostanie skorygowana w następujący sposób.

W https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-generalznajdują się produkcje gramatyczne dla typów tablic.

Typ tablicy jest zapisywany jako non_array_type, po którym następuje co najmniej jeden rank_specifiers.

non_array_type to każdy typ , który sam nie jest array_type.

Ranga typu tablicowego jest określona przez najbardziej lewostronne rank_specifier w array_type: rank_specifier wskazuje, że tablica ma rangę równą jedności powiększonej o liczbę tokenów „,” w rank_specifier.

Typ elementu typu tablicy to typ, który wynika z usunięcia rank_specifierpo lewej stronie:

  • Typ tablicy w formie T[ constant_expression ] jest anonimowym typem tablicy wbudowanej o długości określonej przez constant_expression oraz typem elementu innym niż tablica T.
  • Typ tablicy w formie T[ constant_expression ][R₁]...[Rₓ] to anonimowy wbudowany typ tablicy o długości określonej przez constant_expression i typie elementu T[R₁]...[Rₓ].
  • Typ tablicy postaci T[R] (gdzie R nie jest constant_expression) jest zwykłym typem tablicy o randze R i nietablicowym typem elementu T.
  • Typ tablicy typu T[R][R₁]...[Rₓ] (gdzie R nie jest constant_expression) jest zwykłym typem tablicy o randze R i typem elementu T[R₁]...[Rₓ].

W efekcie rank_specifiersą odczytywane od lewej do prawej przed ostatnim typem składnika innego niż tablica.

Przykład: typ w int[][,,][,] jest jednowymiarową tablicą, która zawiera trójwymiarowe tablice, a te z kolei zawierają dwuwymiarowe tablice int. przykład końcowy

W czasie wykonywania wartość typu tablicy regularnej może być null lub odwołanie do wystąpienia tego typu tablicy.

Uwaga: zgodnie z regułami https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariancewartość może być również odwołaniem do typu tablicy kowariantnej. przypis końcowy

Anonimowy wbudowany typ tablicy jest typem tablicy jednowymiarowej syntetyzowanym przez kompilator z dostępnością wewnętrzną. Typ elementu musi być typem, który może być używany jako argument typu. W przeciwieństwie do jawnie zadeklarowanego typu tablicy wbudowanej nie można odwoływać się do anonimowego typu tablicy wbudowanej według nazwy, można odwoływać się tylko przez składnię array_type. W kontekście tego samego programu wszystkie dwa array_typeoznaczające wbudowane typy tablic tego samego typu elementu i o tej samej długości, odwołują się do tego samego anonimowego typu tablicy wbudowanej.

Oprócz wewnętrznej dostępności kompilator uniemożliwi korzystanie z interfejsów API, które wykorzystują anonimowe, wbudowane typy tablicowe poza granicami zestawu, stosując wymagany modyfikator niestandardowy (dokładny typ do ustalenia) do odwołania do anonimowego, wbudowanego typu tablicowego w sygnaturze.

Wyrażenia tworzenia tablicy

Wyrażenia tworzenia tablic

array_creation_expression
    : 'new' non_array_type '[' expression_list ']' rank_specifier*
      array_initializer?
    | 'new' array_type array_initializer
    | 'new' rank_specifier array_initializer
    ;

Biorąc pod uwagę bieżącą gramatykę, użycie constant_expression zamiast expression_list ma już znaczenie przydzielania regularnego jednowymiarowego typu tablicy o określonej długości. W związku z tym array_creation_expression będzie nadal reprezentować alokację tablicy regularnej.

Jednak nowa forma rank_specifier może być użyta do wprowadzenia anonimowego typu tablicy inline jako typu elementu przydzielonej tablicy.

Na przykład następujące wyrażenia tworzą zwykłą tablicę o długości 2, gdzie typem elementu jest anonimowy typ tablicy wbudowanej o długości 5 i typie elementu int:

new int[2][5];
new int[][5] {default, default};
new [] {default(int[5]), default(int[5])};

Inicjalizatory tablic

Inicializatory tablic nie zostały zaimplementowane w języku C# 12. Ta sekcja pozostaje aktywną propozycją.

Inicjatory tablicy zostaną dostosowane w celu umożliwienia używania array_initializer do inicjowania typów tablic wbudowanych (brak zmian w gramatyki).

array_initializer
    : '{' variable_initializer_list? '}'
    | '{' variable_initializer_list ',' '}'
    ;

variable_initializer_list
    : variable_initializer (',' variable_initializer)*
    ;
    
variable_initializer
    : expression
    | array_initializer
    ;

Długość tablicy wbudowanej musi być jawnie podana przez typ docelowy.

Na przykład:

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

Szczegółowy projekt (opcja 2)

Należy pamiętać, że w celu tego wniosku termin "bufor o stałym rozmiarze" odnosi się do proponowanej funkcji "bezpiecznego buforu o stałym rozmiarze", a nie buforu opisanego w https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers.

W tym projekcie typy o stałym rozmiarze nie otrzymują szczególnego traktowania przez język. Istnieje specjalna składnia do deklarowania członków reprezentujących bufory o stałym rozmiarze oraz nowe zasady dotyczące użycia tych członków. Nie są to pola z punktu widzenia języka.

Gramatyka variable_declarator w https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields zostanie rozszerzona, aby umożliwić określenie rozmiaru buforu:

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 ']'
    ;    

fixed_size_buffer_declarator wprowadza bufor o stałym rozmiarze o danym typie elementu.

Typ elementu buforu to typ określony w field_declaration. Deklarator buforu o stałym rozmiarze wprowadza nową składową i składa się z identyfikatora, który tę składową nazywa, a także z wyrażenia stałego zawartego w tokenach [ i ]. Wyrażenie stałe określa liczbę elementów w elemencie członkowskim wprowadzonym przez deklarator bufora o stałym rozmiarze. Typ wyrażenia stałego musi być niejawnie konwertowany na typ int, a wartość musi być niezerową liczbą całkowitą dodatnią.

Elementy buforu o stałym rozmiarze należy określić sekwencyjnie w pamięci, tak jakby były elementami tablicy.

field_declaration z fixed_size_buffer_declarator w ramach interfejsu powinno zawierać modyfikator static.

W zależności od sytuacji (szczegóły są określone poniżej), dostęp do składowej buforu o stałym rozmiarze jest klasyfikowany jako wartość (nigdy zmienna) System.ReadOnlySpan<S> lub System.Span<S>, gdzie S jest typem elementu buforu o stałym rozmiarze. Oba typy zapewniają indeksatory zwracające odwołanie do określonego elementu z odpowiednimi właściwościami "tylko do odczytu", co uniemożliwia bezpośrednie przypisanie do elementów, kiedy zasady języka na to nie pozwalają.

Ogranicza to zestaw typów, które mogą być używane jako typ elementu buforu o stałym rozmiarze do typów, które mogą być używane jako argumenty typu. Na przykład typ wskaźnika nie może być używany jako typ elementu.

Wynikowa instancja zakresu będzie mieć długość równą rozmiarowi zadeklarowanemu w buforze o stałym rozmiarze. Indeksowanie w ramach zakresu z wyrażeniem stałym poza zadeklarowanymi granicami buforu o stałej wielkości powoduje błąd w czasie kompilacji.

bezpieczny kontekst wartości będzie równy bezpiecznemu kontekstowi kontenera, podobnie jak w przypadku uzyskania dostępu do danych pomocniczych jako pola.

Bufory o stałym rozmiarze w wyrażeniach

Wyszukiwanie składowe elementu buforowego o stałym rozmiarze jest kontynuowane dokładnie tak jak wyszukiwanie składowych pola.

W wyrażeniu można odwoływać się do buforu o stałym rozmiarze przy użyciu simple_name lub member_access .

Gdy element członkowski buforu o stałym rozmiarze wystąpienia jest przywoływany jako prosta nazwa, efekt jest taki sam jak dostęp do elementu członkowskiego w postaci this.I, gdzie I jest elementem członkowskim buforu o stałym rozmiarze. Gdy statyczny składowy bufor o stałym rozmiarze jest odwoływany jako prosta nazwa, efekt jest taki sam jak dostęp do składowej w formie E.I, gdzie I jest składową buforu o stałym rozmiarze, a E jest typem deklarującym.

bufory o stałym rozmiarze, które nie są tylko do odczytu

W przypadku dostępu do elementu członkowskiego w postaci E.I, jeśli E jest typem struktury i w ramach tego typu struktury składowanie I identyfikuje instancję składową o stałym rozmiarze, która nie jest tylko do odczytu, to E.I jest oceniany i klasyfikowany w następujący sposób:

  • Jeśli E jest klasyfikowana jako wartość, E.I można użyć tylko jako primary_no_array_creation_expression dostępu elementu z indeksem typu System.Index lub typu niejawnie konwertowanego na int. Wynik dostępu do elementu jest elementem elementu o stałym rozmiarze w określonej pozycji sklasyfikowanym jako wartość.
  • W przeciwnym razie, jeśli E jest klasyfikowana jako zmienna readonly, a wynik wyrażenia jest klasyfikowany jako wartość typu System.ReadOnlySpan<S>, gdzie S jest typem elementu I. Wartość może być użyta do uzyskiwania dostępu do elementów członka.
  • W przeciwnym razie E jest klasyfikowana jako zmienna zapisywalna, a wynik wyrażenia jest klasyfikowany jako wartość typu System.Span<S>, gdzie S jest typem elementu I. Wartość może być używana do uzyskiwania dostępu do elementów członkowskich.

W przypadku dostępu do elementu członkowskiego w postaci E.I, jeśli E jest typu klasy i wyszukiwanie członka I w tym typie klasy identyfikuje członka instancji o stałym rozmiarze, który nie jest tylko do odczytu, wtedy E.I jest obliczana i klasyfikowana jako wartość typu System.Span<S>, gdzie S jest typem elementu I.

W dostępie do elementu członkowskiego w formie E.I, jeśli wyszukiwanie składowe I identyfikuje niesystematycznie statyczny członkowski element o stałym rozmiarze, wtedy E.I jest oceniany i klasyfikowany jako wartość typu System.Span<S>, gdzie S jest typem elementów I.

Bufory tylko do odczytu o stałym rozmiarze

Gdy field_declaration zawiera modyfikator readonly, element członkowski wprowadzony przez fixed_size_buffer_declarator jest tylko do odczytu buforem o stałym rozmiarze. Bezpośrednie przypisania do elementów buforu o stałym rozmiarze tylko do odczytu mogą występować tylko w konstruktorze wystąpienia, członie inicjującym lub konstruktorze statycznym w tym samym typie. W szczególności bezpośrednie przypisania do elementu niezmiennego bufora o stałym rozmiarze są dozwolone tylko w następujących kontekstach.

  • W przypadku składowej instancyjnej, w konstruktorach instancyjnych lub członku inicjującym typu, który zawiera deklarację składowej; dla składowej statycznej, w konstruktorze statycznym typu, który zawiera deklarację składowej. Są to również jedyne konteksty, w których prawidłowe jest przekazanie elementu bufora o stałym rozmiarze przeznaczonego tylko do odczytu jako parametru out lub ref.

Próba przypisania elementu do bufora o stałym rozmiarze tylko do odczytu lub przekazania go jako parametr out lub ref w innym kontekście jest błędem czasu kompilacji. Można to osiągnąć, wykonując następujące czynności.

Dostęp do elementu członkowskiego dla buforu o stałym rozmiarze odczytu jest oceniany i klasyfikowany w następujący sposób:

  • W dostępie do elementu członkowskiego formularza E.I, jeśli E jest typu struktury i E jest klasyfikowany jako wartość, to E.I można używać tylko jako primary_no_array_creation_expression dostępu do elementu z indeksem typu System.Index lub typu niejawnie konwertowalnego na int. Wynikiem dostępu do elementu jest element członkowski o stałym rozmiarze w określonej pozycji, sklasyfikowany jako wartość.
  • Jeśli dostęp występuje w kontekście, w którym bezpośrednie przypisania do elementu buforu o stałym rozmiarze odczytu są dozwolone, wynik wyrażenia jest klasyfikowany jako wartość typu System.Span<S>, gdzie S jest typem elementu bufora o stałym rozmiarze. Wartość może służyć do uzyskiwania dostępu do elementów członka.
  • W przeciwnym razie wyrażenie jest klasyfikowane jako wartość typu System.ReadOnlySpan<S>, gdzie S jest typem elementu bufora o stałym rozmiarze. Wartość może służyć do uzyskiwania dostępu do elementów członkowskich.

Sprawdzanie określonego przypisania

Bufory o stałym rozmiarze nie podlegają sprawdzaniu przypisań, a elementy członkowskie buforu o stałym rozmiarze są ignorowane dla celów sprawdzania przypisań zmiennych typu struktury.

Gdy składowa buforu o stałym rozmiarze jest statyczna lub najbardziej zewnętrzna struktura zawierająca zmienną buforu o stałym rozmiarze jest zmienną statyczną, zmienną wystąpienia klasy lub elementem tablicy, elementy bufora o stałym rozmiarze są automatycznie inicjowane do ich wartości domyślnych. We wszystkich innych przypadkach początkowa zawartość buforu o stałym rozmiarze jest niezdefiniowana.

Metadane

Emisja metadanych i generacja kodu

Kompilator kodujący metadane odwoła się do niedawno dodanego System.Runtime.CompilerServices.InlineArrayAttribute.

Bufory o stałym rozmiarze, takie jak następujący pseudokod:

// Not valid C#
public partial class C
{
    public int buffer1[10];
    public readonly int buffer2[10];
}

będzie emitowany jako pola specjalnie oznaczonego typu struktury.

Równoważny kod języka C# będzie:

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);
    }
}

Obecne konwencje nazewnictwa dla typu i jego składowych są w trakcie ustalania. Struktura będzie prawdopodobnie zawierać zestaw wstępnie zdefiniowanych typów "buforu", które obejmują ograniczony zestaw rozmiarów. Gdy wstępnie zdefiniowany typ nie istnieje, kompilator zsyntetyzuje go w kompilowanym module. Nazwy wygenerowanych typów będą "czytelne" do wykorzystania w innych językach.

Kod wygenerowany dla dostępu, taki jak:

public partial class C
{
    void M1(int val)
    {
        buffer1[1] = val;
    }

    int M2()
    {
        return buffer2[1];
    }
}

będzie odpowiednikiem:

public partial class C
{
    void M1(int val)
    {
        buffer.AsSpan()[1] = val;
    }

    int M2()
    {
        return buffer2.AsReadOnlySpan()[1];
    }
}
Importowanie metadanych

Gdy kompilator importuje deklarację pola typu T i spełnione są następujące warunki:

  • T to typ struktury ozdobiony atrybutem InlineArray i
  • Pole pierwszego wystąpienia zadeklarowane w T ma typ Fi
  • Istnieje public System.Span<F> AsSpan() w Ti
  • Istnieje public readonly System.ReadOnlySpan<T> AsReadOnlySpan() lub public System.ReadOnlySpan<T> AsReadOnlySpan() w T.

pole będzie traktowane jako bufor o stałym rozmiarze języka C# z typem elementu F. W przeciwnym razie pole będzie traktowane jako zwykłe pole typu T.

Metoda lub grupa właściwości, takie jak podejście w języku

Jedną z myśli jest traktowanie tych członków bardziej jak grupy metod, w ten sposób, że same w sobie nie są automatycznie wartością, ale można je przekształcić w wartość, jeśli to konieczne. Oto jak to zadziała:

  • Bezpieczne bufory o stałym rozmiarze mają własną klasyfikację (tak jak np. grupy metod i lambdy)
  • Można je indeksować bezpośrednio jako operację języka (a nie za pośrednictwem typów rozpiętości), aby utworzyć zmienną (odczytaną, jeśli bufor znajduje się w kontekście tylko do odczytu, tak samo jak pola struktury)
  • Mają niejawne konwersje z wyrażenia na Span<T> i ReadOnlySpan<T>, ale użycie poprzedniego jest błędem, jeśli znajdują się w kontekście tylko do odczytu
  • Ich naturalny typ to ReadOnlySpan<T>, więc to właśnie one współtworzą, jeśli uczestniczą w wnioskowaniu typu (np. var, best-common-type lub ogólny).

Bufory o stałym rozmiarze w C/C++

Język C/C++ ma inne pojęcie buforów o stałej wielkości. Na przykład istnieje pojęcie "buforów o stałej długości zerowej", które jest często używane jako sposób wskazania, że dane mają "zmienną długość". Nie jest to cel tej propozycji, aby móc z tym współdziałać.

Spotkania LDM