Inlinovaná pole
Poznámka
Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.
Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v příslušných poznámkách schůzce návrhu jazyka (LDM).
Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .
Problém šampiona: https://github.com/dotnet/csharplang/issues/7431
Shrnutí
Poskytnout obecný a bezpečný mechanismus pro využívání typů struktur pomocí funkce InlineArrayAttribute. Poskytuje obecný a bezpečný mechanismus pro deklarování vložených polí v rámci tříd, struktur a rozhraní jazyka C#.
Poznámka: Předchozí verze této specifikace používaly termíny "ref-safe-to-escape" a "safe-to-escape", které byly zavedeny ve specifikaci funkce Span safety. Standardní výbor ECMA změnil názvy na "ref-safe-context" a "bezpečný kontext". Hodnoty bezpečného kontextu byly upřesněny tak, aby používaly "blok-deklarace", "funkce-člen" a "kontext-volajícího" konzistentně. Specifikace pro tyto termíny používaly různé formulace a také používaly "safe-to-return" jako synonymum pro "caller-context". Tato specifikace byla aktualizována tak, aby používala termíny ve standardu C# 7.3.
Motivace
Tento návrh si klade za cíl řešit mnoho omezení https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers. Konkrétně má za cíl umožnit:
- přístup k prvkům struktur pomocí funkce InlineArrayAttribute;
- deklarace in-line polí pro spravované a nespravované typy v
struct
,class
nebointerface
.
A zajistěte pro ně ověření bezpečnosti jazyka.
Podrobný návrh
Modul runtime nedávno přidal funkci InlineArrayAttribute. Stručně řečeno, uživatel může deklarovat typ struktury jako následující:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private object _element0;
}
Modul runtime poskytuje zvláštní rozložení typu pro typ Buffer
:
- Velikost typu je rozšířena tak, aby odpovídala 10 (číslo pochází z atributu InlineArray) elementům typu „
object
“ (typ pochází z typu jediného pole instance ve struktuře, „_element0
“ v tomto příkladu). - První prvek je zarovnán s polem instance a se začátkem struktury.
- Prvky jsou rozloženy postupně v paměti, jako by se jedná o prvky pole.
Modul runtime poskytuje pravidelné sledování GC pro všechny prvky ve struktuře.
Tento návrh bude odkazovat na podobné typy jako "vložené typy polí".
K prvkům vloženého typu pole lze přistupovat prostřednictvím ukazatelů nebo prostřednictvím instance rozpětí vrácené rozhraními API System.Runtime.InteropServices.MemoryMarshal.CreateSpan/System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan. Přístup ukazatele ani rozhraní API však neposkytují kontrolu typu a hranic.
Jazyk poskytne typově a referenčně bezpečný způsob přístupu k prvkům vložených typů polí. Přístup bude založený na časovém rozsahu. Toto omezení podporuje vložené typy polí s typy prvků, které lze použít jako argument typu. Například typ ukazatele nelze použít jako typ prvku. Další příklady typů rozsahu
Získání instancí typů úseků pro vnořený typ pole
Vzhledem k tomu, že existuje záruka, že první prvek typu vloženého pole je zarovnaný na začátku typu (bez mezery), kompilátor použije následující kód k dosažení hodnoty Span
.
MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);
A následující kód pro získání hodnoty ReadOnlySpan
:
MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);
Aby se zmenšila velikost IL na místech použití, měl by být kompilátor schopen přidat dva obecné, opakovaně použitelné pomocníky do typu pro soukromé detaily implementace a používat je napříč všemi místy použití v rámci stejného programu.
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);
}
Přístup k elementům
Přístup elementu k bude rozšířen tak, aby podporoval přístup k prvkům pole v řádku.
element_access se skládá z primary_no_array_creation_expression, po němž následuje token "[
", poté argument_list, a nakonec token "]
".
argument_list se skládá z jednoho nebo více argumentů , které jsou odděleny čárkami.
element_access
: primary_no_array_creation_expression '[' argument_list ']'
;
argument_listelement_access nesmí obsahovat ref
ani out
argumenty.
element_access je dynamicky svázaný (§11.3.3) v případě, že platí alespoň jeden z následujících:
-
primary_no_array_creation_expression má typ určený při kompilaci
dynamic
. - Nejméně jeden výraz argument_list má typ kompilace
dynamic
a primary_no_array_creation_expression nemá typ pole, a primary_no_array_creation_expression neobsahuje vložený typ pole nebo existuje více než jedna položka v seznamu argumentů.
V tomto případě kompilátor klasifikuje element_access jako hodnotu typu dynamic
. Pravidla pro určení významu element_access se pak použijí za běhu programu, přičemž se používá typ za běhu místo typu doby kompilace u výrazů primary_no_array_creation_expression a argument_list, které mají typ kompilace dynamic
. Pokud primary_no_array_creation_expression nemá typ kompilace dynamic
, projde přístup k prvku omezenou kontrolou doby kompilace, jak je popsáno v §11.6.5.
Je-li primary_no_array_creation_expressionelement_access hodnota array_type, element_access je maticový přístup (§12.8.12.2). Pokud primary_no_array_creation_expression prvku element_access je proměnná nebo hodnota typu vloženého pole a argument_list se skládá z jediného argumentu, pak je element_access přístup k prvku vloženého pole. V opačném případě musí být primary_no_array_creation_expression proměnnou nebo hodnotou třídy, struktury nebo typu rozhraní, který má jeden nebo více členů indexeru, v takovém případě je element_access přístup indexeru (§12.8.12.3).
Přístup k prvku pole přímo v kódu
Pro přístup k prvkům vloženého pole musí být primary_no_array_creation_expressionelement_access proměnnou nebo hodnotou vloženého typu pole. Kromě toho argument_list při přístupu k prvku vloženého pole nesmí obsahovat pojmenované argumenty. argument_list musí obsahovat jeden výraz a výraz musí být
- typu
int
nebo - implicitně konvertibilní na
int
nebo - implicitně konvertibilní na
System.Index
nebo - implicitně konvertibilní na
System.Range
.
Když je typ výrazu int
Pokud je primary_no_array_creation_expression zapisovatelná proměnná, výsledek vyhodnocení přístupu k prvku pole je zapisovatelná proměnná, která je ekvivalentní volání public ref T this[int index] { get; }
s danou celočíselnou hodnotou na instanci System.Span<T>
, kterou vrací metoda System.Span<T> InlineArrayAsSpan
na primary_no_array_creation_expression. Za účelem analýzy ref-safety jsou ref-safe-context/a safe-context přístupu ekvivalentní stejnému pro vyvolání metody s podpisem static ref T GetItem(ref InlineArrayType array)
.
Výsledná proměnná se považuje za pohyblivou, pouze pokud primary_no_array_creation_expression je pohyblivá.
Pokud primary_no_array_creation_expression je proměnná jen pro čtení, výsledek vyhodnocení přístupu k vloženému prvku pole je proměnná jen pro čtení, což je ekvivalentní volání public ref readonly T this[int index] { get; }
s danou celočíselnou hodnotou na instanci System.ReadOnlySpan<T>
vrácenou metodou System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
na primary_no_array_creation_expression. Pro účely analýzy bezpečnosti referencí jsou /a bezpečný kontext přístupu ekvivalentní k tomu, co platí pro vyvolání metody s podpisem static ref readonly T GetItem(in InlineArrayType array)
.
Výsledná proměnná se považuje za pohyblivou, pouze pokud primary_no_array_creation_expression je pohyblivá.
Pokud je primary_no_array_creation_expression hodnotou, výsledkem vyhodnocení přímého přístupu k prvku pole je hodnota, která je ekvivalentní vyvolání public ref readonly T this[int index] { get; }
s danou celočíselnou hodnotou na instanci System.ReadOnlySpan<T>
, kterou vrátí metoda System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
v rámci primary_no_array_creation_expression. Pro účely analýzy bezpečnosti referencí jsou bezpečné kontexty/a přístupu ekvivalentní k těm, které se používají pro vyvolání metody s podpisem static T GetItem(InlineArrayType array)
.
Například:
[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
}
Indexování do vloženého pole s konstantním výrazem mimo deklarované hranice je chyba při kompilaci.
Pokud je výraz implicitně konvertibilní na int
Výraz se převede na int a pak je přístup k elementu interpretován, jak je uvedeno v sekci Když je typ výrazu int.
Když se výraz stane implicitně konvertibilním na System.Index
Výraz se převede na System.Index
, který se pak transformuje na hodnotu indexu založenou na int, jak je popsáno v https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support, za předpokladu, že délka kolekce je známa v době kompilace a je rovna množství prvků v vložené matici typu primary_no_array_creation_expression. Pak je přístup k elementu interpretován podle popisu v v oddílu Když je typ výrazu int.
Když je výraz implicitně konvertibilní na System.Range
Pokud primary_no_array_creation_expression je zapisovatelná proměnná, výsledek vyhodnocení přístupu k prvku pole je hodnota, která je ekvivalentní vyvolání public Span<T> Slice (int start, int length)
na instanci System.Span<T>
vrácenou metodou System.Span<T> InlineArrayAsSpan
v primary_no_array_creation_expression.
Pro účely analýzy ref-safety je ref-safe-context/bezpečný kontext přístupu ekvivalentní stejnému pro vyvolání metody s podpisem static System.Span<T> GetSlice(ref InlineArrayType array)
.
Pokud je primary_no_array_creation_expression proměnnou jen pro čtení, výsledkem zhodnocení přímého přístupu k prvku pole je hodnota ekvivalentní vyvolání public ReadOnlySpan<T> Slice (int start, int length)
na instanci System.ReadOnlySpan<T>
vrácenou metodou System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
v primary_no_array_creation_expression.
Pro účely analýzy ref-safety je ref-safe-context/a safe-context pro přístup ekvivalentní tomu samému jako při vyvolání metody s podpisem static System.ReadOnlySpan<T> GetSlice(in InlineArrayType array)
.
Pokud primary_no_array_creation_expression má hodnotu, zobrazí se chyba.
Argumenty pro vyvolání metody Slice
se počítají z výrazu indexu převedeného na System.Range
, jak je popsáno v https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support, za předpokladu, že délka kolekce je známa v době kompilace a je rovna počtu prvků v inline matici typu primary_no_array_creation_expression.
Kompilátor může vynechat volání Slice
, pokud je známo v době kompilace, že start
je 0 a length
je menší nebo roven počtu prvků vestavěného typu pole. Kompilátor může také hlásit chybu, pokud je již v době kompilace známo, že řez přesahuje meze vnořeného pole.
Například:
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
}
Konverzace
Bude přidána nová konverze interního pole z výrazu. Vložený převod pole je standardní převod.
Existuje implicitní převod z výrazu typu vloženého pole na následující typy:
System.Span<T>
System.ReadOnlySpan<T>
Převod proměnné pouze pro čtení na System.Span<T>
nebo převod hodnoty na některý z typů je chyba.
Například:
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
}
Pro účely analýzy ref-safety bezpečného kontextu převodu odpovídá bezpečnému kontextu pro vyvolání metody s podpisem static System.Span<T> Convert(ref InlineArrayType array)
nebo static System.ReadOnlySpan<T> Convert(in InlineArrayType array)
.
Vzory seznamů
vzory seznamu nebudou podporovány pro instance vložených typů polí.
Kontrola jednoznačného přiřazení
Pravidla pro standardní určité přiřazení jsou použitelná pro proměnné, které mají typ inline pole.
Literály kolekce
Příklad vloženého typu pole je platným výrazem v spread_element.
Následující funkce nebyla dodávána v jazyce C# 12. Zůstává to otevřený návrh. Kód v tomto příkladu generuje CS9174:
Vložený typ pole je platný sestavitelný cílový typ pro výraz kolekce . Například:
Buffer10<int> b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // initializes user-defined inline array
Délka literálu kolekce musí odpovídat délce cílového typu inline pole. Pokud je délka literálu známá v době kompilace a neodpovídá cílové délce, zobrazí se chyba. V opačném případě dojde k vyvolání výjimky za běhu, jakmile dojde k neshodě. Přesný typ výjimky je TBD. Mezi kandidáty patří: System.NotSupportedException, System.InvalidOperationException.
Ověření aplikací InlineArrayAttribute
Kompilátor ověří následující aspekty aplikací InlineArrayAttribute:
- Cílový typ je struktura , která není záznamem.
- Cílový typ má pouze jedno pole.
- Zadaná délka > 0
- Cílová struktura nemá zadané explicitní rozložení.
Vložené prvky Pole v inicializátoru objektů
Ve výchozím nastavení nebude inicializace prvků podporována prostřednictvím initializer_target formuláře '[' argument_list ']'
(viz 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;
}
Pokud však vložený typ pole explicitně definuje vhodný indexer, inicializátor objektů ho použije:
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;
}
}
Příkaz foreach
Příkaz foreach se upraví tak, aby umožňoval použití typu vnořeného pole jako kolekce v příkazu foreach.
Například:
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;
odpovídá:
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);
}
Budeme podporovat foreach
přes vložená pole, i když začíná jako omezený v async
metodách kvůli zapojení typů rozsahu do překladu.
Otevřené otázky týkající se návrhu
Alternativy
Syntaxe vloženého typu pole
Gramatika na https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general se upraví takto:
array_type
: non_array_type rank_specifier+
;
rank_specifier
: '[' ','* ']'
+ | '[' constant_expression ']'
;
Typ constant_expression musí být implicitně konvertibilní na typ int
a hodnota musí být nenulové kladné celé číslo.
Relevantní část oddílu https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#1721-general se upraví následujícím způsobem.
Gramatická pravidla pro typy polí jsou k dispozici v https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general.
Typ pole se zapíše jako non_array_type následovaný jedním nebo více rank_specifiers.
non_array_type je jakýkoli typ , který není sám o sobě array_type.
Pořadí typu pole je určeno krajním levým rank_specifier v array_type: rank_specifier označuje, že pole je pole s pořadím jedné plus počtem tokenů „,
“ v rank_specifier.
Typ elementu pole typu je typ, který je výsledkem odstranění nejlevějšího rank_specifier:
- Typ pole ve formátu
T[ constant_expression ]
je anonymní inline typ pole s délkou určenou constant_expression a typem prvku, který není poleT
. - Typ pole ve formě
T[ constant_expression ][R₁]...[Rₓ]
je anonymní vložený typ pole s délkou určenou konstantním výrazem a typem prvkuT[R₁]...[Rₓ]
. - Typ pole tvaru
T[R]
(kde R není constant_expression) je běžný typ pole s pořadímR
a nepolovým typem prvkuT
. - Typ pole ve formátu
T[R][R₁]...[Rₓ]
(kde R není konstantní_výraz) je typ pole s pravidelnou strukturou s úrovníR
a typem prvkuT[R₁]...[Rₓ]
.
rank_specifiers se tedy čtou zleva doprava před posledního prvku, který není polem.
Příklad: Typ v
int[][,,][,]
je jednorozměrné pole, které obsahuje třírozměrná pole dvourozměrných políint
. konec příkladu
Za běhu může být hodnota běžného typu pole null
nebo odkaz na instanci tohoto typu pole.
Poznámka: Podle pravidel https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariancemůže být hodnota také odkazem na kovariantní typ pole. koncová poznámka
Anonymní vložený typ pole je kompilátor syntetizovaný vložený typ pole s interní přístupností. Typ prvku musí být typ, který lze použít jako argument typu. Na rozdíl od explicitně deklarovaného vloženého typu pole nelze na anonymní vložený typ pole odkazovat podle názvu, lze na něj odkazovat pouze array_type syntaxí. V kontextu stejného programu se všechny dva array_typeoznačující vložené typy pole stejného typu prvku a stejné délky, odkazují na stejný anonymní vložený typ pole.
Kromě zajištění interní přístupnosti zabrání kompilátor použití rozhraní API, která využívají anonymní inline typy polí přes hranice sestavení. To bude dosaženo pomocí požadovaného vlastního modifikátoru, který bude použit na anonymní inline odkaz typu pole v signatuře. Přesný druh modifikátoru bude teprve určen.
Výrazy pro vytváření polí
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
Vzhledem k aktuální gramatikě již použití constant_expression místo expression_list má význam přidělení běžného jednorozměrného typu pole zadané délky. Proto array_creation_expression bude i nadále představovat přidělení běžného pole.
Novou formu rank_specifier lze však použít k začlenění anonymního vloženého typu pole do typu prvku přiděleného pole.
Například následující výrazy vytvoří regulární pole délky 2 s typem elementu anonymního vnořeného pole s typem elementu int a délkou 5.
new int[2][5];
new int[][5] {default, default};
new [] {default(int[5]), default(int[5])};
Inicializátory polí
Inicializátory polí nebyly implementovány v jazyce C# 12. Tato část zůstává aktivním návrhem.
Oddíl Inicializátory pole se upraví tak, aby umožnil použití array_initializer k inicializaci vložených typů polí (není nutné měnit gramatiku).
array_initializer
: '{' variable_initializer_list? '}'
| '{' variable_initializer_list ',' '}'
;
variable_initializer_list
: variable_initializer (',' variable_initializer)*
;
variable_initializer
: expression
| array_initializer
;
Délka vloženého pole musí být explicitně určena cílovým typem.
Například:
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
Podrobný návrh (možnost 2)
Všimněte si, že pro účely tohoto návrhu pojem "vyrovnávací paměť s pevnou velikostí" odkazuje na navrženou funkci "bezpečné vyrovnávací paměti s pevnou velikostí", nikoli na vyrovnávací paměť popsanou v https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers.
V tomto návrhu nedostávají typy vyrovnávací paměti s pevnou velikostí obecně speciální zacházení jazykem. Existuje speciální syntaxe pro deklaraci členů, které představují vyrovnávací paměti s pevnou velikostí a nová pravidla týkající se využívání těchto členů. Nejsou pole z pohledu jazyka.
Gramatika pro variable_declarator v https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields se rozšíří, aby bylo možné určit velikost vyrovnávací paměti:
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 zavádí vyrovnávací paměť s pevnou velikostí daného typu prvku.
Typ prvku vyrovnávací paměti je typu , jak je specifikováno v field_declaration
. Deklarátor vyrovnávací paměti s pevnou velikostí zavádí nový člen. Skládá se z identifikátoru, který tento člen pojmenovává, a konstantního výrazu, který je uzavřen v tokenech [
a ]
. Konstantní výraz označuje počet prvků v členu zavedeného deklarátorem vyrovnávací paměti s pevnou velikostí. Typ konstantního výrazu musí být implicitně konvertibilní na typ int
a hodnota musí být nenulové kladné celé číslo.
Prvky vyrovnávací paměti s pevnou velikostí musí být rozloženy postupně v paměti, jako by se jedná o prvky pole.
field_declaration v rozhraní s fixed_size_buffer_declarator musí mít modifikátor static
.
V závislosti na situaci (podrobnosti jsou uvedeny níže) je přístup k členu vyrovnávací paměti s pevnou velikostí klasifikován jako hodnota (nikdy proměnná) System.ReadOnlySpan<S>
nebo System.Span<S>
, kde S je typ prvku vyrovnávací paměti s pevnou velikostí. Oba typy poskytují indexery vracející odkaz na konkrétní prvek s odpovídajícím režimem "pouze pro čtení", což brání přímému přiřazení k prvku, pokud to pravidla jazyků nedovolují.
Tím se omezí sada typů, které lze použít jako typ prvku vyrovnávací paměti s pevnou velikostí, na typy, které lze použít jako argumenty typu. Například typ ukazatele nelze použít jako typ prvku.
Výsledná instance třídy Span bude mít délku rovnu velikosti deklarované ve vyrovnávací paměti s pevnou velikostí. Vkládání indexu do rozsahu s konstantním výrazem mimo deklarované hranice vyrovnávací paměti s pevnou velikostí je chyba při kompilaci.
bezpečné kontextové hodnoty se budou rovnat bezpečnému kontextu kontejneru, stejně jako kdyby byla záložní data přístupná jako pole.
Vyrovnávací paměti s pevnou velikostí ve výrazech
Vyhledávání člena vyrovnávací paměti s pevnou velikostí pokračuje přesně stejně jako vyhledávání člena pole.
Vyrovnávací paměť s pevnou velikostí lze ve výrazu odkázat pomocí simple_name nebo member_access .
Pokud je na člen vyrovnávací paměti instance s pevnou velikostí odkazován jako na jednoduchý název, efekt je stejný jako přístup člena formuláře this.I
, kde I
je člen vyrovnávací paměti s pevnou velikostí. Pokud se na člen vyrovnávací paměti statické pevné velikosti odkazuje jako na jednoduchý název, efekt je stejný jako přístup člena formuláře E.I
, kde I
je člen vyrovnávací paměti s pevnou velikostí a E
je deklarující typ.
Vyrovnávací paměti bez čtení s pevnou velikostí
V přístupu k členu ve formě E.I
, pokud je E
typu strukt a vyhledávání člena I
v této struktuře identifikuje instanci nepouze pro čtení s pevně stanovenou velikostí, pak se E.I
vyhodnotí a klasifikuje takto:
- Pokud je
E
klasifikováno jako hodnota, lzeE.I
používat pouze jako primary_no_array_creation_expression pro přístup k elementu s indexem typuSystem.Index
nebo typu implicitně převeditelného na int. Výsledkem přístupu k elementu je prvek člena s pevně stanovenou velikostí na zadané pozici, který je klasifikován jako hodnota. - V opačném případě, pokud je
E
klasifikována jako proměnná pouze pro čtení a výsledek výrazu je klasifikován jako hodnota typuSystem.ReadOnlySpan<S>
, kde S je typ prvkuI
. Hodnotu lze použít pro přístup k prvkům člena. - Jinak se
E
klasifikuje jako zapisovatelná proměnná a výsledek výrazu se klasifikuje jako hodnota typuSystem.Span<S>
, kde S je typ prvkuI
. Hodnotu lze použít pro přístup k prvkům člena.
V přístupu člena formuláře E.I
, pokud E
je typu třídy a členské vyhledávání I
v tomto typu třídy identifikuje nečtenou instanci s pevnou velikostí, pak E.I
je vyhodnocena a klasifikována jako hodnota typu System.Span<S>
, kde S je typ prvku typu I
.
Při přístupu člena formuláře E.I
, pokud členské vyhledávání I
identifikuje nečteně statický člen pevné velikosti, E.I
je vyhodnocen a klasifikován jako hodnota typu System.Span<S>
, kde S je typ prvku I
.
Vyrovnávací paměti s pevnou velikostí jen pro čtení
Pokud field_declaration obsahuje modifikátor readonly
, člen zavedený pomocí fixed_size_buffer_declarator je readonly pevné velikosti vyrovnávací paměti.
Přímé přiřazení k prvkům vyrovnávací paměti jen pro čtení s pevnou velikostí může nastat pouze v konstruktoru instance, inicializačním členu nebo statickém konstruktoru stejného typu.
Konkrétně přímé přiřazení k prvku vyrovnávací paměti s pevnou velikostí jen pro čtení jsou povolena pouze v následujících kontextech:
- Pro člena instance, v konstruktorech instancí nebo iniciačním členu typu, který obsahuje deklaraci člena; pro statického člena, ve statickém konstruktoru typu, který obsahuje deklaraci člena. Jedná se také o jediné kontexty, ve kterých je platné předat prvek pevného vyrovnávacího pole jen pro čtení jako parametr
out
neboref
.
Pokus přiřadit hodnotu prvku vyrovnávací paměti s pevnou velikostí jen pro čtení nebo jej předat jako parametr out
nebo ref
v jakémkoli jiném kontextu je chyba při kompilaci.
Toho dosáhnete následujícím způsobem.
Přístup člena pro vyrovnávací paměť s pevnou velikostí jen pro čtení se vyhodnotí a klasifikuje takto:
- V přístupu člena formuláře
E.I
, pokudE
je typu struktury aE
je klasifikována jako hodnota, lzeE.I
použít pouze jako primary_no_array_creation_expressionelementu, který s indexem typuSystem.Index
nebo typu implicitně převést na int. Výsledkem přístupu elementu je prvek člena s pevnou velikostí na zadané pozici klasifikovaný jako hodnota. - Pokud dojde k přístupu v kontextu, kde jsou povolena přímá přiřazení elementu vyrovnávací paměti s pevnou velikostí jen pro čtení, výsledek výrazu je klasifikován jako hodnota typu
System.Span<S>
, kde S je typ prvku vyrovnávací paměti s pevnou velikostí. Hodnotu lze použít pro přístup k prvkům člena. - V opačném případě je výraz klasifikován jako hodnota typu
System.ReadOnlySpan<S>
, kde S je typ prvku vyrovnávací paměti s pevnou velikostí. Hodnotu lze použít pro přístup k prvkům člena.
Kontrola určitého přiřazení
Vyrovnávací paměti s pevnou velikostí nejsou předmětem kontroly určitého přiřazení a členové těchto vyrovnávacích pamětí jsou ignorováni pro účely kontroly určitého přiřazení proměnných typu struktura.
Pokud je člen vyrovnávací paměti s pevnou velikostí statický, nebo pokud je proměnná struktury, která obsahuje pevnou velikost vyrovnávací paměti, statická proměnná, proměnná instance třídy nebo prvek pole, prvky vyrovnávací paměti s pevnou velikostí se automaticky inicializují na své výchozí hodnoty. Ve všech ostatních případech není definován počáteční obsah vyrovnávací paměti s pevnou velikostí.
Metadata
Generování metadat a generování kódu
Kompilátor se bude spoléhat na nedávno přidané System.Runtime.CompilerServices.InlineArrayAttribute
pro kódování metadat.
Pevné vyrovnávací paměti, například následující pseudokód:
// Not valid C#
public partial class C
{
public int buffer1[10];
public readonly int buffer2[10];
}
budou generovány jako pole speciálně označeného typu struktury.
Ekvivalentní kód jazyka C# bude:
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);
}
}
Skutečné zásady vytváření názvů pro typ a jeho členy budou teprve určeny. Architektura bude pravděpodobně zahrnovat sadu předdefinovaných typů vyrovnávací paměti, které pokrývají omezenou sadu velikostí vyrovnávací paměti. Pokud předdefinovaný typ neexistuje, kompilátor ho syntetizuje v modulu, který se sestavuje. Názvy vygenerovaných typů budou srozumitelné, aby podporovaly používání v jiných jazycích.
Kód vygenerovaný pro přístup, například:
public partial class C
{
void M1(int val)
{
buffer1[1] = val;
}
int M2()
{
return buffer2[1];
}
}
bude odpovídat:
public partial class C
{
void M1(int val)
{
buffer.AsSpan()[1] = val;
}
int M2()
{
return buffer2.AsReadOnlySpan()[1];
}
}
Import metadat
Když kompilátor importuje deklaraci pole typu T a všechny následující podmínky jsou splněny:
-
T je typ struktury zdobený atributem
InlineArray
a - První pole instance deklarované v T má typ Fa
- V Tje
public System.Span<F> AsSpan()
a - V Tje
public readonly System.ReadOnlySpan<T> AsReadOnlySpan()
nebopublic System.ReadOnlySpan<T> AsReadOnlySpan()
.
Pole se bude chovat jako buffer jazyka C# s pevnou velikostí s typem prvku F. Jinak se pole bude chovat jako běžné pole typu T.
Metoda nebo soubor vlastností podobný metodickému přístupu v jazyce
Jedním z myšlenek je zacházet s těmito členy spíše jako se skupinami metod, protože nejsou automaticky hodnotou v sobě a samy o sobě, ale v případě potřeby se dají vytvořit do jedné. Takto by to fungovalo:
- Přístupy k bezpečné vyrovnávací paměti s pevnou velikostí mají vlastní klasifikaci (stejně jako skupiny metod a lambda)
- Je možné je indexovat přímo jako operaci jazyka (ne prostřednictvím typů span) a vytvořit proměnnou (která je jen pro čtení, pokud je vyrovnávací paměť v kontextu jen pro čtení, stejně jako pole struktury).
- Mají implicitní převody z výrazu na
Span<T>
aReadOnlySpan<T>
, ale použití předchozího výrazu je chyba, pokud jsou v kontextu jen pro čtení. - Jejich přirozený typ je
ReadOnlySpan<T>
, a to, co přispívají, pokud se účastní odvození typu (např. var, best-common-type nebo obecný typ)
C/C++ buffery s pevnou velikostí
C/C++ má odlišný koncept vyrovnávacích pamětí s pevnou velikostí. Existuje například pojem "vyrovnávací paměti s pevnou velikostí a nulovou délkou", který se často používá jako způsob, jak indikovat, že data jsou "proměnlivé délky". Cílem tohoto návrhu není být schopna s tím spolupracovat.
Schůzky LDM
- 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