Udostępnij za pośrednictwem


9 zmiennych

9.1 Ogólne

Zmienne reprezentują lokalizacje magazynu. Każda zmienna ma typ, który określa, jakie wartości mogą być przechowywane w zmiennej. C# jest językiem bezpiecznym dla typu, a kompilator języka C# gwarantuje, że wartości przechowywane w zmiennych są zawsze odpowiedniego typu. Wartość zmiennej można zmienić za pomocą przypisania lub za pomocą operatorów ++ i -- .

Zmienna musi być zdecydowanie przypisana (§9.4), zanim będzie można uzyskać jej wartość.

Zgodnie z opisem w poniższych podklasach zmienne są początkowo przypisywane lub początkowo nieprzypisane. Początkowo przypisana zmienna ma dobrze zdefiniowaną wartość początkową i zawsze jest uznawana za zdecydowanie przypisaną. Początkowo nieprzypisane zmiennej nie ma wartości początkowej. W przypadku początkowo nieprzypisanej zmiennej, która ma zostać uznana za zdecydowanie przypisaną w określonej lokalizacji, przypisanie do zmiennej odbywa się w każdej możliwej ścieżce wykonywania prowadzącej do tej lokalizacji.

9.2 Kategorie zmiennych

9.2.1 Ogólne

Język C# definiuje osiem kategorii zmiennych: zmienne statyczne, zmienne wystąpienia, elementy tablicy, parametry wartości, parametry wejściowe, parametry referencyjne, parametry wyjściowe i zmienne lokalne. Podklasy, które są zgodne z opisem każdej z tych kategorii.

Przykład: w poniższym kodzie

class A
{
    public static int x;
    int y;

    void F(int[] v, int a, ref int b, out int c, in int d)
    {
        int i = 1;
        c = a + b++ + d;
    }
}

x jest zmienną statyczną, y jest zmienną wystąpienia, v[0] jest elementem tablicy, a jest parametrem wartości, b jest parametrem referencyjnym, c jest parametrem wyjściowym, d jest parametrem wejściowym i i jest zmienną lokalną. przykład końcowy

9.2.2 Zmienne statyczne

Pole zadeklarowane za pomocą static modyfikatora jest zmienną statyczną. Zmienna statyczna występuje przed wykonaniem static konstruktora (§15.12) dla jego typu zawierającego i przestaje istnieć, gdy skojarzona domena aplikacji przestanie istnieć.

Początkowa wartość zmiennej statycznej jest wartością domyślną (§9.3) typu zmiennej.

Do celów sprawdzania określonego przypisania zmienna statyczna jest uznawana za początkowo przypisaną.

9.2.3 Zmienne wystąpienia

9.2.3.1 Ogólne

Pole zadeklarowane bez static modyfikatora jest zmienną wystąpienia.

9.2.3.2 Zmienne wystąpienia w klasach

Zmienna wystąpienia klasy istnieje, gdy zostanie utworzone nowe wystąpienie tej klasy i przestanie istnieć, gdy nie ma odwołań do tego wystąpienia i finalizatora wystąpienia (jeśli istnieje).

Początkowa wartość zmiennej wystąpienia klasy jest wartością domyślną (§9.3) typu zmiennej.

Do celów sprawdzania określonego przypisania zmienna wystąpienia klasy jest uznawana za początkowo przypisaną.

9.2.3.3 Zmienne wystąpienia w strukturach

Zmienna wystąpienia struktury ma dokładnie taki sam okres istnienia, jak zmienna struktury, do której należy. Innymi słowy, gdy zmienna typu struktury wchodzi w życie lub przestaje istnieć, należy też wykonać zmienne wystąpienia struktury.

Początkowy stan przypisania zmiennej wystąpienia struktury jest taki sam jak w przypadku zmiennej zawierającej struct . Innymi słowy, gdy zmienna struktury jest uznawana za początkowo przypisaną, dlatego też są jej zmiennymi wystąpienia, a gdy zmienna struktury jest uznawana za początkowo nieprzypisaną, jej zmienne wystąpienia są również nieprzypisane.

9.2.4 Elementy tablicy

Elementy tablicy pojawiają się po utworzeniu wystąpienia tablicy i przestaną istnieć, gdy nie ma odwołań do tego wystąpienia tablicy.

Początkowa wartość każdego elementu tablicy jest wartością domyślną (§9.3) typu elementów tablicy.

Do celów sprawdzania określonego przypisania element tablicy jest uznawany za początkowo przypisany.

9.2.5 Parametry wartości

Parametr wartości występuje po wywołaniu elementu członkowskiego funkcji (metody, konstruktora wystąpienia, metody, metody dostępu lub operatora) lub funkcji anonimowej, do której należy parametr, i jest inicjowany z wartością argumentu podanego w wywołaniu. Parametr wartości zwykle przestaje istnieć po zakończeniu wykonywania treści funkcji. Jeśli jednak parametr wartości jest przechwytywany przez funkcję anonimową (§12.19.6.2), jego okres istnienia wydłuża się co najmniej do czasu, aż drzewo delegatów lub wyrażeń utworzone na podstawie tej funkcji anonimowej kwalifikuje się do odzyskiwania pamięci.

Do celów sprawdzania określonego przypisania parametr wartości jest uznawany za początkowo przypisany.

Parametry wartości zostały omówione dalej w §15.6.2.2.

9.2.6 Parametry referencyjne

Parametr odwołania jest zmienną referencyjną (§9.7), która występuje po wywołaniu elementu członkowskiego funkcji, delegata, funkcji anonimowej lub funkcji lokalnej, a jego odwołanie jest inicjowane do zmiennej podanej jako argument w tym wywołaniu. Parametr odwołania przestaje istnieć po zakończeniu wykonywania treści funkcji. W przeciwieństwie do parametrów wartości parametr referencyjny nie jest przechwytywany (§9.7.2.9).

Następujące reguły określonego przypisania mają zastosowanie do parametrów referencyjnych.

Uwaga: Reguły parametrów wyjściowych są różne i są opisane w sekcji (§9.2.7). notatka końcowa

  • Zmienna musi być zdecydowanie przypisana (§9.4), zanim będzie mogła zostać przekazana jako parametr referencyjny w wywołaniu elementu członkowskiego funkcji lub delegata.
  • W ramach funkcji członkowskiej lub anonimowej parametr odwołania jest uznawany za początkowo przypisany.

Parametry referencyjne zostały omówione dalej w §15.6.2.3.3.

9.2.7 Parametry wyjściowe

Parametr wyjściowy jest zmienną referencyjną (§9.7), która występuje po wywołaniu elementu członkowskiego funkcji, delegata, funkcji anonimowej lub funkcji lokalnej, a jego odwołanie jest inicjowane do zmiennej podanej jako argument w tym wywołaniu. Parametr wyjściowy przestaje istnieć po zakończeniu wykonywania treści funkcji. W przeciwieństwie do parametrów wartości parametr wyjściowy nie jest przechwytywany (§9.7.2.9).

Następujące reguły określonego przypisania mają zastosowanie do parametrów wyjściowych.

Uwaga: Reguły dotyczące parametrów referencyjnych są różne i są opisane w temacie (§9.2.6). notatka końcowa

  • Zmienna nie musi być zdecydowanie przypisana, zanim będzie można ją przekazać jako parametr wyjściowy w elemencie członkowskim funkcji lub wywołaniu delegata.
  • Po normalnym zakończeniu wywołania elementu członkowskiego funkcji lub wywołania delegata każda zmienna przekazana jako parametr wyjściowy jest traktowana jako przypisana w tej ścieżce wykonywania.
  • W obrębie elementu członkowskiego funkcji lub funkcji anonimowej parametr wyjściowy jest uznawany za początkowo nieprzypisany.
  • Każdy parametr wyjściowy elementu członkowskiego funkcji, funkcji anonimowej lub funkcji lokalnej jest zdecydowanie przypisany (§9.4), zanim element członkowski funkcji, funkcja anonimowa lub funkcja lokalna zwraca normalnie.

Parametry wyjściowe zostały omówione w temacie §15.6.2.3.4.

9.2.8 Parametry wejściowe

Parametr wejściowy jest zmienną referencyjną (§9.7), która występuje po wywołaniu elementu członkowskiego funkcji, delegata, funkcji anonimowej lub funkcji lokalnej, a jego odwołanie jest inicjowane do variable_reference podane jako argument w tym wywołaniu. Parametr wejściowy przestaje istnieć po zakończeniu wykonywania treści funkcji. W przeciwieństwie do parametrów wartości parametr wejściowy nie jest przechwytywany (§9.7.2.9).

Następujące określone reguły przypisania mają zastosowanie do parametrów wejściowych.

  • Zmienna musi być zdecydowanie przypisana (§9.4), zanim będzie mogła zostać przekazana jako parametr wejściowy w wywołaniu elementu członkowskiego funkcji lub delegata.
  • W ramach składowej funkcji, funkcji anonimowej lub funkcji lokalnej parametr wejściowy jest uznawany za początkowo przypisany.

