16, struktury
16.1 Ogólne
Struktury są podobne do klas, w których reprezentują struktury danych, które mogą zawierać elementy członkowskie danych i składowe funkcji. Jednak w przeciwieństwie do klas struktury są typami wartości i nie wymagają alokacji sterty. Zmienna struct
typu zawiera bezpośrednio dane struct
typu , natomiast zmienna typu klasy zawiera odwołanie do danych, które jest nazywane obiektem.
Uwaga: struktury są szczególnie przydatne w przypadku małych struktur danych, które mają semantyka wartości. Liczby złożone, punkty w układzie współrzędnych lub pary klucz-wartość w słowniku są dobrymi przykładami struktur. Kluczem do tych struktur danych jest to, że mają kilka elementów członkowskich danych, że nie wymagają użycia semantyki dziedziczenia lub odwołań, a raczej można je wygodnie zaimplementować przy użyciu semantyki wartości, gdzie przypisanie kopiuje wartość zamiast odwołania. notatka końcowa
Zgodnie z opisem w §8.3.5 proste typy udostępniane przez język C#, takie jak int
, double
i bool
, są w rzeczywistości wszystkimi typami struktur.
Deklaracje struktury 16.2
16.2.1 Ogólne
Struct_declaration to type_declaration (§14.7), która deklaruje nową strukturę:
struct_declaration
: attributes? struct_modifier* 'ref'? 'partial'? 'struct'
identifier type_parameter_list? struct_interfaces?
type_parameter_constraints_clause* struct_body ';'?
;
Struct_declaration składa się z opcjonalnego zestawu atrybutów (§22), a następnie opcjonalnego zestawu struct_modifiers (§16.2.2), a następnie opcjonalnego ref
modyfikatora (§16.2.3), a następnie opcjonalnego modyfikatora częściowego (§15.2.7), a następnie słowa kluczowego struct
i identyfikatora, który nazywa strukturę, oraz opcjonalna specyfikacja type_parameter_list (§15.2.3)), a następnie opcjonalną specyfikację struct_interfaces (§16.2.5), a następnie opcjonalną specyfikację type_parameter_constraints-klauzul (§15.2.5), a następnie struct_body (§16.2.6), po której następuje średnik.
Deklaracja struktury nie dostarcza type_parameter_constraints_clauses , chyba że dostarcza również type_parameter_list.
Deklaracja struktury dostarczająca type_parameter_list jest deklaracją struktury ogólnej. Ponadto każda struktura zagnieżdżona wewnątrz deklaracji klasy ogólnej lub deklaracji struktury ogólnej jest samą deklaracją struktury ogólnej, ponieważ argumenty typu dla typu zawierającego należy podać w celu utworzenia skonstruowanego typu (§8.4).
Deklaracja struktury zawierająca ref
słowo kluczowe nie ma struct_interfaces części.
Modyfikatory struktury 16.2.2
Struct_declaration może opcjonalnie zawierać sekwencję struct_modifier:
struct_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'readonly'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§23.2) jest dostępny tylko w niebezpiecznym kodzie (§23).
Jest to błąd czasu kompilacji dla tego samego modyfikatora, który pojawia się wiele razy w deklaracji struktury.
readonly
Z wyjątkiem , modyfikatory deklaracji struktury mają takie samo znaczenie jak te deklaracji klasy (§15.2.2).
Modyfikator readonly
wskazuje, że struct_declaration deklaruje typ, którego wystąpienia są niezmienne.
Struktura readonly ma następujące ograniczenia:
- Każde z pól wystąpienia jest również deklarowane
readonly
. - Żadna z jego właściwości wystąpienia nie ma set_accessor_declaration (§15.7.3).
- Nie deklaruje żadnych zdarzeń podobnych do pól (§15.8.2).
Gdy wystąpienie struktury readonly jest przekazywane do metody, jest this
traktowane jak argument wejściowy/parametr, który nie zezwala na dostęp do zapisu do dowolnych pól wystąpienia (z wyjątkiem konstruktorów).
16.2.3 Modyfikator ref
Modyfikator ref
wskazuje, że struct_declaration deklaruje typ, którego wystąpienia są przydzielane na stosie wykonywania. Te typy są nazywane typami struktury ref. Modyfikator ref
deklaruje, że wystąpienia mogą zawierać pola podobne do ref i nie są kopiowane z kontekstu bezpiecznego (§16.4.12). Reguły określania bezpiecznego kontekstu struktury ref opisano w §16.4.12.
Jest to błąd czasu kompilacji, jeśli typ struktury ref jest używany w dowolnym z następujących kontekstów:
- Jako typ elementu tablicy.
- Jako zadeklarowany typ pola klasy lub struktury, która nie ma
ref
modyfikatora. - Jest w pudełku do
System.ValueType
lubSystem.Object
. - Jako argument typu.
- Jako typ elementu krotki.
- Metoda asynchronina.
- Iterację.
- Nie ma konwersji typu
ref struct
na typobject
lub typuSystem.ValueType
. - Typ
ref struct
nie jest deklarowany w celu zaimplementowania żadnego interfejsu. - Metoda wystąpienia zadeklarowana w elem lub in
System.ValueType
,object
ale nie przesłonięta w typieref struct
, nie jest wywoływana z odbiornikiem tegoref struct
typu. - Metoda
ref struct
wystąpienia typu nie jest przechwytywana przez konwersję grupy metod na typ delegata. - Struktura ref nie jest przechwytywana przez wyrażenie lambda ani funkcję lokalną.
Uwaga: Metoda wystąpienia nie
ref struct
deklarujeasync
metod wystąpienia ani nie używayield return
instrukcji oryield break
w metodzie wystąpienia, ponieważ niejawnythis
parametr nie może być używany w tych kontekstach. notatka końcowa
Te ograniczenia zapewniają, że zmienna typu nie odnosi się do pamięci stosu ref struct
, która nie jest już prawidłowa, ani do zmiennych, które nie są już prawidłowe.
16.2.4 Modyfikator częściowy
Modyfikator partial
wskazuje, że ten struct_declaration jest deklaracją typu częściowego. Wiele deklaracji częściowej struktury o tej samej nazwie w otaczającej przestrzeni nazw lub deklaracji typu łączą się w celu utworzenia jednej deklaracji struktury, zgodnie z regułami określonymi w §15.2.7.
Interfejsy struktury 16.2.5
Deklaracja struktury może zawierać specyfikację struct_interfaces , w tym przypadku struktura jest określana bezpośrednio do implementowania danych typów interfejsów. Dla skonstruowanego typu struktury, w tym typu zagnieżdżonego zadeklarowanego w deklaracji typu ogólnego (§15.3.9.7), każdy zaimplementowany typ interfejsu jest uzyskiwany przez podstawianie, dla każdego type_parameter w danym interfejsie, odpowiadających type_argument typu skonstruowanego.
struct_interfaces
: ':' interface_type_list
;
Obsługa interfejsów w wielu częściach części częściowej deklaracji struktury (§15.2.7) jest omówiona dalej w §15.2.4.3.
Implementacje interfejsu zostały omówione bardziej szczegółowo w §18.6.
Treść struktury 16.2.6
Struct_body struktury definiuje elementy członkowskie struktury.
struct_body
: '{' struct_member_declaration* '}'
;
16.3, składowe struktury
Elementy członkowskie struktury składają się z elementów członkowskich wprowadzonych przez jego struct_member_declarations, a elementy członkowskie dziedziczone z typu System.ValueType
.
struct_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| static_constructor_declaration
| type_declaration
| fixed_size_buffer_declaration // unsafe code support
;
fixed_size_buffer_declaration (§23.8.2) jest dostępny tylko w niebezpiecznym kodzie (§23).
Uwaga: wszystkie rodzaje class_member_declarationz wyjątkiem finalizer_declaration są również struct_member_declarations. notatka końcowa
Z wyjątkiem różnic odnotowanych w §16.4, opisy składowych klas podanych w §15.3 do §15.12 mają zastosowanie również do składowych struktury.
16.4 Różnice klas i struktur
16.4.1 Ogólne
Struktury różnią się od klas na kilka ważnych sposobów:
- Struktury są typami wartości (§16.4.2).
- Wszystkie typy struktur niejawnie dziedziczą z klasy
System.ValueType
(§16.4.3). - Przypisanie do zmiennej typu struktury tworzy kopię przypisanej wartości (§16.4.4).
- Wartość domyślna struktury to wartość wygenerowana przez ustawienie wszystkich pól na wartość domyślną (§16.4.5).
- Operacje boksowania i rozpatrunia są używane do konwertowania między typem struktury a niektórymi typami referencyjnymi (§16.4.6).
- Znaczenie
this
elementu różni się w obrębie składowych struktury (§16.4.7). - Deklaracje pól wystąpienia dla struktury nie mogą zawierać inicjatorów zmiennych (§16.4.8).
- Struktura nie może zadeklarować konstruktora wystąpienia bez parametrów (§16.4.9).
- Struktura nie może zadeklarować finalizatora.
16.4.2 Semantyka wartości
Struktury są typami wartości (§8.3) i mówi się, że mają semantyka wartości. Klasy, z drugiej strony, są typami referencyjnymi (§8.2) i mówi się, że mają semantyka referencyjna.
Zmienna typu struktury zawiera bezpośrednio dane struktury, natomiast zmienna typu klasy zawiera odwołanie do obiektu zawierającego dane. Gdy struktura B
zawiera pole wystąpienia typu A
i A
jest typem struktury, jest to błąd czasu kompilacji, który A
może zależeć od B
typu lub typu skonstruowanego z B
klasy . A struct X
bezpośrednio zależy od strukturyY
, jeśli X
zawiera pole wystąpienia typu Y
. Biorąc pod uwagę tę definicję, kompletny zestaw struktur, od których zależy struktura, to przejściowe zamknięcie obiektu bezpośrednio zależy od relacji.
Przykład:
struct Node { int data; Node next; // error, Node directly depends on itself }
jest błędem, ponieważ
Node
zawiera pole wystąpienia własnego typu. Inny przykładstruct A { B b; } struct B { C c; } struct C { A a; }
jest błędem, ponieważ każdy z typów
A
,B
iC
zależy od siebie nawzajem.przykład końcowy
W przypadku klas możliwe jest, aby dwie zmienne odwoły się do tego samego obiektu, a tym samym możliwe, aby operacje na jednej zmiennej wpływały na obiekt, do którego odwołuje się druga zmienna. W przypadku struktur zmienne mają własną kopię danych (z wyjątkiem parametrów referencyjnych) i nie jest możliwe, aby operacje na jednym z nich miały wpływ na drugą. Ponadto, z wyjątkiem sytuacji, gdy jawnie dopuszczana wartość null (§8.3.12), nie jest możliwe, aby wartości typu struktury to null
.
Uwaga: jeśli struktura zawiera pole typu odwołania, zawartość obiektu, do których odwołuje się odwołanie, może zostać zmieniona przez inne operacje. Jednak wartość samego pola, tj. obiekt, do którego się odwołuje, nie może zostać zmieniona przez mutację innej wartości struktury. notatka końcowa
Przykład: biorąc pod uwagę następujące kwestie
struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } class A { static void Main() { Point a = new Point(10, 10); Point b = a; a.x = 100; Console.WriteLine(b.x); } }
dane wyjściowe to
10
. Przypisanie elementua
w celub
utworzenia kopii wartości ib
w związku z tym nie ma to wpływu na przypisanie doa.x
. ZamiastPoint
tego został zadeklarowany jako klasa, dane wyjściowe byłyby100
spowodowane tym, żea
ib
odwoływały się do tego samego obiektu.przykład końcowy
Dziedziczenie 16.4.3
Wszystkie typy struktur niejawnie dziedziczą z klasy System.ValueType
, która z kolei dziedziczy z klasy object
. Deklaracja struktury może określać listę zaimplementowanych interfejsów, ale nie jest możliwe, aby deklaracja struktury określała klasę bazową.
Typy struktur nigdy nie są abstrakcyjne i zawsze są niejawnie zapieczętowane. Modyfikatory abstract
i sealed
nie są zatem dozwolone w deklaracji struktury.
Ponieważ dziedziczenie nie jest obsługiwane dla struktur, deklarowane ułatwienia dostępu elementu członkowskiego struktury nie mogą być protected
, private protected
lub protected internal
.
Elementy członkowskie funkcji w strukturze nie mogą być abstrakcyjne ani wirtualne, a override
modyfikator może zastąpić tylko metody dziedziczone z System.ValueType
klasy .
Przypisanie 16.4.4
Przypisanie do zmiennej typu struktury tworzy kopię przypisanej wartości. Różni się to od przypisania do zmiennej typu klasy, która kopiuje odwołanie, ale nie obiekt zidentyfikowany przez odwołanie.
Podobnie jak w przypadku przypisania, gdy struktura jest przekazywana jako parametr wartości lub zwracana w wyniku elementu członkowskiego funkcji, tworzona jest kopia struktury. Struktura może zostać przekazana przez odwołanie do elementu członkowskiego funkcji przy użyciu parametru by-reference.
Gdy właściwość lub indeksator struktury jest celem przypisania, wyrażenie wystąpienia skojarzone z właściwością lub indeksatorem jest klasyfikowane jako zmienna. Jeśli wyrażenie wystąpienia jest klasyfikowane jako wartość, wystąpi błąd czasu kompilacji. Opisano to szczegółowo w §12.21.2.
16.4.5 Wartości domyślne
Zgodnie z opisem w §9.3, kilka rodzajów zmiennych jest automatycznie inicjowanych do ich wartości domyślnej podczas ich tworzenia. W przypadku zmiennych typów klas i innych typów odwołań ta wartość domyślna to null
. Jednak ponieważ struktury są typami wartości, których nie może być null
, wartość domyślna struktury to wartość wygenerowana przez ustawienie wszystkich pól typu wartości na wartość domyślną, a wszystkie pola typu odwołania na null
wartość .
Przykład: odwołanie do struktury zadeklarowanej
Point
powyżej, przykładPoint[] a = new Point[100];
inicjuje każdy
Point
element w tablicy do wartości wygenerowanej przez ustawieniex
pól iy
na zero.przykład końcowy
Wartość domyślna struktury odpowiada wartości zwracanej przez domyślny konstruktor struktury (§8.3.3). W przeciwieństwie do klasy, struktura nie może zadeklarować konstruktora wystąpienia bez parametrów. Zamiast tego każda struktura niejawnie ma konstruktor wystąpienia bez parametrów, który zawsze zwraca wartość, która wynika z ustawienia wszystkich pól na wartości domyślne.
Uwaga: Struktury powinny być zaprojektowane tak, aby uwzględnić domyślny stan inicjowania prawidłowym stanem. W przykładzie
struct KeyValuePair { string key; string value; public KeyValuePair(string key, string value) { if (key == null || value == null) { throw new ArgumentException(); } this.key = key; this.value = value; } }
konstruktor wystąpienia zdefiniowanego przez użytkownika chroni przed wartościami
null
tylko wtedy, gdy jest jawnie wywoływany. W przypadkach, gdy zmiennaKeyValuePair
podlega inicjowaniu wartości domyślnej,key
pola ivalue
będąnull
, a struktura powinna być przygotowana do obsługi tego stanu.notatka końcowa
16.4.6 Boxing i rozpakowywanie
Wartość typu klasy można przekonwertować na typ object
lub typ interfejsu implementowany przez klasę po prostu traktując odwołanie jako inny typ w czasie kompilacji. Podobnie wartość typu object
lub wartości typu interfejsu można przekonwertować z powrotem na typ klasy bez zmiany odwołania (ale oczywiście w tym przypadku jest wymagane sprawdzanie typu czasu wykonywania).
Ponieważ struktury nie są typami referencyjnymi, te operacje są implementowane inaczej dla typów struktur. Gdy wartość typu struktury jest konwertowana na niektóre typy odwołań (zgodnie z definicją w §10.2.9), odbywa się operacja boksowania. Podobnie, gdy wartość niektórych typów odwołań (zgodnie z definicją w §10.3.7) jest konwertowana z powrotem na typ struktury, odbywa się operacja rozpatrunia. Kluczową różnicą między tymi samymi operacjami w typach klas jest to, że boxing i rozpatrujący kopiuje wartość struktury do lub z gotowego wystąpienia.
Uwaga: W związku z tym po operacji boksowania lub rozpakowania zmiany wprowadzone w rozplekanym
struct
polu nie są odzwierciedlane w polustruct
. notatka końcowa
Aby uzyskać więcej informacji na temat boksu i rozpakowania, zobacz §10.2.9 i §10.3.7.
16.4.7 Znaczenie tego
Znaczenie this
struktury różni się od znaczenia this
klasy, zgodnie z opisem w §12.8.14. Gdy typ struktury zastępuje metodę wirtualną dziedziczącą z System.ValueType
metody (np Equals
. , GetHashCode
lub ToString
), wywołanie metody wirtualnej za pośrednictwem wystąpienia typu struktury nie powoduje wystąpienia typu struktury. Dotyczy to nawet sytuacji, gdy struktura jest używana jako parametr typu, a wywołanie odbywa się za pośrednictwem wystąpienia typu parametru typu.
Przykład:
struct Counter { int value; public override string ToString() { value++; return value.ToString(); } } class Program { static void Test<T>() where T : new() { T x = new T(); Console.WriteLine(x.ToString()); Console.WriteLine(x.ToString()); Console.WriteLine(x.ToString()); } static void Main() => Test<Counter>(); }
Dane wyjściowe programu to:
1 2 3
Chociaż jest to zły styl dla
ToString
skutków ubocznych, w przykładzie pokazano, że nie wystąpiły żadne bokse dla trzech wywołańx.ToString()
.przykład końcowy
Podobnie, boxing nigdy niejawnie nie występuje podczas uzyskiwania dostępu do elementu członkowskiego w parametrze typu ograniczonego, gdy element członkowski jest implementowany w typie wartości. Załóżmy na przykład, że interfejs ICounter
zawiera metodę Increment
, która może służyć do modyfikowania wartości. Jeśli ICounter
jest używana jako ograniczenie, implementacja Increment
metody jest wywoływana z odwołaniem do zmiennej, która Increment
została wywołana, nigdy nie jest kopią w polu.
Przykład:
interface ICounter { void Increment(); } struct Counter : ICounter { int value; public override string ToString() => value.ToString(); void ICounter.Increment() => value++; } class Program { static void Test<T>() where T : ICounter, new() { T x = new T(); Console.WriteLine(x); x.Increment(); // Modify x Console.WriteLine(x); ((ICounter)x).Increment(); // Modify boxed copy of x Console.WriteLine(x); } static void Main() => Test<Counter>(); }
Pierwsze wywołanie modyfikuje
Increment
wartość w zmiennejx
. Nie jest to równoważne drugiemu wywołaniu metodyIncrement
, które modyfikuje wartość w polu kopiix
. W związku z tym dane wyjściowe programu to:0 1 1
przykład końcowy
16.4.8 Inicjatory pól
Zgodnie z opisem w §16.4.5 wartość domyślna struktury składa się z wartości, która wynika z ustawienia wszystkich pól typu wartości na wartość domyślną i wszystkich pól typu odwołania do null
. Z tego powodu struktura nie zezwala deklaracjom pól wystąpienia na dołączanie inicjatorów zmiennych. To ograniczenie dotyczy tylko pól wystąpień. Pola statyczne struktury mogą zawierać inicjatory zmiennych.
Przykład: następujące
struct Point { public int x = 1; // Error, initializer not permitted public int y = 1; // Error, initializer not permitted }
występuje błąd, ponieważ deklaracje pól wystąpienia zawierają inicjatory zmiennych.
przykład końcowy
Konstruktory 16.4.9
W przeciwieństwie do klasy, struktura nie może zadeklarować konstruktora wystąpienia bez parametrów. Zamiast tego każda struktura niejawnie ma konstruktor wystąpienia bez parametrów, który zawsze zwraca wartość wynikającą z ustawienia wszystkich pól typu wartości na wartość domyślną i wszystkich pól typu odwołania do null
(§8.3.3). Struktura może deklarować konstruktory wystąpień o parametrach.
Przykład: biorąc pod uwagę następujące kwestie
struct Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } class A { static void Main() { Point p1 = new Point(); Point p2 = new Point(0, 0); } }
instrukcje tworzą element
Point
z wartością ix
y
inicjowane do zera.przykład końcowy
Konstruktor wystąpienia struktury nie może zawierać inicjatora konstruktora formularza base(
argument_list)
, gdzie argument_list jest opcjonalna.
Parametr this
konstruktora wystąpienia struktury odpowiada parametrowi wyjściowemu typu struktury. W związku z tym this
należy na pewno przypisać (§9.4) w każdej lokalizacji, w której konstruktor zwraca. Podobnie nie można go odczytać (nawet niejawnie) w treści konstruktora przed zdecydowanie przypisanym.
Jeśli konstruktor wystąpienia struktury określa inicjator konstruktora, inicjator ten jest uważany za określone przypisanie do tego, który występuje przed treścią konstruktora. W związku z tym sama treść nie ma wymagań inicjalizacji.
Przykład: Rozważmy implementację konstruktora wystąpienia poniżej:
struct Point { int x, y; public int X { set { x = value; } } public int Y { set { y = value; } } public Point(int x, int y) { X = x; // error, this is not yet definitely assigned Y = y; // error, this is not yet definitely assigned } }
Nie można wywołać elementu członkowskiego funkcji wystąpienia (w tym metod dostępu zestawu dla właściwości
X
iY
) do momentu, aż wszystkie pola konstruowanej struktury zostały zdecydowanie przypisane. Należy jednak pamiętać, że w przypadkuPoint
klasy zamiast struktury implementacja konstruktora wystąpienia byłaby dozwolona. Istnieje jeden wyjątek od tego i obejmuje on automatycznie zaimplementowane właściwości (§15.7.4). Określone reguły przypisania (§12.21.2) w szczególności wykluczają przypisanie do właściwości automatycznej typu struktury w konstruktorze wystąpienia tego typu struktury: takie przypisanie jest uważane za określone przypisanie ukrytego pola tworzenia kopii zapasowej właściwości automatycznej. W związku z tym dozwolone są następujące elementy:struct Point { public int X { get; set; } public int Y { get; set; } public Point(int x, int y) { X = x; // allowed, definitely assigns backing field Y = y; // allowed, definitely assigns backing field } }
przykład końcowy]
16.4.10 Konstruktory statyczne
Konstruktory statyczne dla struktur są zgodne z większością tych samych reguł co w przypadku klas. Wykonanie konstruktora statycznego dla typu struktury jest wyzwalane przez pierwsze z następujących zdarzeń, które mają wystąpić w domenie aplikacji:
- Odwołano się do statycznego elementu członkowskiego typu struktury.
- Wywoływany jest jawnie zadeklarowany konstruktor typu struktury.
Uwaga: Tworzenie wartości domyślnych (§16.4.5) typów struktur nie powoduje wyzwolenia konstruktora statycznego. (Przykładem jest początkowa wartość elementów w tablicy). notatka końcowa
16.4.11 Automatycznie zaimplementowane właściwości
Automatycznie zaimplementowane właściwości (§15.7.4) używają ukrytych pól pomocniczych, które są dostępne tylko dla metod dostępu do właściwości.
Uwaga: To ograniczenie dostępu oznacza, że konstruktory w strukturach zawierających automatycznie zaimplementowane właściwości często potrzebują jawnego inicjatora konstruktora, w którym nie będą one w inny sposób potrzebne, aby spełnić wymaganie, aby wszystkie pola były zdecydowanie przypisane przed wywołaniem dowolnego elementu członkowskiego funkcji lub zwraca konstruktor. notatka końcowa
16.4.12 Bezpieczne ograniczenie kontekstu
16.4.12.1 Ogólne
W czasie kompilacji każde wyrażenie jest skojarzone z kontekstem, w którym to wystąpienie i wszystkie jego pola mogą być bezpiecznie dostępne, jego bezpieczny kontekst. Bezpieczny kontekst to kontekst, który otacza wyrażenie, które jest bezpieczne dla wartości, do której należy uciec.
Dowolne wyrażenie, którego typ czasu kompilacji nie jest strukturą ref, ma bezpieczny kontekst kontekstu wywołującego-kontekstu.
Wyrażenie default
dla dowolnego typu ma bezpieczny kontekst kontekstu wywołującego.
W przypadku dowolnego wyrażenia innego niż domyślne, którego typem czasu kompilacji jest struktura ref, ma bezpieczny kontekst zdefiniowany w poniższych sekcjach.
Rekordy bezpiecznego kontekstu, do których można skopiować wartość. Przypisanie z wyrażenia E1
z bezpiecznym kontekstem do wyrażenia E2
z bezpiecznym kontekstem S2
S1
jest błędem, jeśli S2
jest szerszym kontekstem niż S1
.
Istnieją trzy różne wartości bezpiecznego kontekstu, takie same jak wartości kontekstu bezpiecznego ref zdefiniowane dla zmiennych referencyjnych (§9.7.2): blok-deklaracji, składowa funkcji i kontekst-wywołujący. Bezpieczny kontekst wyrażenia ogranicza jego użycie w następujący sposób:
- W przypadku instrukcji
return e1
zwrotneje1
kontekst bezpiecznego kontekstu jest kontekstem wywołującym. - W przypadku przypisania
e1 = e2
bezpieczny kontekste2
musi być co najmniej tak szeroki, jak kontekst bezpieczny .e1
W przypadku wywołania metody, jeśli istnieje ref
argument typu lub out
(w tym odbiornik, chyba że typem jest readonly
), z bezpiecznym kontekstem S1
, a następnie żaden argument (w tym odbiornik) może mieć węższy kontekst niż S1
ref struct
.
16.4.12.2 Kontekst bezpieczny parametru
Parametr typu struktury ref, w tym this
parametr metody wystąpienia, ma bezpieczny kontekst kontekstu wywołującego kontekstu.
16.4.12.3 Kontekst bezpieczny zmiennej lokalnej
Lokalna zmienna typu struktury ref ma bezpieczny kontekst w następujący sposób:
- Jeśli zmienna jest zmienną
foreach
iteracji pętli, bezpieczny kontekst zmiennej jest taki sam jak bezpieczny kontekstforeach
wyrażenia pętli. - W przeciwnym razie, jeśli deklaracja zmiennej ma inicjator, bezpieczny kontekst zmiennej jest taki sam jak bezpieczny kontekst tego inicjatora.
- W przeciwnym razie zmienna jest niezainicjowana w punkcie deklaracji i ma bezpieczny kontekst kontekstu wywołującego.
Kontekst bezpieczny pola 16.4.12.4
Odwołanie do pola e.F
, w którym typ F
struktury jest typem struktury ref, ma bezpieczny kontekst, który jest taki sam jak kontekst bezpieczny .e
Operatory 16.4.12.5
Zastosowanie operatora zdefiniowanego przez użytkownika jest traktowane jako wywołanie metody (§16.4.12.6).
Dla operatora, który zwraca wartość, taką jak e1 + e2
lub c ? e1 : e2
, bezpieczny kontekst wyniku jest najwęższym kontekstem wśród bezpiecznych kontekstów operandów operatora. W konsekwencji dla operatora jednoargumentowego, który daje wartość, taką jak +e
, bezpieczny kontekst wyniku jest bezpiecznym kontekstem operandu.
Uwaga: pierwszy operand operatora warunkowego to
bool
, więc jego kontekst bezpieczny jest kontekstem wywołującym. Wynika z tego, że wynikowy kontekst bezpieczny jest najwęższym kontekstem drugiego i trzeciego operandu. notatka końcowa
16.4.12.6, wywołanie metody i właściwości
Wartość wynikająca z wywołania e1.M(e2, ...)
metody lub wywołania e.P
właściwości ma bezpieczny kontekst najmniejszych z następujących kontekstów:
- kontekst wywołujący.
- Bezpieczny kontekst wszystkich wyrażeń argumentów (w tym odbiornika).
Wywołanie właściwości ( get
lub set
) jest traktowane jako wywołanie metody bazowej przez powyższe reguły.
16.4.12.7 stackalloc
Wynik wyrażenia stackalloc ma bezpieczny kontekst elementu członkowskiego funkcji.
Wywołania konstruktora 16.4.12.8
Wyrażenie new
, które wywołuje konstruktor, przestrzega tych samych reguł co wywołanie metody, które jest uważane za zwracanie tworzonego typu.
Ponadto bezpieczny kontekst jest najmniejszym kontekstem bezpiecznym wszystkich argumentów i operandów wszystkich wyrażeń inicjatora obiektów, rekursywnie, jeśli istnieje jakikolwiek inicjator.
Uwaga: Te reguły nie mają
Span<T>
konstruktora następującego formularza:public Span<T>(ref T p)
Taki konstruktor sprawia, że wystąpienia
Span<T>
używane jako pola nie do odróżnienia odref
pola. Reguły bezpieczeństwa opisane w tym dokumencie zależą odref
pól, które nie są prawidłową konstrukcją w języku C# lub .NET. notatka końcowa
ECMA C# draft specification