C # 7: podkreślenie (_) i gwiazdka (*) w zmiennej Out

79

Czytałem o nowych funkcjach zmiennych wyjściowych w C # 7 tutaj . Mam dwa pytania:

  1. To mówi

    Zezwalamy również na „discards” jako parametry out, w postaci a _, aby pozwolić Ci zignorować parametry, które Cię nie interesują:

    p.GetCoordinates(out var x, out _); // I only care about x
    

    P: Wydaje mi się, że to tylko informacja, a nie nowa funkcja języka C # 7, ponieważ możemy to zrobić również w C # 7.0:

    var _;
    if (Int.TryParse(str, out _))
    ...
    

    czy coś mi tu brakuje?

  2. Mój kod wyświetla błąd, gdy robię to, co wspomniano na tym samym blogu:

    ~Person() => names.TryRemove(id, out *);
    

    *nie jest prawidłowym identyfikatorem. Myślę, że przeoczenie Madsa Torgersena?

Nikhil Agrawal
źródło
15
in out _ _nie jest zmienną, nie deklarujesz jej i nie możesz jej używać z nazwy. W int _tym jest zmienna.
Evk
9
Wydaje się, że symbol wieloznaczny gwiazdki nie pojawił się w ostatecznej wersji C # 7.
Michael Stum
3
@NikhilAgrawal, ale to jest inna składnia. W swoim pytaniu używasz out _bez var. Dzięki varnim jest w istocie takie same jak poprzednio.
Evk
2
@NikhilAgrawal, który rzeczywiście kompiluje się z jakiegoś dziwnego powodu. Jeśli jednak spojrzymy na zdekompilowany kod - tego przypisania po prostu nie ma, całkowicie usunięto. A jeśli zrobisz coś takiego Console.WriteLine(_)- to się nie skompiluje, twierdząc, że nie ma takiej zmiennej. Całkiem dziwne. Co więcej: jeśli zrobisz coś takiego _ = SomeMethodCall()- zostanie to zastąpione przez tylko SomeMethodCall()w skompilowanym kodzie. W końcu nadal nie możesz używać tej zmiennej w żadnym sensownym sensie.
Evk

Odpowiedzi:

111

Odrzucenia , w C # 7 mogą być używane wszędzie tam, gdzie deklarowana jest zmienna, aby - jak sama nazwa wskazuje - odrzucić wynik. Zatem odrzucenie może być użyte bez zmiennych:

p.GetCoordinates(out var x, out _);

i można go użyć do odrzucenia wyniku wyrażenia:

_ = 42;

W przykładzie

p.GetCoordinates(out var x, out _);
_ = 42;

Nie ma żadnej zmiennej _, która jest wprowadzana. Istnieją tylko dwa przypadki użycia odrzutu.

Jeśli jednak _w zakresie istnieje identyfikator , nie można użyć odrzutów:

var _ = 42;
_ = "hello"; // error - a string cannot explicitly convert from string to int

Wyjątkiem jest sytuacja, gdy _zmienna jest używana jako zmienna wyjściowa. W tym przypadku kompilator ignoruje typ lub vari traktuje go jako odrzucenie:

if (p.GetCoordinates(out double x, out double _))
{
    _ = "hello"; // works fine.
    Console.WriteLine(_); // error: _ doesn't exist in this context.
}

Zauważ, że dzieje się tak tylko wtedy, gdy w tym przypadku jest używane out var _lub out double _. Po prostu użyj, out _a następnie jest traktowany jako odniesienie do istniejącej zmiennej _, jeśli jest w zakresie, np:

string _;
int.TryParse("1", out _); // complains _ is of the wrong type

Wreszcie *notacja została zaproponowana na wczesnym etapie dyskusji na temat odrzutów, ale została porzucona na rzecz _tego ostatniego, ponieważ jest to notacja częściej stosowana w innych językach .

David Arno
źródło
Myślę, że masz na myśli _
``
@ martijnn2008, dobrze zauważony. Dzięki.
David Arno
1
Przypuszczam, że jest to dorozumiane, ale celem odrzucenia jest to, że jego potencjalna wartość nigdy nie jest w rzeczywistości przechowywana?
Sinjai
Stwierdzenie, że _ = 42„odrzuca [s] wynik wyrażenia” jest mylące, ponieważ _ = 42samo w sobie jest wyrażeniem z wartością 42, więc w rzeczywistości nie ma miejsca odrzucanie. Nadal istnieje różnica, ponieważ _ = 42;jest to również stwierdzenie, podczas gdy 42;nie jest, co ma znaczenie w niektórych kontekstach.
Jeroen Mostert
1
Sformułowanie jest w porządku, po prostu _ = 42nie pokazuje, jaki jest cel tego odrzucenia - tj. Kiedy musiałbyś "nie przechowywać" wyrażenia, ale i tak je ocenić, biorąc pod uwagę, jak zwykle możesz ocenić (nietrywialne , przydatne) wyrażenie dobrze, bez jego przechowywania. Sam nie mogę od razu wymyślić użytecznego przykładu (i nie wiem, czy taki istnieje, czy jest to tylko wynik konsekwentnej gramatyki).
Jeroen Mostert
30

Innym przykładem Discard Operator _w C # 7 jest dopasowanie wzorca zmiennej typu objectw switchinstrukcji, która została ostatnio dodana w C # 7:

