C # 7: Unterstrich (_) & Stern (*) in Out-Variable


79

Ich lese über neue aus variablen Funktionen in C # 7 hier . Ich habe zwei Fragen:

  1. Es sagt

    Wir erlauben auch "Verwerfen" als Out-Parameter in Form von a _, damit Sie Parameter ignorieren können, die Sie nicht interessieren:

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

    F: Ich denke, dies ist nur eine Information und keine neue Funktion von C # 7, da wir dies auch in Pre C # 7.0 tun können:

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

    oder fehlt mir hier etwas

  2. Mein Code gibt einen Fehler aus, wenn ich wie im selben Blog erwähnt vorgehe:

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

    *ist keine gültige Kennung. Ein Versehen von Mads Torgersen, denke ich?


15
in out _ _ist keine Variable, Sie deklarieren sie nicht und können sie nicht mit Namen verwenden. Darin int _liegt eine Variable.
Evk

9
Der Sternchen-Platzhalter hat es anscheinend nicht in die endgültige Version von C # 7 geschafft.
Michael Stum

3
@NikhilAgrawal, aber das ist eine andere Syntax. In Ihrer Frage verwenden Sie out _, ohne var. Mit ist vares in der Tat das gleiche wie zuvor.
Evk

2
@NikhilAgrawal, der tatsächlich aus irgendeinem seltsamen Grund kompiliert. Wenn Sie sich jedoch dekompilierten Code ansehen, ist diese Zuordnung einfach nicht vorhanden und vollständig entfernt. Und wenn Sie so etwas tun, Console.WriteLine(_)wird dies nicht kompiliert und behauptet, dass es keine solche Variable gibt. Ziemlich seltsam. Noch mehr: Wenn Sie so etwas tun, _ = SomeMethodCall()wird dies durch nur SomeMethodCall()kompilierten Code ersetzt. Schließlich können Sie diese Variable immer noch nicht wirklich in einem sinnvollen Sinne verwenden.
Evk

Antworten:


111

Discards in C # 7 können überall dort verwendet werden, wo eine Variable deklariert ist, um - wie der Name schon sagt - das Ergebnis zu verwerfen. Ein Discard kann also ohne Variablen verwendet werden:

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

und es kann verwendet werden, um ein Ausdrucksergebnis zu verwerfen:

_ = 42;

Im Beispiel

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

Es wird keine Variable _eingeführt. Es gibt nur zwei Fälle, in denen ein Abwurf verwendet wird.

Wenn jedoch ein Bezeichner _im Bereich vorhanden ist, können Rückwürfe nicht verwendet werden:

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

Die Ausnahme ist, wenn eine _Variable als Out-Variable verwendet wird. In diesem Fall ignoriert der Compiler den Typ oder varund behandelt ihn als Verwerfen:

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

Beachten Sie, dass dies nur auftritt, wenn in diesem Fall out var _oder out double _verwendet wird. Verwenden out _Sie einfach und dann wird es als Referenz auf eine vorhandene Variable behandelt _, wenn es im Geltungsbereich liegt, z.

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

Schließlich wurde die *Notation zu Beginn der Diskussionen um Rückwürfe vorgeschlagen, jedoch zugunsten der _Notation aufgegeben , da letztere in anderen Sprachen häufiger verwendet wird .


Ich denke du meinst '... _weil letzteres ein ...' ist
martijnn2008

@ martijnn2008, gut entdeckt. Vielen Dank.
David Arno

1
Ich nehme an, dass dies impliziert ist, aber der Punkt des Verwerfens ist, dass sein potenzieller Wert niemals tatsächlich gespeichert wird?
Sinjai

Die Angabe, dass _ = 42"das Ausdrucksergebnis verwerfen" ist irreführend, da _ = 42es sich selbst um einen Ausdruck mit dem Wert handelt 42, sodass kein tatsächliches Verwerfen stattfindet. Es gibt immer noch einen Unterschied, weil _ = 42;es sich auch um eine Aussage handelt, während 42;dies in einigen Kontexten nicht der Fall ist.
Jeroen Mostert

1
Die Phrasierung ist in Ordnung, es zeigt nur _ = 42nicht, wozu dieses Verwerfen dient - dh wenn Sie einen Ausdruck "nicht speichern", sondern trotzdem bewerten müssten, da Sie normalerweise einen (nicht trivialen) bewerten können , nützlicher) Ausdruck ganz gut, ohne ihn zu speichern. Ich kann mir nicht sofort ein nützliches Beispiel vorstellen (und ich weiß nicht, ob es eines gibt oder ob dies nur ein Ergebnis der Konsistenz in der Grammatik ist).
Jeroen Mostert

