Udostępnij za pośrednictwem


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 structtypu , 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, doublei 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.

readonlyZ 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 lub System.Object.
  • Jako argument typu.
  • Jako typ elementu krotki.
  • Metoda asynchronina.
  • Iterację.
  • Nie ma konwersji typu ref struct na typ object lub typu System.ValueType.
  • Typ ref struct nie jest deklarowany w celu zaimplementowania żadnego interfejsu.
  • Metoda wystąpienia zadeklarowana w elem lub inSystem.ValueType, object ale nie przesłonięta w typieref struct, nie jest wywoływana z odbiornikiem tego ref 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 deklaruje async metod wystąpienia ani nie używa yield return instrukcji or yield break w metodzie wystąpienia, ponieważ niejawny this 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 Bklasy . A struct Xbezpoś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ład

struct A { B b; }
struct B { C c; }
struct C { A a; }

jest błędem, ponieważ każdy z typów A, Bi C 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 elementu a w celu b utworzenia kopii wartości i b w związku z tym nie ma to wpływu na przypisanie do a.x. Zamiast Point tego został zadeklarowany jako klasa, dane wyjściowe byłyby 100 spowodowane tym, że a i b 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 protectedlub 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.ValueTypeklasy .

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 nullwartość .

Przykład: odwołanie do struktury zadeklarowanej Point powyżej, przykład

Point[] a = new Point[100];

inicjuje każdy Point element w tablicy do wartości wygenerowanej przez ustawienie x pól i y 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 zmienna KeyValuePair podlega inicjowaniu wartości domyślnej, key pola i value 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 polu struct. 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. , GetHashCodelub 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 zmiennej x. Nie jest to równoważne drugiemu wywołaniu metody Increment, które modyfikuje wartość w polu kopii x. 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ą i x 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 i Y) do momentu, aż wszystkie pola konstruowanej struktury zostały zdecydowanie przypisane. Należy jednak pamiętać, że w przypadku Point 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 S2S1jest 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 e1zwrotnej e1 kontekst bezpiecznego kontekstu jest kontekstem wywołującym.
  • W przypadku przypisania e1 = e2 bezpieczny kontekst e2 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ż S1ref 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 kontekst foreach 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 od ref pola. Reguły bezpieczeństwa opisane w tym dokumencie zależą od ref pól, które nie są prawidłową konstrukcją w języku C# lub .NET. notatka końcowa