16 struktur
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ściowymi i nie wymagają alokacji sterty. Zmienne typu struct
bezpośrednio zawierają dane typu struct
, natomiast zmienne typu klasowego zawierają odwołanie do danych, co jest znane jako obiekt.
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ą niewiele członków danych, nie wymagają dziedziczenia ani semantyki odwołań, a 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 jest 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 ';'?
;
Struktura deklaracji () składa się z opcjonalnego zestawu atrybutów (§22), po którym może nastąpić opcjonalny zestaw modyfikatorów struktury (§16.2.2), opcjonalny modyfikator ref
(§16.2.3), opcjonalny modyfikator częściowy (§15.2.7), słowo kluczowe struct
oraz identyfikator , który określa nazwę struktury, a także opcjonalna specyfikacja listy parametrów typu (§15.2.3), opcjonalna specyfikacja interfejsów struktury (§16.2.5), opcjonalna specyfikacja klauzul ograniczeń parametrów typu (§15.2.5), po której następuje ciało struktury (§16.2.6), a całość może być zakończona średnikiem.
Deklaracja struktury nie dostarcza type_parameter_constraints_clauses, chyba że dostarcza również type_parameter_list.
Deklaracja struktury zawierająca type_parameter_list jest deklaracją struktury generycznej. 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 może mieć części struct_interfaces.
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 instancji powinno być również zadeklarowane
readonly
. - Żadna z właściwości jego instancji nie powinna mieć set_accessor_declaration (§15.7.3).
- Nie powinno deklarować żadnych zdarzeń przypominających pola (§15.8.2).
Gdy wystąpienie struktury readonly jest przekazywane do metody, jest ono this
traktowane jak argument wejściowy lub parametr, co uniemożliwia zapis do dowolnych pól wystąpienia (z wyjątkiem przypadku 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 ref struct. 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. - Bycie zapakowanym w
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
object
lub wSystem.ValueType
, ale nie przesłonięta w typieref struct
, nie powinna być wywoływana z odbiornikiem tegoref struct
typu. - Instancji metody typu
ref struct
nie należy przechwytywać przez konwersję grupy metod na typ delegata. - Struktura ref nie jest przechwytywana przez wyrażenie lambda ani funkcję lokalną.
Uwaga:
ref struct
nie może deklarować metod instancjiasync
ani używać instrukcjiyield return
lubyield break
w metodzie instancji, ponieważ niejawny parametrthis
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 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 zdefiniowanych przez struct_member_declaration oraz elementów dziedziczonych 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_declaration z wyjątkiem finalizer_declaration są jednocześnie struct_member_declaration. 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 rozpakowywania są używane do konwersji 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. Pl-PL: Gdy struktura B
zawiera pole instancji typu A
i A
jest typem struktury, jest to błąd czasu kompilacji, aby A
zależało od B
lub typu skonstruowanego z B
. Struktura X
bezpośrednio zależy odY
struktury, jeśli X
zawiera pole wystąpienia typu Y
. Biorąc pod uwagę tę definicję, pełny zbiór struktur, od których dana struktura zależy, to przejściowe domknięcie relacji bezpośrednio zależy od.
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 jest wartość null (§8.3.12), wartości dla typu struktury nie mogą być 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
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
. Przypisaniea
dob
powoduje utworzenie kopii wartości, i dlategob
nie jest przez to zmieniony przy przypisaniu doa.x
. Gdyby zamiast tego był zadeklarowany jako klasa, wynik byłby100
, ponieważa
ib
odwoływałyby 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
.
Członkowie funkcji w strukturze nie mogą być abstrakcyjni ani wirtualni, a modyfikator override
może służyć tylko do zastępowania metod dziedziczonych z System.ValueType
.
16.4.4 Przypisanie
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 typu wartości lub zwracana w wyniku 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 dostępem do właściwości lub indeksatora powinno być klasyfikowane jako zmienna. Jeśli wyrażenie wystąpienia zostanie sklasyfikowane jako wartość, pojawi się błąd podczas 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 element
Point
w tablicy, nadając mu wartość uzyskaną przez ustawienie pólx
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 instancji definiowany przez użytkownika chroni przed wartościami
null
tylko wtedy, gdy jest wywoływany w sposób jawny. W przypadkach, gdy zmiennaKeyValuePair
podlega inicjalizacji wartości domyślnej, polakey
ivalue
będąnull
, a struktura powinna być przygotowana do obsługi tego stanu.notatka końcowa
16.4.6 Opakowywanie 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ść typu interfejsu można przekonwertować z powrotem na typ klasy bez zmiany odwołania (ale oczywiście w tym przypadku wymagane jest sprawdzenie typu w czasie wykonywania).
Ponieważ struktury nie są typami referencyjnymi, te operacje są implementowane inaczej dla typów struktur. Gdy wartość typu struktury jest konwertowana na określone 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 wyodrębnienia. Kluczową różnicą między tymi samymi operacjami na typach klas jest to, że boxing i unboxing kopiują wartość struktury do lub z opakowanego wystąpienia.
Uwaga: W związku z tym po operacji boksowania lub rozpakowania zmiany wprowadzone w rozpakowanym
struct
nie są odzwierciedlane w zakapsułowanymstruct
. notatka końcowa
Aby uzyskać więcej informacji na temat boxingu i unboxingu, 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ą odziedziczoną z System.ValueType
(np. Equals
, GetHashCode
lub ToString
), wywołanie tej metody wirtualnej za pośrednictwem instancji typu struktury nie powoduje wystąpienia boksowania. Dotyczy to nawet sytuacji, gdy struktura jest używana jako parametr typu, a wywołanie odbywa się poprzez instancję parametru typowego.
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, aby
ToString
miał skutki uboczne, w przykładzie pokazano, że w przypadku trzech wywołańx.ToString()
nie wystąpiło żadne boksowanie.przykład końcowy
Podobnie, boxing nigdy nie występuje niejawnie podczas dostępu do elementu członkowskiego w parametrze typu ograniczonego, gdy element ten 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żywane jako ograniczenie, implementacja metody Increment
jest wywoływana z odniesieniem do zmiennej, do której wywołano Increment
, nigdy z zaboksowaną kopią.
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
Increment
modyfikuje wartość w zmiennejx
. Nie jest to równoważne drugiemu wywołaniu funkcjiIncrement
, które modyfikuje wartość w zamkniętej kopiix
. W związku z tym dane wyjściowe programu to:0 1 1
przykład końcowy
16.4.8 Inicjalizatory 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 instancji 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
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
zx
iy
zainicjowanymi do zera.przykład końcowy
Konstruktor wystąpienia struktury nie może zawierać inicjalizatora konstruktora w formie base(
argument_list)
, gdzie argument_list jest opcjonalny.
Parametr this
konstruktora wystąpienia struktury odpowiada parametrowi wyjściowemu typu struktury. W związku z tym this
musi być definitywnie przypisane (§9.4) w każdym miejscu, gdzie konstruktor zwraca. Podobnie nie można go odczytać (nawet niejawnie) w ciele konstruktora przed jednoznacznym przypisaniem.
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 samo ciało nie ma wymagań inicjalizacji.
Przykład: Rozważmy implementację konstruktora instancji 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ć funkcji członkowskiej instancji (w tym akcesorów set dla właściwości
X
iY
), dopóki wszystkie pola konstruowanej struktury nie zostaną jednoznacznie 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 statycznej składowej typu struktury.
- Jawnie zadeklarowany konstruktor typu struktury jest wywoływany.
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 struktur zawierających automatycznie zaimplementowane właściwości często wymagają jawnego inicjatora konstruktora, nawet jeśli nie byłoby to konieczne w innych okolicznościach, aby spełnić wymóg, że wszystkie pola muszą być jednoznacznie przypisane przed wywołaniem dowolnego elementu członkowskiego funkcji lub zakończeniem się konstruktora. 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 otaczający wyrażenie, do którego wartości mogą bezpiecznie przechodzić.
Dowolne wyrażenie, którego typ czasu kompilacji nie jest ref struct, ma bezpieczny kontekst wywołującego.
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 S1
do wyrażenia E2
z bezpiecznym kontekstem S2
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ącego. Bezpieczny kontekst wyrażenia ogranicza jego użycie w następujący sposób:
- W przypadku instrukcji return
return e1
, kontekstem bezpiecznym dlae1
powinien być kontekst wywołujący. - W przypadku przypisania
e1 = e2
bezpieczny kontekste2
musi być co najmniej tak szeroki, jak bezpieczny kontekste1
.
W przypadku wywołania metody, jeśli istnieje argument ref
lub out
typu ref struct
(w tym odbiornik, chyba że typem jest readonly
), z bezpiecznym kontekstem S1
, wówczas żaden argument (w tym odbiornik) nie może mieć węższego bezpiecznego kontekstu niż S1
.
16.4.12.2 Kontekst bezpieczny parametru
Parametr typu struktury ref, w tym parametr this
metody wystąpienia, ma bezpieczny kontekst wywołującego.
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 momencie deklaracji i ma bezpieczny kontekst w kontekście wywołującego.
Kontekst bezpieczeństwa pola 16.4.12.4
Odwołanie do pola e.F
, gdzie typ F
jest typem struktury ref, ma bezpieczny kontekst, który jest taki sam jak kontekst bezpieczny e
.
16.4.12.5 Operatory
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 bezpieczeństwa jest najwęższym kontekstem bezpieczeństwa 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 metody e1.M(e2, ...)
lub wywołania właściwości e.P
jest bezpieczna w kontekście najmniejszego z następujących kontekstów.
- kontekst dzwoniącego
- Kontekst bezpieczeństwa wszystkich wyrażeń argumentów (w tym odbiorcy).
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 członka funkcji.
Wywołania konstruktora 16.4.12.8
Wyrażenie new
, które wywołuje konstruktor, przestrzega tych samych reguł co wywołanie metody, uważane za zwracające typ, który jest tworzony.
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 opierają się na
Span<T>
, który nie ma konstruktora w następującej formie:public Span<T>(ref T p)
Taki konstruktor sprawia, że wystąpienia
Span<T>
, używane jako pola, są nie do odróżnienia od polaref
. 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