Parametry wejściowe zostały omówione w temacie §15.6.2.3.2.

9.2.9 Zmienne lokalne

9.2.9.1 Ogólne

Zmienna lokalna jest deklarowana przez local_variable_declaration, declaration_expression, foreach_statement lub specific_catch_clausetry_statement. Zmienna lokalna może być również zadeklarowana przez określone rodzaje wzorców(§11). W przypadku foreach_statement zmienna lokalna jest zmienną iteracji (§13.9.5). W przypadku specific_catch_clause zmienna lokalna jest zmienną wyjątku (§13.11). Zmienna lokalna zadeklarowana przez foreach_statement lub specific_catch_clause jest uważana za początkowo przypisaną.

W bloku, for_statement, switch_block lub using_statement może wystąpić local_variable_declaration. Declaration_expression może wystąpić jako out argument_value, a jako tuple_element, który jest celem dekonstrukcji przypisania (§12.21.2).

Okres istnienia zmiennej lokalnej jest częścią wykonywania programu, podczas której magazyn jest gwarantowany do jego zarezerwowania. Ten okres istnienia rozciąga się od wejścia do zakresu, z którym jest skojarzony, co najmniej do czasu zakończenia wykonywania tego zakresu w jakiś sposób. (Wprowadzanie zamkniętego bloku, wywoływanie metody lub zwracanie wartości z bloku iteratora zawiesza się, ale nie kończy wykonywania bieżącego zakresu). Jeśli zmienna lokalna jest przechwytywana przez funkcję anonimową (§12.19.6.2), jego okres istnienia wydłuża się co najmniej do momentu utworzenia drzewa delegata lub wyrażenia z funkcji anonimowej wraz z innymi obiektami, które odwołują się do przechwyconej zmiennej, kwalifikują się do odzyskiwania pamięci. Jeśli zakres nadrzędny jest wprowadzany rekursywnie lub iteracyjnie, nowe wystąpienie zmiennej lokalnej jest tworzone za każdym razem, a jego inicjator , jeśli istnieje, jest obliczany za każdym razem.

Uwaga: zmienna lokalna jest tworzone za każdym razem, gdy zostanie wprowadzony jego zakres. To zachowanie jest widoczne dla kodu użytkownika zawierającego metody anonimowe. notatka końcowa

Uwaga: okres istnienia zmiennej iteracji (§13.9.5) zadeklarowany przez foreach_statement jest pojedynczą iterację tej instrukcji. Każda iteracja tworzy nową zmienną. notatka końcowa

Uwaga: rzeczywisty okres istnienia zmiennej lokalnej jest zależny od implementacji. Na przykład kompilator może statycznie określić, że zmienna lokalna w bloku jest używana tylko dla niewielkiej części tego bloku. Korzystając z tej analizy, kompilator może wygenerować kod, który powoduje, że magazyn zmiennej ma krótszy okres istnienia niż jego blok zawierający.

Magazyn, do którego odwołuje się lokalna zmienna referencyjna, jest odzyskiwane niezależnie od okresu istnienia tej lokalnej zmiennej referencyjnej (§7.9).

notatka końcowa

Zmienna lokalna wprowadzona przez local_variable_declaration lub declaration_expression nie jest inicjowana automatycznie i w związku z tym nie ma wartości domyślnej. Taka zmienna lokalna jest uznawana za początkowo nieprzypisaną.

Uwaga: local_variable_declaration , która zawiera inicjator, jest nadal nieprzypisany. Wykonanie deklaracji zachowuje się dokładnie tak jak przypisanie do zmiennej (§9.4.4.5). Używanie zmiennej przed wykonaniem inicjatora; na przykład w samym wyrażeniu inicjatora lub przy użyciu goto_statement , który pomija inicjator; jest błędem czasu kompilacji:

goto L;

int x = 1; // never executed

L: x += 1; // error: x not definitely assigned

W zakresie zmiennej lokalnej jest to błąd czasu kompilacji odwołujący się do tej zmiennej lokalnej w pozycji tekstowej, która poprzedza deklarator.

notatka końcowa

9.2.9.2 Odrzuty

Odrzucenie to zmienna lokalna, która nie ma nazwy. Odrzucenie jest wprowadzane przez wyrażenie deklaracji (§12.17) z identyfikatorem _; i jest niejawnie typizowane (_ lub ) lub var _jawnie wpisane (T _).

Uwaga: _ jest prawidłowym identyfikatorem w wielu formach deklaracji. notatka końcowa

Ponieważ odrzucenie nie ma nazwy, jedynym odwołaniem do reprezentowanej zmiennej jest wyrażenie, które go wprowadza.

Uwaga: odrzucenie można jednak przekazać jako argument wyjściowy, umożliwiając odpowiedniemu parametrowi wyjściowemu oznaczenie skojarzonej lokalizacji magazynu. notatka końcowa

Odrzucenie nie jest początkowo przypisane, dlatego zawsze występuje błąd podczas uzyskiwania dostępu do jego wartości.

Przykład:

_ = "Hello".Length;
(int, int, int) M(out int i1, out int i2, out int i3) { ... }
(int _, var _, _) = M(out int _, out var _, out _);

W przykładzie przyjęto założenie, że w zakresie nie ma deklaracji nazwy _ .

Przypisanie w celu _ przedstawia prosty wzorzec ignorowania wyniku wyrażenia. Wywołanie metody M pokazuje różne formy odrzucania dostępne w krotkach i jako parametry wyjściowe.

przykład końcowy

9.3 Wartości domyślne

Następujące kategorie zmiennych są automatycznie inicjowane do ich wartości domyślnych:

  • Zmienne statyczne.
  • Zmienne wystąpienia wystąpień klas.
  • Elementy tablicy.

Wartość domyślna zmiennej zależy od typu zmiennej i jest określana w następujący sposób:

  • Dla zmiennej value_type wartość domyślna jest taka sama jak wartość obliczona przez domyślny konstruktor value_type (§8.3.3).
  • Dla zmiennej reference_type wartość domyślna to null.

Uwaga: inicjowanie wartości domyślnych jest zwykle wykonywane przez zainicjowanie pamięci przez menedżera pamięci lub moduł odśmiecania pamięci do zera wszystkich bitów przed przydzielenie do użycia. Z tego powodu wygodnie jest użyć all-bits-zero do reprezentowania odwołania o wartości null. notatka końcowa

9.4 Określone przypisanie

9.4.1 Ogólne

W danej lokalizacji w kodzie wykonywalnym elementu członkowskiego funkcji lub funkcji anonimowej mówi się, że zmienna jest zdecydowanie przypisana, jeśli kompilator może udowodnić, przez określoną analizę przepływu statycznego (§9.4.4), że zmienna została automatycznie zainicjowana lub jest celem co najmniej jednego przypisania.

Uwaga: Nieformalnie określone zasady przypisania są następujące:

  • Początkowo przypisana zmienna (§9.4.2) jest zawsze uznawana za zdecydowanie przypisaną.
  • Początkowo nieprzypisana zmienna (§9.4.3) jest uznawana za zdecydowanie przypisaną w danej lokalizacji, jeśli wszystkie możliwe ścieżki wykonywania prowadzące do tej lokalizacji zawierają co najmniej jedną z następujących wartości:
    • Proste przypisanie (§12.21.2), w którym zmienna jest lewym operandem.
    • Wyrażenie wywołania (§12.8.10) lub wyrażenie tworzenia obiektu (§12.8.17.2), które przekazuje zmienną jako parametr wyjściowy.
    • W przypadku zmiennej lokalnej deklaracja zmiennej lokalnej dla zmiennej (§13.6.2), która zawiera inicjator zmiennej.

Formalna specyfikacja leżąca u podstaw powyższych zasad nieformalnych jest opisana w §9.4.2, §9.4.3 i §9.4.4.

notatka końcowa

Stany określonego przypisania zmiennych wystąpienia zmiennej struct_type są śledzone indywidualnie, a także zbiorczo. Oprócz reguł opisanych w §9.4.2, §9.4.3 i §9.4.4, następujące reguły dotyczą struct_type zmiennych i ich zmiennych wystąpień:

  • Zmienna wystąpienia jest zdecydowanie przypisywana, jeśli zmienna zawierająca struct_type jest uznawana za zdecydowanie przypisaną.
  • Zmienna struct_type jest uznawana za zdecydowanie przypisaną, jeśli każda ze zmiennych wystąpienia jest zdecydowanie przypisana.