30

Ein weiteres Beispiel des Discard Operators _in C # 7 ist auf Mustererkennung eine Variable vom Typ objectin einer switchErklärung, die vor kurzem in C # 7 hinzugefügt wurde:

Code:

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
}

Dieser Code entspricht dem Typ und verwirft die an die übergebene Variable case ... _.


14

Für neugierigere

Betrachten Sie das folgende Snippet

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)
{
    //...
}

Folgendes passiert:

...

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 

...

Wie Sie hinter den Kulissen sehen können, machen die beiden Anrufe dasselbe.

Als @ Servé Laurijssen die kühle Sache wies darauf hin, dass Sie müssen nicht im Voraus declare Variablen , die praktisch ist , wenn Sie nicht in einigen Werten interessiert sind.


3
Die IL muss identisch sein, da für die von Ihnen aufgerufene Funktion weiterhin die Slots für die out-Variablen erforderlich sind. Es ist nur so, dass die Verwendung der neuen Verwerfungssyntax es dem Compiler ermöglicht, weitere Annahmen über die lokale Variable (oder vielmehr das Fehlen von) zu treffen und sie effizienter zu verwenden (zumindest theoretisch; ich weiß nicht, ob es bereits Optimierungen gibt ab sofort im Compiler).
stupsen

9

Zur ersten Frage

Ich denke, dies ist nur eine Information und keine neue Funktion von C # 7, da wir dies auch in Pre C # 7.0 tun können.

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

Das Neue ist, dass Sie nicht _mehr innerhalb oder außerhalb des Ausdrucks deklarieren müssen und einfach tippen können

int.TryParse(s, out _);

Versuchen Sie, diesen einen Liner vor C # 7 zu machen:

private void btnDialogOk_Click_1(object sender, RoutedEventArgs e)
{
     DialogResult = int.TryParse(Answer, out _);
}

7
Hinzufügen: Der Unterstrich funktioniert sehr gut für Methoden mit mehreren Out-Parametern, z. B. SomeMethod(out _, out _, out three)mit 3 Out-Parametern, aber ich werfe die ersten beiden weg, ohne Variablen wie unused1, unused2usw. erstellen zu müssen .
Michael Stum

@ MichaelStum: Was ist hier los? if (SomeMethod(out _, out _, out _)) _ = 5; Worauf _bezieht es sich?
Nikhil Agrawal

4
@NikhilAgrawal Es würde überhaupt keine _Variable geben, selbst wenn Sie verwenden würden out var _. Es scheint, dass der Unterstrich ein spezielles Gehäuse ist, um das Ergebnis wegzuwerfen.
Michael Stum

0

In C # 7.0 (Visual Studio 2017 um März 2017) werden Verwerfungen in Zuweisungen in den folgenden Kontexten unterstützt:

  • Tupel- und Objektdekonstruktion.
  • Musterabgleich mit is und switch .
  • Ruft Methoden ohne Parameter auf.
  • Ein eigenständiges _, wenn kein _ im Geltungsbereich ist.

Andere nützliche Hinweise

  • Durch Verwerfen können die Speicherzuordnungen verringert werden. Da sie die Absicht Ihres Codes klar machen, verbessern sie dessen Lesbarkeit und Wartbarkeit
  • Beachten Sie, dass _ auch ein gültiger Bezeichner ist. Bei Verwendung außerhalb eines unterstützten Kontexts

Einfaches Beispiel: Hier wollen wir nicht den 1. und 2. Parameter verwenden und brauchen nur den 3. Parameter

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

Erweitertes Beispiel im Switch-Fall, bei dem auch der moderne Switch-Case-Pattern-Matching ( Quelle ) verwendet wurde.

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;

}}


0

F: ... wir können dies auch in Pre C # 7.0 tun:

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

oder fehlt mir hier etwas

Das ist nicht dasselbe.
Ihr Code nimmt eine Zuordnung vor.

In C # 7.0 ist _ keine Variable, sondern weist den Compiler an, den Wert zu verwerfen
(es sei denn, Sie haben _ als Variable deklariert ... wenn Sie dies tun, wird die Variable anstelle des Verwerfungssymbols verwendet).

Beispiel: Sie können _ als Sting und Int in derselben Codezeile verwenden :

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

//...

void Test(out string a, out int b)
{
   //...
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.