Kod:

static void Main(string[] args)
{
    object x = 6.4; 
    switch (x)
    {
        case string _:
            Console.WriteLine("it is string");
            break;
        case double _:
            Console.WriteLine("it is double");
            break;
        case int _:
            Console.WriteLine("it is int");
            break;
        default:
            Console.WriteLine("it is Unknown type");
            break;
    }

    // end of main method
}

Ten kod będzie pasował do typu i odrzuci zmienną przekazaną do case ... _.

Cyber ​​Progs
źródło
14

Dla bardziej zaciekawionych

Rozważmy następujący fragment

static void Main(string[] args)
{
    //....
    int a;
    int b;

    Test(out a, out b);
    Test(out _, out _);    
    //....
}

private static void Test(out int a, out int b)
{
    //...
}

Oto, co się dzieje:

...

13:             int  a;
14:             int  b;
15: 
16:             Test(out a, out b);
02340473  lea         ecx,[ebp-40h]  
02340476  lea         edx,[ebp-44h]  
02340479  call        02340040  
0234047E  nop  
    17:             Test(out _, out _);
0234047F  lea         ecx,[ebp-48h]  
02340482  lea         edx,[ebp-4Ch]  
02340485  call        02340040  
0234048A  nop 

...

Jak widać za kulisami, te dwie rozmowy mają ten sam skutek.

Jak zauważył @ Servé Laurijssen, fajne jest to, że nie musisz wstępnie deklarować zmiennych, co jest przydatne, jeśli nie jesteś zainteresowany niektórymi wartościami.

Sid
źródło
3
IL musi być taki sam, ponieważ wywoływana funkcja nadal wymaga slotów dla zmiennych wyjściowych. Tyle, że użycie nowej składni odrzucającej pozwala kompilatorowi poczynić dalsze założenia dotyczące zmiennej lokalnej (a raczej jej braku), pozwalając na bardziej efektywne jej wykorzystanie (przynajmniej teoretycznie; nie wiem, czy są już jakieś optymalizacje w kompilatorze od teraz).
szturchnij
9

Odnośnie pierwszego pytania

Myślę, że to tylko informacja, a nie nowa funkcja C # 7, ponieważ możemy to zrobić również w C # 7,0.

var _;
if (Int.TryParse(str, out _))
    // ...

Nowością jest to, że nie musisz _już deklarować wewnątrz ani na zewnątrz wyrażenia i możesz po prostu wpisać

int.TryParse(s, out _);

Spróbuj zrobić to jedną linijką przed C # 7:

private void btnDialogOk_Click_1(object sender, RoutedEventArgs e)
{
     DialogResult = int.TryParse(Answer, out _);
}
Służ Laurijssen
źródło
7
Aby dodać: Podkreślenie działa naprawdę dobrze w przypadku metod z wieloma parametrami wyjściowymi, np. SomeMethod(out _, out _, out three)Ma 3 parametry wyjściowe, ale odrzucam pierwsze dwa bez konieczności tworzenia zmiennych, takich jak unused1, unused2itp.
Michael Stum
@MichaelStum: Co się tutaj dzieje? if (SomeMethod(out _, out _, out _)) _ = 5; Do kogo _to się odnosi?
Nikhil Agrawal
4
@NikhilAgrawal W ogóle nie byłoby _zmiennej, nawet gdybyś użył out var _. Wygląda na to, że podkreślenie ma specjalną obudowę, aby odrzucić wynik.
Michael Stum
0

W C # 7.0 (Visual Studio 2017 około marca 2017) odrzucenia są obsługiwane w przydziałach w następujących kontekstach:


Inne przydatne uwagi

  • odrzucenia mogą zmniejszyć alokacje pamięci. Ponieważ wyjaśniają intencję kodu, zwiększają jego czytelność i łatwość utrzymania
  • Zauważ, że _ jest również prawidłowym identyfikatorem. Gdy jest używany poza obsługiwanym kontekstem

Prosty przykład: tutaj nie chcemy używać pierwszego i drugiego parametru i potrzebujemy tylko trzeciego parametru

(_, _, area) = city.GetCityInformation(cityName);

Zaawansowany przykład w przypadku przełącznika, w którym zastosowano również nowoczesne dopasowanie wzorców obudowy przełącznika ( źródło )

switch (exception)                {
case ExceptionCustom exceptionCustom:       
        //do something unique
        //...
    break;
case OperationCanceledException _:
    //do something else here and we can also cast it 
    //...
    break;
default:
    logger?.Error(exception.Message, exception);
    //..
    break;

}

Iman
źródło
0

P: ... możemy to zrobić również w C # 7.0:

var _;
if (Int.TryParse(str, out _))

czy coś mi tu brakuje?

To nie to samo.
Twój kod dokonuje przypisania.

W C # 7.0 _ nie jest zmienną, informuje kompilator, aby odrzucił wartość
( chyba że zadeklarowałeś _ jako zmienną ... jeśli to zrobisz, zmienna jest używana zamiast symbolu odrzucenia)

Przykład: możesz użyć _ jako żądła i int w tej samej linii kodu :

string a; 
int b;
Test(out a, out b);
Test(out _, out _);

//...

void Test(out string a, out int b)
{
   //...
}
J. Chris Compton
źródło