Określone przypisanie jest wymagane w następujących kontekstach:

  • Zmienna musi być zdecydowanie przypisana w każdej lokalizacji, w której uzyskuje się jej wartość.

    Uwaga: gwarantuje to, że wartości niezdefiniowane nigdy nie występują. notatka końcowa

    Wystąpienie zmiennej w wyrażeniu jest uznawane za uzyskanie wartości zmiennej, z wyjątkiem sytuacji, gdy

    • zmienna jest lewym operandem prostego przypisania,
    • zmienna jest przekazywana jako parametr wyjściowy lub
    • zmienna jest zmienną struct_type i występuje jako lewy operand dostępu do składowych.
  • Zmienna musi być zdecydowanie przypisana w każdej lokalizacji, w której jest przekazywana jako parametr referencyjny.

    Uwaga: gwarantuje to, że wywoływany element członkowski funkcji może początkowo rozważyć przypisanie parametru odwołania. notatka końcowa

  • Zmienna musi być zdecydowanie przypisana w każdej lokalizacji, w której jest przekazywana jako parametr wejściowy.

    Uwaga: Gwarantuje to, że wywoływany element członkowski funkcji może początkowo rozważyć przypisanie parametru wejściowego. notatka końcowa

  • Wszystkie parametry wyjściowe elementu członkowskiego funkcji są zdecydowanie przypisywane w każdej lokalizacji, w której element członkowski funkcji zwraca (za pomocą instrukcji return lub przez wykonanie osiągając koniec treści składowej funkcji).

    Uwaga: zapewnia to, że elementy członkowskie funkcji nie zwracają niezdefiniowanych wartości w parametrach wyjściowych, umożliwiając kompilatorowi rozważenie wywołania elementu członkowskiego funkcji, które przyjmuje zmienną jako parametr wyjściowy odpowiadający przypisaniu do zmiennej. notatka końcowa

  • Zmienna this konstruktora wystąpienia struct_type musi być zdecydowanie przypisana w każdej lokalizacji, w której zwraca ten konstruktor wystąpienia.

9.4.2 Początkowo przypisane zmienne

Następujące kategorie zmiennych są klasyfikowane jako początkowo przypisane:

  • Zmienne statyczne.
  • Zmienne wystąpienia wystąpień klas.
  • Zmienne wystąpienia początkowo przypisanych zmiennych struktury.
  • Elementy tablicy.
  • Parametry wartości.
  • Parametry odwołania.
  • Parametry wejściowe.
  • Zmienne zadeklarowane w klauzuli catch lub instrukcji foreach .

9.4.3 Początkowo nieprzypisane zmienne

Następujące kategorie zmiennych są klasyfikowane jako początkowo nieprzypisane:

  • Zmienne wystąpienia początkowo nieprzypisanych zmiennych struktury.
  • Parametry wyjściowe, w tym zmienna this konstruktorów wystąpienia struktury bez inicjatora konstruktora.
  • Zmienne lokalne, z wyjątkiem tych zadeklarowanych w klauzuli catch lub instrukcji foreach .

9.4.4 Dokładne reguły określania określonego przypisania

9.4.4.1 Ogólne

Aby ustalić, czy każda używana zmienna jest zdecydowanie przypisana, kompilator używa procesu, który jest odpowiednikiem tego, który został opisany w tej podklasie.

Treść członka funkcji może zawierać jedną lub więcej początkowo nieprzypisanych zmiennych. Dla każdej początkowo nieprzypisanej zmiennej vkompilator określa określony stan przypisania dla v w każdym z następujących punktów w elemencie członkowskim funkcji:

  • Na początku każdej instrukcji
  • W punkcie końcowym (§13.2) każdego oświadczenia
  • Na każdym łuku, który przenosi kontrolkę do innej instrukcji lub do punktu końcowego instrukcji
  • Na początku każdego wyrażenia
  • Na końcu każdego wyrażenia

Określony stan przypisania v może być:

  • Zdecydowanie przypisano. Oznacza to, że we wszystkich możliwych przepływach sterowania do tego punktu przypisano wartość v.
  • Zdecydowanie nie przypisano. W przypadku stanu zmiennej na końcu wyrażenia typu bool, stan zmiennej, która nie jest zdecydowanie przypisana, może (ale niekoniecznie) należeć do jednego z następujących stanów podrzędnych:
    • Zdecydowanie przypisano po wyrażeniu true. Ten stan wskazuje, że wartość v jest zdecydowanie przypisana, jeśli wyrażenie logiczne jest obliczane jako prawda, ale nie musi być przypisane, jeśli wyrażenie logiczne jest oceniane jako false.
    • Zdecydowanie przypisano po wyrażeniu false. Ten stan wskazuje, że wartość v jest zdecydowanie przypisana, jeśli wyrażenie warunkowe jest oceniane jako false, ale nie musi być przypisane, jeśli wyrażenie warunkowe jest oceniane jako prawda.

Poniższe reguły określają, w jaki sposób stan zmiennej v jest określany w każdej lokalizacji.

9.4.4.2 Ogólne zasady dotyczące instrukcji

  • v nie jest zdecydowanie przypisany na początku treści składowej funkcji.
  • Określony stan przypisania v na początku każdej innej instrukcji jest określany przez sprawdzenie określonego stanu przypisania v na wszystkich transferach przepływu sterowania, które są celem początku tej instrukcji. Jeśli (i tylko wtedy) v jest zdecydowanie przypisany do wszystkich takich transferów przepływu sterowania, vjest zdecydowanie przypisany na początku instrukcji. Zestaw możliwych transferów przepływu sterowania jest określany w taki sam sposób, jak w przypadku sprawdzania dostępności instrukcji (§13.2).
  • Określony stan przypisania v w punkcie blockkońcowym , checked, uncheckedifwhiledoforforeachlock, usinglub switch instrukcji jest określany przez sprawdzenie określonego stanu przypisania v na wszystkich transferach przepływu sterowania, które są celem punktu końcowego tej instrukcji. Jeśli v jest zdecydowanie przypisany do wszystkich takich transferów przepływu sterowania, vjest zdecydowanie przypisany na końcu instrukcji . W przeciwnym razie v nie jest zdecydowanie przypisany w punkcie końcowym instrukcji . Zestaw możliwych transferów przepływu sterowania jest określany w taki sam sposób, jak w przypadku sprawdzania dostępności instrukcji (§13.2).

Uwaga: ponieważ nie ma ścieżek sterujących do instrukcji niemożliwej do osiągnięcia, v jest zdecydowanie przypisywana na początku jakiejkolwiek niemożliwej do osiągnięcia instrukcji. notatka końcowa

9.4.4.3 Instrukcje blokowania, zaznaczone i niezaznaczone instrukcje

Określony stan przypisania v w transferze kontrolki do pierwszej instrukcji listy instrukcji w bloku (lub do punktu końcowego bloku, jeśli lista instrukcji jest pusta) jest taka sama jak określona instrukcja przypisania v przed blokiem, checked, lub unchecked instrukcji.

9.4.4.4 Instrukcje wyrażeń

Dla instrukcji wyrażenia stmt , która składa się z wyrażenia wyrażenie wyrażenie:

  • v ma ten sam stan przypisania określonego na początku wyrażenie co na początku stmt.
  • Jeśli v jeśli zdecydowanie przypisano na końcu expr, jest on zdecydowanie przypisany w punkcie końcowym stmt; w przeciwnym razie nie jest on zdecydowanie przypisany w punkcie końcowym stmt.

9.4.4.5 Deklaracje deklaracji

  • Jeśli stmt jest instrukcją deklaracji bez inicjatorów, v ma ten sam stan określonego przypisania w punkcie końcowym stmt co na początku stmt.
  • Jeśli stmt jest instrukcją deklaracji z inicjatorami, wówczas określony stan przypisania dlav jest określany tak, jakby stmt był listą instrukcji, z jedną instrukcją przypisania dla każdej deklaracji z inicjatorem (w kolejności deklaracji).

9.4.4.6 Instrukcje If

Dla instrukcji stmt formularza:

if ( «expr» ) «then_stmt» else «else_stmt»
  • v ma ten sam stan przypisania określonego na początku wyrażenie co na początku stmt.
  • Jeśli v jest zdecydowanie przypisany na końcu eksplorowania, to na pewno jest przypisany do transferu przepływu sterowania do then_stmt i do else_stmt lub do punktu końcowego stmt , jeśli nie ma innej klauzuli.
  • Jeśli v ma stan "zdecydowanie przypisany po wyrażeniu true" na końcu wyrażenia, to na pewno jest przypisany do transferu przepływu sterowania do then_stmt, a nie na pewno przypisany do transferu przepływu sterowania do else_stmt lub do punktu końcowego stmt, jeśli nie ma klauzuli innej.
  • Jeśli v ma stan "zdecydowanie przypisany po wyrażeniu fałszywym" na końcu wyrażenia, to na pewno jest przypisany do transferu przepływu sterowania do else_stmt, a nie na pewno przypisany do transferu przepływu sterowania do then_stmt. Zdecydowanie jest przypisywany w punkcie końcowym stmt , jeśli i tylko wtedy, gdy jest zdecydowanie przypisany na koniec then_stmt.
  • W przeciwnym razie v nie jest zdecydowanie przypisany do transferu przepływu sterowania do then_stmt lub else_stmt lub do punktu końcowego stmt, jeśli nie ma klauzuli else.

