Udostępnij za pośrednictwem


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

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 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 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 object lub w System.ValueType, ale nie przesłonięta w typie ref struct, nie powinna być wywoływana z odbiornikiem tego ref 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 instancji async ani używać instrukcji yield return lub yield break w metodzie instancji, ponieważ niejawny parametr this 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 Xbezpoś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ł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 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. Przypisanie a do b powoduje utworzenie kopii wartości, i dlatego b nie jest przez to zmieniony przy przypisaniu do a.x. Gdyby zamiast tego był zadeklarowany jako klasa, wynik byłby 100, ponieważ a i b 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 protectedlub 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 nullwartość .

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

Point[] a = new Point[100];

Inicjuje każdy element Point w tablicy, nadając mu wartość uzyskaną przez ustawienie pól x 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 instancji definiowany przez użytkownika chroni przed wartościami null tylko wtedy, gdy jest wywoływany w sposób jawny. W przypadkach, gdy zmienna KeyValuePair podlega inicjalizacji wartości domyślnej, pola key i value 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łowanym struct. 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 zmiennej x. Nie jest to równoważne drugiemu wywołaniu funkcji Increment, które modyfikuje wartość w zamkniętej kopii x. 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 z x i y 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 i Y), dopóki wszystkie pola konstruowanej struktury nie zostaną jednoznacznie 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 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 dla e1 powinien być kontekst wywołujący.
  • W przypadku przypisania e1 = e2 bezpieczny kontekst e2 musi być co najmniej tak szeroki, jak bezpieczny kontekst e1.

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 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 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 pola ref. 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