9.4.4.7 Instrukcje switch

W przypadku instrukcji switchstmt z wyrażeniem sterującym:

Określony stan przypisania v na początku wyrażenie jest taki sam jak stan v na początku stmt.

Określony stan przypisania v na początku klauzuli straży sprawy to

  • Jeśli v jest zmienną wzorca zadeklarowaną w switch_label: "zdecydowanie przypisano".
  • Jeśli etykieta przełącznika zawierająca klauzulę ochrony (§13.8.3) nie jest osiągalna: "zdecydowanie przypisano".
  • W przeciwnym razie stan v jest taki sam jak stan v po eksplorze.

Przykład: Druga reguła eliminuje konieczność zgłaszania błędu przez kompilator, jeśli w nieosiągalnym kodzie zostanie uzyskany dostęp do nieprzypisanej zmiennej. Stan b jest "zdecydowanie przypisany" w niemożliwej do osiągnięcia etykiecie case 2 when bprzełącznika .

bool b;
switch (1) 
{
    case 2 when b: // b is definitely assigned here.
    break;
}

przykład końcowy

Określony stan przypisania v w transferze przepływu sterowania do dostępnej listy instrukcji przełącznika jest

  • Jeśli transfer kontroli był spowodowany instrukcją "goto case" lub "goto default", stan v jest taki sam jak stan na początku tej instrukcji "goto".
  • Jeśli transfer sterowania był spowodowany etykietą default przełącznika, stan v jest taki sam jak stan v po eksplorze.
  • Jeśli transfer sterowania był spowodowany niemożliwą do osiągnięcia etykietą przełącznika , stan v jest "zdecydowanie przypisany".
  • Jeśli transfer sterowania był spowodowany osiągalną etykietą przełącznika z klauzulą guard, stan v jest taki sam jak stan v po klauzuli guard.
  • Jeśli transfer sterowania był spowodowany osiągalną etykietą przełącznika bez klauzuli guard, stan v to
    • Jeśli v jest zmienną wzorca zadeklarowaną w switch_label: "zdecydowanie przypisano".
    • W przeciwnym razie stan v jest taki sam jak statystykav po eksplorze.

Konsekwencją tych reguł jest to, że zmienna wzorca zadeklarowana w switch_label będzie "zdecydowanie przypisana" w instrukcjach sekcji przełącznika, jeśli nie jest jedyną osiągalną etykietą przełącznika w jej sekcji.

Przykład:

public static double ComputeArea(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
        case Triangle t when t.Base == 0 || t.Height == 0:
        case Rectangle r when r.Length == 0 || r.Height == 0:
            // none of s, c, t, or r is definitely assigned
            return 0;
        case Square s:
            // s is definitely assigned
            return s.Side * s.Side;
        case Circle c:
            // c is definitely assigned
            return c.Radius * c.Radius * Math.PI;
           …
    }
}

przykład końcowy

9.4.4.8 Instrukcje While

Dla instrukcji stmt formularza:

while ( «expr» ) «while_body»
  • v ma ten sam stan przypisania określonego na początku wyrażenie co na początku stmt.
  • Jeśli v jest zdecydowanie przypisany na końcu expr, to na pewno jest przypisany do transferu przepływu sterowania do while_body i do punktu końcowego stmt.
  • Jeśli v ma stan "zdecydowanie przypisany po wyrażeniu true" na końcu wyrażenia, to na pewno jest przypisany do transferu przepływu sterowania do while_body, ale nie na pewno przypisany na końcu stmt.
  • Jeśli v ma stan "zdecydowanie przypisany po wyrażeniu fałszywym" na końcu wyrażenia, to na pewno jest przypisany do transferu przepływu sterowania do punktu końcowego stmt, ale nie na pewno przypisany do transferu przepływu sterowania do while_body.

9.4.4.9 Instrukcje Do

Dla instrukcji stmt formularza:

do «do_body» while ( «expr» ) ;
  • v ma taki sam stan określonego przypisania w przepływie sterowania od początku stmt do do_body jak na początku stmt.
  • v ma taki sam stan określonego przypisania na początku wiersza , co w punkcie końcowym do_body.
  • Jeśli v jest zdecydowanie przypisany na końcu expr, to na pewno jest przypisany do transferu przepływu sterowania do punktu końcowego stmt.
  • Jeśli v ma stan "zdecydowanie przypisany po wyrażeniu fałszywym" na końcu wyrażenia, to na pewno jest przypisany do transferu przepływu sterowania do punktu końcowego stmt, ale nie na pewno przypisany do transferu przepływu sterowania do do_body.

9.4.4.10 Instrukcje for

W przypadku instrukcji formularza:

for ( «for_initializer» ; «for_condition» ; «for_iterator» )
    «embedded_statement»

sprawdzanie określonego przypisania odbywa się tak, jakby instrukcja została napisana:

{
    «for_initializer» ;
    while ( «for_condition» )
    {
        «embedded_statement» ;
        LLoop: «for_iterator» ;
    }
}

with continue statements that target the for statement being translated to goto statements targeting the label LLoop. Jeśli for_condition zostanie pominięta w for instrukcji, ocena określonego przypisania będzie kontynuowana tak, jakby for_condition zostały zastąpione wartością true w powyższym rozszerzeniu.

9.4.4.11 Przerwanie, kontynuowanie i wykonywanie instrukcji goto

Określony stan przypisania v w transferze przepływu sterowania spowodowany przez breakinstrukcję , continuelub goto jest taki sam jak określony stan przypisania v na początku instrukcji.

9.4.4.12 Instrukcje Throw

Dla instrukcji stmt formularza:

throw «expr» ;

określony stan przypisania v na początku wyrażenie jest taki sam jak określony stanprzypisania v na początku stmt.

9.4.4.13 Instrukcje Return

Dla instrukcji stmt formularza:

return «expr» ;
  • Określony stan przypisania v na początku wyrażenie jest taki sam jak określony stanprzypisania v na początku stmt.
  • Jeśli v jest parametrem wyjściowym, należy go zdecydowanie przypisać:
    • po wyrzuceniu
    • lub na końcu finally bloku obiektu try-finallylub try-catch-finally zawierającego instrukcję .return

Dla instrukcji stmt formularza:

return ;
  • Jeśli v jest parametrem wyjściowym, należy go zdecydowanie przypisać:
    • przed stmt
    • lub na końcu finally bloku obiektu try-finallylub try-catch-finally zawierającego instrukcję .return

9.4.4.14 Instrukcje try-catch

Dla instrukcji stmt formularza:

try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
  • Określony stan przypisania v na początku try_block jest taki sam jak określony stan przypisania v na początku stmt.
  • Określony stan przypisania v na początku catch_block_i (dla każdego i) jest taki sam jak określony stan przypisania v na początku stmt.
  • Określony stan przypisania v w punkcie końcowym stmt jest zdecydowanie przypisany, jeśli (i tylko wtedy)v jest zdecydowanie przypisany na końcu try_block i każdy catch_block_i (dla każdego i od 1 do n).

9.4.4.15 Instrukcje Try-finally

Dla instrukcji stmt formularza:

try «try_block» finally «finally_block»
  • Określony stan przypisania v na początku try_block jest taki sam jak określony stan przypisania v na początku stmt.
  • Określony stan przypisania v na początku finally_block jest taki sam jak określony stanprzypisania v na początku stmt.
  • Określony stan przypisania v w punkcie końcowym stmt jest zdecydowanie przypisany, jeśli (i tylko wtedy) co najmniej jeden z następujących jest spełniony:
    • v jest zdecydowanie przypisany w punkcie końcowym try_block
    • v jest zdecydowanie przypisany w punkcie końcowym finally_block

Jeśli zostanie wykonany transfer przepływu sterowania (na goto przykład instrukcja), który rozpoczyna się w try_block i kończy się poza try_block, v jest również uważany za zdecydowanie przypisany do tego transferu przepływu sterowania, jeśli v jest zdecydowanie przypisany na koniec finally_block. (Nie jest to tylko wtedy, gdy v jest zdecydowanie przypisany z innego powodu do tego transferu przepływu sterowania, nadal jest uważany za zdecydowanie przypisany.

9.4.4.16 Instrukcje Try-catch-finally

W przypadku instrukcji formularza:

try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
finally «finally_block»

Analiza określonego przypisania jest wykonywana tak, jakby instrukcja była instrukcją try-finally otaczającą instrukcję:try-catch

try
{
    try «try_block»
    catch ( ... ) «catch_block_1»
    ...
    catch ( ... ) «catch_block_n»
}
finally «finally_block»

Przykład: W poniższym przykładzie pokazano, jak różne bloki instrukcji try (§13.11) wpływają na określone przypisanie.

class A
{
    static void F()
    {
        int i, j;
        try
        {
            goto LABEL;
            // neither i nor j definitely assigned
            i = 1;
            // i definitely assigned
        }
        catch
        {
            // neither i nor j definitely assigned
            i = 3;
            // i definitely assigned
        }
        finally
        {
            // neither i nor j definitely assigned
            j = 5;
            // j definitely assigned
        }
        // i and j definitely assigned
        LABEL: ;
        // j definitely assigned
    }
}

przykład końcowy

9.4.4.17 Instrukcje foreach

Dla instrukcji stmt formularza:

foreach ( «type» «identifier» in «expr» ) «embedded_statement»
  • Określony stan przypisania v na początku wyrażenie jest taki sam jak stan v na początku stmt.
  • Określony stan przypisania v w transferze przepływu sterowania do embedded_statement lub do punktu końcowego stmt jest taki sam jak stan v na końcu wiersza.

9.4.4.18 Using instrukcji

Dla instrukcji stmt formularza:

using ( «resource_acquisition» ) «embedded_statement»
  • Określony stan przypisania v na początku resource_acquisition jest taki sam jak stan v na początku stmt.
  • Określony stan przypisania v w transferze przepływu sterowania do embedded_statement jest taki sam jak stan v na końcu resource_acquisition.

Instrukcje 9.4.4.19 Lock

Dla instrukcji stmt formularza:

lock ( «expr» ) «embedded_statement»
  • Określony stan przypisania v na początku wyrażenie jest taki sam jak stan v na początku stmt.
  • Określony stan przypisania v w transferze przepływu sterowania do embedded_statement jest taki sam jak stan v na końcu eksplorowania.

9.4.4.20 Instrukcje yield

Dla instrukcji stmt formularza:

yield return «expr» ;
  • Określony stan przypisania v na początku wyrażenie jest taki sam jak stan v na początku stmt.
  • Określony stan przypisania v na końcu stmt jest taki sam jak stan v na końcu wiersza.

Instrukcja yield break nie ma wpływu na określony stan przypisania.

9.4.4.21 Ogólne reguły dla wyrażeń stałych

Poniższe zasady dotyczą dowolnego wyrażenia stałego i mają pierwszeństwo przed wszelkimi regułami z poniższych sekcji, które mogą mieć zastosowanie:

Dla wyrażenia stałego z wartością true:

  • Jeśli v jest zdecydowanie przypisany przed wyrażeniem, v jest zdecydowanie przypisany po wyrażeniu.
  • W przeciwnym razie v jest "zdecydowanie przypisany po wyrażeniu false" po wyrażeniu.

Przykład:

int x;
if (true) {}
else
{
    Console.WriteLine(x);
}

przykład końcowy

Dla wyrażenia stałego z wartością false:

  • Jeśli v jest zdecydowanie przypisany przed wyrażeniem, v jest zdecydowanie przypisany po wyrażeniu.
  • W przeciwnym razie v jest "zdecydowanie przypisany po wyrażeniu true" po wyrażeniu.

Przykład:

int x;
if (false)
{
    Console.WriteLine(x);
}

przykład końcowy

Dla wszystkich innych wyrażeń stałych określony stan przypisania v po wyrażeniu jest taki sam jak określony stan przypisania v przed wyrażeniem.

9.4.4.22 Ogólne reguły dla wyrażeń prostych

Następująca reguła dotyczy tych rodzajów wyrażeń: literały (§12.8.2), proste nazwy (§12.8.4), wyrażenia dostępu do składowych (§12.8.7), wyrażenia dostępu podstawowego nieindeksowane (§12.8.15), wyrażenia typeof (§12.8.18), wyrażenia wartości domyślnej (§12.8.21), wyrażenia nameof (§12.8.23) i wyrażenia deklaracji (§12.17).

  • Określony stan przypisania v na końcu takiego wyrażenia jest taki sam jak określony stan przypisania v na początku wyrażenia.

9.4.4.23 Ogólne reguły dla wyrażeń z osadzonymi wyrażeniami

Następujące reguły mają zastosowanie do tego rodzaju wyrażeń: wyrażenia nawiasowe (§12.8.5), wyrażenia krotki (§12.8.6), wyrażenia dostępu elementów (§12.8.12), wyrażenia dostępu podstawowego z indeksowaniem (§12.8.15), wyrażenia przyrostowe i dekrementacyjne (§12.8.16, §12.9.6), wyrażenia rzutowania (§12.9.7), jednoargumentowe wyrażenia +, -, ~, *, wyrażenia binarne +, -, *, /, %, <<, >>, <, <=, >, >=, ==, !=, is, as, &, |, ^ (§12.10, §12.11, §12.12, §12.13), wyrażenia przypisania złożonego (§12.21.4), wyrażenia checked i unchecked (§12.8.20), wyrażenia tworzenia tablicy i delegata (§12.8.17) i wyrażenia await (§12.9.8).

Każde z tych wyrażeń ma co najmniej jedno wyrażenie podrzędne, które są bezwarunkowo oceniane w stałej kolejności.

Przykład: operator binarny % ocenia lewą stronę operatora, a następnie prawą stronę. Operacja indeksowania oblicza indeksowane wyrażenie, a następnie oblicza każde z wyrażeń indeksu w kolejności od lewej do prawej. przykład końcowy

W przypadku wyrażenia wyrażenie, które ma wyrażenie podrzędne expr₁, expr null, ..., exprₓ, obliczone w tej kolejności:

  • Określony stan przypisania v na początku wyrażenie₁ jest taki sam jak określony stan przypisania na początku eksplorowania.
  • Określony stan przypisania v na początku expri (i większe niż jeden) jest taki sam jak określony stan przypisania na końcu expri₋₁.
  • Określony stan przypisania v na końcu wyrażenie jest taki sam jak określony stan przypisania na końcu exprₓ.

9.4.4.24 Wyrażenia wywołania i wyrażenia tworzenia obiektów

Jeśli wywoływana metoda jest metodą częściową, która nie implementuje deklaracji metody częściowej lub jest metodą warunkową, dla której wywołanie zostanie pominięte (§22.5.3.2), wówczas określony stanprzypisania v po wywołaniu jest taki sam jak określony stanprzypisania v przed wywołaniem. W przeciwnym razie obowiązują następujące reguły:

W przypadku wyrażenia wywołania formularza:

«primary_expression» ( «arg₁», «arg₂», … , «argₓ» )

lub wyrażenie wyrażenia tworzenia obiektu formularza:

new «type» ( «arg₁», «arg₂», … , «argₓ» )
  • W przypadku wyrażenia wywołania określony stan przypisania v przed primary_expression jest taki sam jak stan v przed wyrażeniem.
  • W przypadku wyrażenia wywołania określony stan przypisania v przed arg₁ jest taki sam jak stan v po primary_expression.
  • W przypadku wyrażenia tworzenia obiektu określony stan przypisania v przed wyrażeniem arg₁ jest taki sam jak stan v przed wyrażeniem.
  • Dla każdego argumentu argi określony stan przypisania v po argi jest określany przez reguły wyrażeń normalnych, ignorując dowolne inmodyfikatory , outlub ref .
  • Dla każdego argumentu argi dla każdego i większego niż jeden, określony stan przypisania v przed argi jest taki sam jak stan v po argi₋₁.
  • Jeśli zmienna v jest przekazywana jako out argument (tj. argument formularza "out v") w dowolnym z argumentów, stan v po eksplor jest zdecydowanie przypisany. W przeciwnym razie stan v po eksplorze jest taki sam jak stan v po argₓ.
  • W przypadku inicjatorów tablicy (§12.8.17.5), inicjatorów obiektów (§12.8.17.3), inicjatorów kolekcji (§12.8.17.4) i anonimowych inicjatorów obiektów (§12.8.17.7), określony stan przypisania jest ustalany na podstawie sposobu, w jaki te konstrukcje są rozwinięte.

9.4.4.25 Proste wyrażenia przypisania

Pozwól, aby zestaw obiektów docelowych przypisania w wyrażeniu e był definiowany w następujący sposób:

  • Jeśli e jest wyrażeniem krotki, cele przypisania w elemecie e są unią celów przypisania elementów e.
  • W przeciwnym razie cele przypisania w e e.

W przypadku wyrażenia wyrażenia w formularzu:

«expr_lhs» = «expr_rhs»
  • Określony stan przypisania v przed expr_lhs jest taki sam jak określony stan przypisania v przed wyrażeniem.
  • Określony stan przypisania v przed expr_rhs jest taki sam jak określony stanprzypisania v po expr_lhs.
  • Jeśli v jest celem przypisania expr_lhs, to określony stanprzypisania v po eksplor jest zdecydowanie przypisany. W przeciwnym razie, jeśli przypisanie występuje w konstruktorze wystąpienia typu struktury, a v jest ukrytym polem tworzenia kopii zapasowej automatycznie zaimplementowanej właściwości P na tworzonym wystąpieniu, a dostęp do właściwości wyznaczający P jest obiektem docelowym assigmentu expr_lhs, wówczas określony stan przypisania v po tym, jak wyrażenie jest zdecydowanie przypisane. W przeciwnym razie określony stan przypisania v po expr jest taki sam jak określony stanprzypisania v po expr_rhs.

Przykład: w poniższym kodzie

class A
{
    static void F(int[] arr)
    {
        int x;
        arr[x = 1] = x; // ok
    }
}

zmienna x jest uznawana za zdecydowanie przypisaną po arr[x = 1] ocenie jako lewa strona drugiego prostego przypisania.

przykład końcowy

9.4.4.26 && wyrażenia

W przypadku wyrażenia wyrażenia w formularzu:

«expr_first» && «expr_second»
  • Określony stan przypisania v przed expr_first jest taki sam jak określony stan przypisania v przed wyrażeniem.
  • Określony stan przypisania v przed expr_second jest zdecydowanie przypisany, jeśli i tylko wtedy, gdy stan v po expr_first jest zdecydowanie przypisany lub "zdecydowanie przypisany po prawdziwym wyrażeniu". W przeciwnym razie nie jest zdecydowanie przypisany.
  • Określony stan przypisania v po expr jest określany przez:
    • Jeśli stan v po expr_first jest zdecydowanie przypisany, stan v po eksplor jest zdecydowanie przypisany.
    • W przeciwnym razie, jeśli stan v po expr_second jest zdecydowanie przypisany, a stan v po expr_first jest "zdecydowanie przypisany po wyrażeniu fałszywym", stan v po wyrażeniu jest zdecydowanie przypisany.
    • W przeciwnym razie, jeśli stan v po expr_second jest zdecydowanie przypisany lub "zdecydowanie przypisany po wyrażeniu true", stan v po wyrażeniu jest "zdecydowanie przypisany po wyrażeniu true".
    • W przeciwnym razie, jeśli stan v po expr_first jest "zdecydowanie przypisany po wyrażeniu fałszywym", a stan v po expr_second jest "zdecydowanie przypisany po wyrażeniu fałszywym", stan v po wyrażeniu jest "zdecydowanie przypisany po wyrażeniu fałszywym".
    • W przeciwnym razie stan v po eksplorze nie jest zdecydowanie przypisany.

Przykład: w poniższym kodzie

class A
{
    static void F(int x, int y)
    {
        int i;
        if (x >= 0 && (i = y) >= 0)
        {
            // i definitely assigned
        }
        else
        {
            // i not definitely assigned
        }
        // i not definitely assigned
    }
}

zmienna i jest uznawana za zdecydowanie przypisaną w jednej z osadzonych instrukcji if , ale nie w drugiej. W instrukcji if w metodzie Fzmienna i jest zdecydowanie przypisana w pierwszej instrukcji osadzonej, ponieważ wykonanie wyrażenia (i = y) zawsze poprzedza wykonanie tej osadzonej instrukcji. Natomiast zmienna i nie jest zdecydowanie przypisana w drugiej osadzonej instrukcji, ponieważ x >= 0 mogła być testowana fałsz, co powoduje, że zmienna ijest nieprzypisane.

przykład końcowy

9.4.4.27 || Wyrażenia

W przypadku wyrażenia wyrażenia w formularzu:

«expr_first» || «expr_second»
  • Określony stan przypisania v przed expr_first jest taki sam jak określony stan przypisania v przed wyrażeniem.
  • Określony stan przypisania v przed expr_second jest zdecydowanie przypisany, jeśli i tylko wtedy, gdy stan v po expr_first jest zdecydowanie przypisany lub "zdecydowanie przypisany po prawdziwym wyrażeniu". W przeciwnym razie nie jest zdecydowanie przypisany.
  • Określona instrukcja przypisania v po expr jest określana przez:
    • Jeśli stan v po expr_first jest zdecydowanie przypisany, stan v po eksplor jest zdecydowanie przypisany.
    • W przeciwnym razie, jeśli stan v po expr_second jest zdecydowanie przypisany, a stan v po expr_first jest "zdecydowanie przypisany po wyrażeniu true", stan v po wyrażeniu jest zdecydowanie przypisany.
    • W przeciwnym razie, jeśli stan v po expr_second jest zdecydowanie przypisany lub "zdecydowanie przypisany po wyrażeniu fałszywym", stan v po wyrażeniu jest "zdecydowanie przypisany po wyrażeniu fałszywym".
    • W przeciwnym razie, jeśli stan v po expr_first jest "zdecydowanie przypisany po wyrażeniu true", a stan v po expr_ sekundy jest "zdecydowanie przypisany po wyrażeniu true", stan v po wyrażeniu jest "zdecydowanie przypisany po wyrażeniu true".
    • W przeciwnym razie stan v po eksplorze nie jest zdecydowanie przypisany.

Przykład: w poniższym kodzie

class A
{
    static void G(int x, int y)
    {
        int i;
        if (x >= 0 || (i = y) >= 0)
        {
            // i not definitely assigned
        }
        else
        {
            // i definitely assigned
        }
        // i not definitely assigned
    }
}

zmienna i jest uznawana za zdecydowanie przypisaną w jednej z osadzonych instrukcji if , ale nie w drugiej. W instrukcji if w metodzie Gzmienna i jest zdecydowanie przypisana w drugiej osadzonej instrukcji, ponieważ wykonanie wyrażenia (i = y) zawsze poprzedza wykonanie tej osadzonej instrukcji. Z kolei zmienna i nie jest zdecydowanie przypisana w pierwszej instrukcji osadzonej, ponieważ x >= 0 mogła mieć przetestowaną wartość true, co powoduje, że zmienna ijest nieprzypisane.

przykład końcowy

9.4.4.28 ! wyrażenia

W przypadku wyrażenia wyrażenia w formularzu:

! «expr_operand»
  • Określony stan przypisania v przed expr_operand jest taki sam jak określony stan przypisania v przed wyrażeniem.
  • Określony stan przypisania v po expr jest określany przez:
    • Jeśli stan v po expr_operand jest zdecydowanie przypisany, stan v po eksplorze jest zdecydowanie przypisany.
    • W przeciwnym razie, jeśli stan v po expr_operand jest "zdecydowanie przypisany po wyrażeniu fałszywym", stan v po wyrażeniu jest "zdecydowanie przypisany po wyrażeniu true".
    • W przeciwnym razie, jeśli stan v po expr_operand jest "zdecydowanie przypisany po wyrażeniu true", stan v po wyrażeniu jest "zdecydowanie przypisany po wyrażeniu fałszywym".
    • W przeciwnym razie stan v po eksplorze nie jest zdecydowanie przypisany.

9.4.4.29 ?? wyrażenia

W przypadku wyrażenia wyrażenia w formularzu:

«expr_first» ?? «expr_second»
  • Określony stan przypisania v przed expr_first jest taki sam jak określony stan przypisania v przed wyrażeniem.
  • Określony stan przypisania v przed expr_second jest taki sam jak określony stanprzypisania v po expr_first.
  • Określona instrukcja przypisania v po expr jest określana przez:
    • Jeśli expr_first jest wyrażeniem stałym (§12.23) z wartością null, stan v po wyrażeniu jest taki sam jak stan v po expr_second.
    • W przeciwnym razie stan v po eksplorze jest taki sam jak określony stan przypisania v po expr_first.

9.4.4.30 ?: wyrażenia

W przypadku wyrażenia wyrażenia w formularzu:

«expr_cond» ? «expr_true» : «expr_false»
  • Określony stan przypisania v przed expr_cond jest taki sam jak stan v przed wyrażeniem.
  • Określony stan przypisania v przed expr_true jest zdecydowanie przypisany, jeśli stan v po expr_cond jest zdecydowanie przypisany lub "zdecydowanie przypisany po prawdziwym wyrażeniu".
  • Określony stan przypisania v przed expr_false jest zdecydowanie przypisany, jeśli stan v po expr_cond jest zdecydowanie przypisany lub "zdecydowanie przypisany po wyrażeniu fałszywym".
  • Określony stan przypisania v po expr jest określany przez:
    • Jeśli expr_cond jest wyrażeniem stałym (§12.23) z wartościątrue, stanv po wyrażeniu jest taki sam jak stan v po expr_true.
    • W przeciwnym razie, jeśli expr_cond jest wyrażeniem stałym (§12.23) z wartościąfalse, stan v po wyrażeniu jest taki sam jak stan v po expr_false.
    • W przeciwnym razie, jeśli stan v po expr_true jest zdecydowanie przypisany, a stanv po expr_false jest zdecydowanie przypisany, stan v po eksplor jest zdecydowanie przypisany.
    • W przeciwnym razie stan v po eksplorze nie jest zdecydowanie przypisany.

9.4.4.31 Funkcje anonimowe

W przypadku lambda_expression lub anonymous_method_expression wyrażenie z treścią (blok lub wyrażenie):

  • Określony stan przypisania parametru jest taki sam jak dla parametru nazwanej metody (§9.2.6, §9.2.7, §9.2.8).
  • Określony stan przypisania zmiennej zewnętrznej v przed treścią jest taki sam jak stan v przed wyrażeniem. Oznacza to, że określony stan przypisania zmiennych zewnętrznych jest dziedziczony z kontekstu funkcji anonimowej.
  • Określony stan przypisania zmiennej zewnętrznej v po expr jest taki sam jak stan v przed wyrażeniem.

Przykład: przykład

class A
{
    delegate bool Filter(int i);
    void F()
    {
        int max;
        // Error, max is not definitely assigned
        Filter f = (int n) => n < max;
        max = 5;
        DoWork(f);
    }
    void DoWork(Filter f) { ... }
}

Generuje błąd czasu kompilacji, ponieważ maksymalna wartość nie jest zdecydowanie przypisana, gdy zadeklarowana jest funkcja anonimowa.

przykład końcowy

Przykład: przykład

class A
{
    delegate void D();
    void F()
    {
        int n;
        D d = () => { n = 1; };
        d();
        // Error, n is not definitely assigned
        Console.WriteLine(n);
    }
}

Generuje również błąd czasu kompilacji, ponieważ przypisanie do n w funkcji anonimowej nie ma wpływu na określony stan n przypisania poza funkcją anonimową.

przykład końcowy

9.4.4.32 Wyrażenia throw

W przypadku wyrażenia wyrażenia w formularzu:

throw thrown_expr

  • Określony stan przypisania v przed thrown_expr jest taki sam jak stan v przed wyrażeniem.
  • Określony stan przypisania v po wyrażenie jest "zdecydowanie przypisany".

9.4.4.33 Reguły dotyczące zmiennych w funkcjach lokalnych

Funkcje lokalne są analizowane w kontekście metody nadrzędnej. Istnieją dwie ścieżki przepływu sterowania, które mają znaczenie dla funkcji lokalnych: wywołania funkcji i konwersje delegatów.

Określone przypisanie dla treści każdej funkcji lokalnej jest definiowane oddzielnie dla każdej lokacji wywołania. Przy każdym wywołaniu zmienne przechwycone przez funkcję lokalną są uznawane za zdecydowanie przypisane, jeśli zostały one zdecydowanie przypisane w momencie wywołania. W tym momencie istnieje również ścieżka przepływu sterowania do lokalnej treści funkcji i jest uważana za osiągalną. Po wywołaniu funkcji lokalnej przechwycone zmienne, które zostały zdecydowanie przypisane w każdym punkcie kontrolnym pozostawiając funkcję (return instrukcje, yield instrukcje, await wyrażenia) są uważane za zdecydowanie przypisane po lokalizacji wywołania.

Konwersje delegatów mają ścieżkę przepływu sterowania do lokalnej treści funkcji. Przechwycone zmienne są zdecydowanie przypisane do treści, jeśli na pewno są przypisane przed konwersją. Zmienne przypisane przez funkcję lokalną nie są uznawane za przypisane po konwersji.

Uwaga: powyższe oznacza, że jednostki są ponownie analizowane pod kątem określonego przypisania przy każdym wywołaniu funkcji lokalnej lub konwersji delegata. Kompilatory nie są wymagane do ponownej analizy treści funkcji lokalnej podczas każdej konwersji wywołania lub delegata. Implementacja musi zawierać wyniki odpowiadające temu opisowi. notatka końcowa

Przykład: W poniższym przykładzie pokazano określone przypisanie dla przechwyconych zmiennych w funkcjach lokalnych. Jeśli funkcja lokalna odczytuje przechwyconą zmienną przed zapisaniem, przechwycona zmienna musi być zdecydowanie przypisana przed wywołaniem funkcji lokalnej. Funkcja F1 lokalna odczytuje s bez przypisywania. Jest to błąd, jeśli F1 jest wywoływany przed s jest zdecydowanie przypisany. F2 i polecenie przypisuje przed przeczytaniem. Można go wywołać, zanim i zostanie zdecydowanie przypisany. Ponadto może być wywoływany poF3, F2 ponieważ s2 jest zdecydowanie przypisany w .F2

void M()
{
    string s;
    int i;
    string s2;
   
    // Error: Use of unassigned local variable s:
    F1();
    // OK, F2 assigns i before reading it.
    F2();
    
    // OK, i is definitely assigned in the body of F2:
    s = i.ToString();
    
    // OK. s is now definitely assigned.
    F1();

    // OK, F3 reads s2, which is definitely assigned in F2.
    F3();

    void F1()
    {
        Console.WriteLine(s);
    }
    
    void F2()
    {
        i = 5;
        // OK. i is definitely assigned.
        Console.WriteLine(i);
        s2 = i.ToString();
    }

    void F3()
    {
        Console.WriteLine(s2);
    }
}

przykład końcowy

9.4.4.34 wyrażenia is-pattern

W przypadku wyrażenia wyrażenia w formularzu:

expr_operand jest wzorcem

  • Określony stan przypisania v przed expr_operand jest taki sam jak określony stan przypisania v przed wyrażeniem.
  • Jeśli zmienna "v" jest zadeklarowana we wzorcu, wówczas określony stan przypisania "v" po wyrzuceniu jest "zdecydowanie przypisany, gdy prawda".
  • W przeciwnym razie określony stan przypisania "v" po expr jest taki sam jak określony stan przypisania "v" po expr_operand.

Odwołania do zmiennych 9.5

Variable_reference jest wyrażeniem sklasyfikowanym jako zmienna. Variable_reference oznacza lokalizację magazynu, do której można uzyskać dostęp zarówno w celu pobrania bieżącej wartości, jak i do przechowywania nowej wartości.

variable_reference
    : expression
    ;

Uwaga: w językach C i C++ variable_reference jest znany jako lvalue. notatka końcowa

9.6 Niepodzielność odwołań do zmiennych

Odczyty i zapisy następujących typów danych są niepodzielne: bool, char, bytesbyteshortushortuintint, floati typy referencyjne. Ponadto odczyty i zapisy typów wyliczenia z typem bazowym na poprzedniej liście również są niepodzielne. Odczyty i zapisy innych typów, w tym long, ulong, doublei decimal, oraz typy zdefiniowane przez użytkownika, nie muszą być niepodzielne. Oprócz funkcji biblioteki zaprojektowanych w tym celu, nie ma gwarancji niepodzielnego odczytu modyfikacji i zapisu, takich jak w przypadku przyrostu lub dekrementacji.

9.7 Zmienne referencyjne i zwracane

9.7.1 Ogólne

Zmienna referencyjna to zmienna, która odwołuje się do innej zmiennej nazywanej odwołaniem (§9.2.6). Zmienna referencyjna to zmienna lokalna zadeklarowana za pomocą ref modyfikatora.

Zmienna referencyjna przechowuje variable_reference (§9.5) na jej odwołanie, a nie wartość jego odwołania. Gdy jest używana zmienna referencyjna, w której zwracana jest wartość referenta; podobnie, gdy zmienna referencyjna jest elementem docelowym przypisania, jest to odwołanie, do którego przypisano. Zmienna, do której odwołuje się zmienna referencyjna, tj. przechowywana variable_reference dla jej odwołania, może zostać zmieniona przy użyciu przypisania ref (= ref).

Przykład: W poniższym przykładzie pokazano lokalną zmienną referencyjną, której odwołanie jest elementem tablicy:

public class C
{
    public void M()
    {
        int[] arr = new int[10];
        // element is a reference variable that refers to arr[5]
        ref int element = ref arr[5];
        element += 5; // arr[5] has been incremented by 5
    }     
}

przykład końcowy

Zwracany jest variable_reference zwracany z metody return-by-ref (§15.6.1). Ta variable_reference jest odwołaniem zwrotu odwołania.

Przykład: W poniższym przykładzie pokazano odwołanie, którego odwołanie jest elementem pola tablicy:

public class C
{
    private int[] arr = new int[10];

    public ref readonly int M()
    {
        // element is a reference variable that refers to arr[5]
        ref int element = ref arr[5];
        return ref element; // return reference to arr[5];
    }     
}

przykład końcowy

9.7.2 Konteksty bezpieczne ref

9.7.2.1 Ogólne

Wszystkie zmienne referencyjne przestrzegają zasad bezpieczeństwa, które zapewniają, że kontekst ref-safe-context zmiennej referencyjnej nie jest większy niż kontekst ref-safe-kontekstu jego odwołania.

Uwaga: Powiązane pojęcie kontekstu bezpiecznego jest zdefiniowane w pkt (§16.4.12) wraz ze skojarzonymi ograniczeniami. notatka końcowa

Dla każdej zmiennej kontekst ref-safe-kontekst tej zmiennej jest kontekstem, w którym variable_reference (§9.5) do tej zmiennej jest prawidłowy. Odwołanie do zmiennej referencyjnej ma kontekst ref-safe-context, który jest co najmniej tak szeroki, jak kontekst ref-safe-kontekstu samej zmiennej referencyjnej.

Uwaga: kompilator określa kontekst ref-safe-context za pomocą statycznej analizy tekstu programu. Kontekst bezpieczny ref odzwierciedla okres istnienia zmiennej w czasie wykonywania. notatka końcowa

Istnieją trzy konteksty bezpieczne ref:

  • bloku deklaracji: Ref-safe-context zmiennej variable_reference odnoszącej się do zmiennej lokalnej (§9.2.9.1) to zakres tej zmiennej lokalnej (§13.6.2), włączając wszelkie zagnieżdżone osadzone instrukcjew tym zakresie.

    Variable_reference do zmiennej lokalnej jest prawidłowym odwołaniem dla zmiennej referencyjnej tylko wtedy, gdy zmienna referencyjna jest zadeklarowana w kontekście ref-safe-context tej zmiennej.

  • element członkowski funkcji: W ramach funkcji variable_reference do dowolnego z poniższych elementów ma kontekst ref safe-context elementu członkowskiego funkcji:

    • Parametry wartości (§15.6.2.2) w deklaracji składowej funkcji, w tym niejawne this funkcje składowe klasy; i
    • Niejawne odwołanie () parametr (ref§15.6.2.3.3) this funkcji składowej struktury wraz z jego polami.

    Variable_reference z kontekstem bezpiecznym ref elementu członkowskiego funkcji jest prawidłowym odwołaniem tylko wtedy, gdy zmienna referencyjna jest zadeklarowana w tym samym elemencie członkowskim funkcji.

  • kontekst wywołujący: W ramach funkcji variable_reference do dowolnego z poniższych elementów ma kontekst ref safe-context kontekstu wywołującego:

    • Parametry odwołania (§9.2.6) inne niż niejawna this funkcja składowa struktury;
    • Pola składowe i elementy takich parametrów;
    • Pola składowe parametrów typu klasy; i
    • Elementy parametrów typu tablicy.

Variable_reference z kontekstem ref safe-context kontekstu wywołującego może być odwołaniem zwrotu odwołania.

Te wartości tworzą relację zagnieżdżającą z najwęższego (bloku deklaracji) do najszerszego (kontekstu wywołującego). Każdy zagnieżdżony blok reprezentuje inny kontekst.

Przykład: Poniższy kod przedstawia przykłady różnych kontekstów ref-safe-context. Deklaracje pokazują kontekst ref-safe-context dla odwołania jako wyrażenie inicjacyjne dla zmiennej ref . W przykładach przedstawiono kontekst ref-safe-context dla zwracania odwołania:

public class C
{
    // ref safe context of arr is "caller-context". 
    // ref safe context of arr[i] is "caller-context".
    private int[] arr = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 

    // ref safe context is "caller-context"
    public ref int M1(ref int r1)
    {
        return ref r1; // r1 is safe to ref return
    }

    // ref safe context is "function-member"
    public ref int M2(int v1)
    {
        return ref v1; // error: v1 isn't safe to ref return
    }

    public ref int M3()
    {
        int v2 = 5;

        return ref arr[v2]; // arr[v2] is safe to ref return
    }

    public void M4(int p) 
    {
        int v3 = 6;

        // context of r2 is declaration-block,
        // ref safe context of p is function-member
        ref int r2 = ref p;

        // context of r3 is declaration-block,
        // ref safe context of v3 is declaration-block
        ref int r3 = ref v3;

        // context of r4 is declaration-block,
        // ref safe context of arr[v3] is caller-context
        ref int r4 = ref arr[v3]; 
    }
}

przykład końcowy.

Przykład: w przypadku struct typów niejawny this parametr jest przekazywany jako parametr referencyjny. Kontekst bezpieczne ref pól struct typu jako element członkowski funkcji zapobiega zwracaniu tych pól przez odwołanie. Ta reguła uniemożliwia wykonanie następującego kodu:

public struct S
{
     private int n;

     // Disallowed: returning ref of a field.
     public ref int GetN() => ref n;
}

class Test
{
    public ref int M()
    {
        S s = new S();
        ref int numRef = ref s.GetN();
        return ref numRef; // reference to local variable 'numRef' returned
    }
}

przykład końcowy.

9.7.2.2 Kontekst bezpieczny zmiennej lokalnej ref

Dla zmiennej vlokalnej :

  • Jeśli v jest zmienną referencyjną, kontekst ref-safe-context jest taki sam jak kontekst ref-safe-context jego wyrażenia inicjującego.
  • W przeciwnym razie kontekst ref-safe-context jest blok-deklaracji.

9.7.2.3 Parametr ref safe context

Dla parametru p:

  • Jeśli p jest parametrem referencyjnym lub wejściowym, jego kontekst ref-safe-context jest kontekstem wywołującym. Jeśli p jest parametrem wejściowym, nie można go zwrócić jako zapisywalnego ref , ale można go zwrócić jako ref readonly.
  • Jeśli p jest parametrem wyjściowym, jego kontekst ref-safe-context jest kontekstem wywołującym.
  • W przeciwnym razie, jeśli p jest parametrem this typu struktury, jego kontekst ref-safe-context jest elementem członkowskim funkcji.
  • W przeciwnym razie parametr jest parametrem wartości, a jego kontekst ref-safe-context jest elementem członkowskim funkcji.

9.7.2.4 Kontekst bezpieczny ref pola

W przypadku zmiennej wyznaczającej odwołanie do pola: e.F

  • Jeśli e jest typem odwołania, jego kontekst ref-safe-context jest kontekstem wywołującym.
  • W przeciwnym razie, jeśli e jest typu wartości, jego kontekst ref-safe-context jest taki sam jak kontekst ref-safe-context .e

Operatory 9.7.2.5

Operator warunkowy (§12.18) c ? ref e1 : ref e2i operator = ref e przypisania odwołania (§12.21.1) mają zmienne referencyjne jako operandy i dają zmienną referencyjną. W przypadku tych operatorów kontekst ref-safe-context wyniku jest najwęższym kontekstem wśród kontekstów ref-safe-wszystkich ref operandów.

Wywołanie funkcji 9.7.2.6

W przypadku zmiennej c wynikającej z wywołania funkcji zwrotnej ref jej kontekst ref-safe-context jest najwęższy z następujących kontekstów:

  • Kontekst wywołujący.
  • Kontekst bezpieczny ref wszystkich refwyrażeń , outi in argumentów (z wyłączeniem odbiornika).
  • Dla każdego parametru wejściowego, jeśli istnieje odpowiednie wyrażenie, które jest zmienną i istnieje konwersja tożsamości między typem zmiennej a typem parametru, kontekst ref zmiennej, w przeciwnym razie najbliższy otaczający kontekst.
  • Bezpieczny kontekst (§16.4.12) wszystkich wyrażeń argumentów (w tym odbiornika).

Przykład: ostatni punktor jest niezbędny do obsługi kodu, takiego jak

ref int M2()
{
    int v = 5;
    // Not valid.
    // ref safe context of "v" is block.
    // Therefore, ref safe context of the return value of M() is block.
    return ref M(ref v);
}

ref int M(ref int p)
{
    return ref p;
}

przykład końcowy

Wywołanie właściwości i wywołanie indeksatora ( get lub set) jest traktowane jako wywołanie funkcji podstawowego dostępu przez powyższe reguły. Wywołanie funkcji lokalnej jest wywołaniem funkcji.

9.7.2.7.7 Wartości

Kontekst wartości ref-safe-context jest najbliższym otaczającym kontekstem.

Uwaga: występuje to w wywołaniu, takim jak M(ref d.Length) gdzie d jest typu dynamic. Jest on również zgodny z argumentami odpowiadającymi parametrom wejściowym. notatka końcowa

9.7.2.8 Wywołania konstruktorów

Wyrażenie new , które wywołuje konstruktor, przestrzega tych samych reguł co wywołanie metody (§9.7.2.6), które jest uważane za zwracanie tworzonego typu.

9.7.2.9 Ograniczenia dotyczące zmiennych referencyjnych

  • Ani parametr referencyjny, ani parametr wyjściowy, ani parametr wejściowy, ani ref lokalny, ani parametr lub lokalny ref struct typu nie są przechwytywane przez wyrażenie lambda lub funkcję lokalną.
  • Ani parametr referencyjny, ani parametr wyjściowy, ani parametr wejściowy, ani parametr ref struct typu nie są argumentem metody iteratora ani async metody.
  • Ani ref lokalny, ani lokalny ref struct typu nie są w kontekście w punkcie yield return instrukcji lub await wyrażenia.
  • W przypadku ponownego przypisania e1 = ref e2ref, kontekst ref-safe-context musi e2 być co najmniej tak szeroki, jak kontekst ref-safe-context .e1
  • W przypadku instrukcji return ref e1zwrotu ref kontekst ref jest kontekstem e1 wywołującym.