C#7: Underscore ( _ ) & Star ( * ) in Out variable
Asked Answered
G

6

96

I was reading about new out variable features in C#7 here. I have two questions:

  1. It says

    We allow "discards" as out parameters as well, in the form of a _, to let you ignore out parameters you don’t care about:

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

    Q: I guess this is just an info and not a new feature of C#7 because we can do so in pre C#7.0 too:

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

    or am I missing something here?

  2. My code gives an error when I do as mentioned in same blog:

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

    * is not a valid identifier. An oversight by Mads Torgersen I guess?

Golden answered 21/3, 2017 at 7:20 Comment(10)
in out _ _ is not a variable, you do not declare it and you cannot use it by name. In int _ that is a variable.Monogenetic
The asterisk wildcard did not make it into the final release of C# 7 it seems.Tonsure
@Evk: you cannot use it by name. I don't think so. I can do if (int.TryParse(str, out var _)) _ = 8;Golden
Regarding the out *, there is a comment to the blog mentioning this, so this was probably just an error in the post.Sounder
@NikhilAgrawal but that is different syntax. In your question you use out _, without var. With var it's indeed the same as before.Monogenetic
@Evk: My bad. I meant this. if (int.TryParse(str, out _)) _ = 8;Golden
@NikhilAgrawal that indeed compiles for some strange reason. However, if look at decompiled code - this assignment is just not there, completely removed. And if you do something like Console.WriteLine(_) - this won't compile claiming that there is no such variable. Quite weird. Even more: if you do something like _ = SomeMethodCall() - this will be replaced by just SomeMethodCall() in compiled code. So after all you still cannot really use that variable in any meaningful sense.Monogenetic
I’ve opened a bug on this observation.Sounder
Follow-up from that bug: _ is a general purpose discard now, and the behavior is fully intended.Sounder
@Monogenetic out _ and out var _ mean exactly the same thing and behave the same. @NikhilAgrawal You can assign anything to discards, you just can never get anything back from it.Oteliaotero
B
138

Discards, in C#7 can be used wherever a variable is declared, to - as the name suggests - discard the result. So a discard can be used with out variables:

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

and it can be used to discard an expression result:

_ = 42;

In the example,

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

There is no variable, _, being introduced. There are just two cases of a discard being used.

If however, an identifier _ exists in the scope, then discards cannot be used:

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

The exception to this is when a _ variable is used as an out variable. In this case, the compiler ignores the type or var and treats it as a discard:

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

Note that this only occurs if, in this case, out var _ or out double _ is used. Just use out _ and then it's treated as a reference to an existing variable, _, if it's in scope, eg:

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

Finally, the * notation was proposed early in the discussions around discards, but was abandoned in favour of _ due to the latter being a more commonly used notation in other languages.

Bjorn answered 21/3, 2017 at 10:18 Comment(5)
I presume this is implied, but the point of the discard is that its would-be value is never actually stored?Maser
Stating that _ = 42 "discard[s] the expression result" is misleading, because _ = 42 is itself an expression with the value 42, so there's no actual discarding going on. There's still a difference because _ = 42; is also a statement, whereas 42; is not, which matters in some contexts.Climb
@JeroenMostert, how would you phrase it differently? The expression is discarded as a discard is used. Whilst you are right that _ = 42' is an expression statement, I'm unclear as to how that is relevant to the fact that a discard is being used and so the result of the expression is not stored anywhere; it's discarded.Bjorn
The phrasing is fine, it's just that _ = 42 fails to show what the point of this discard is -- i.e, when you would need to "not store" an expression but evaluate it anyway, seeing as how you can usually evaluate a (non-trivial, useful) expression just fine without storing it. I can't immediately think of a useful example myself (and I don't know if there is one, or if this is just a result of consistency in the grammar).Climb
@JeroenMostert, ah OK. Got you. Yes, a better example than just 42 would be useful. I'll have a think about a better example and will update the answer. Thanks.Bjorn
C
35

Another example of the Discard Operator _ in C# 7 is to pattern match a variable of type object in a switch statement, which was recently added in C# 7:

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
}

This code will match the type and discard the variable passed to the case ... _.

Chair answered 9/11, 2017 at 8:46 Comment(0)
A
17

For more curious

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

This is what's happening:

...

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 

...

As you can see behind the scene the two calls are making the same thing.

As @Servé Laurijssen pointed out the cool thing is that you don't have to pre-declare variables which is handy if you are not interested in some values.

Arteaga answered 21/3, 2017 at 8:59 Comment(1)
The IL has to be the same because the function you are calling still requires the slots for the out variables. It’s just that using the new discarding syntax allows the compiler to make further assumptions about the local variable (or rather the lack of), allowing it to more efficiently use it (at least theoretically; I don’t know if there are already any optimizations in the compiler as of now).Sounder
P
9

Regarding the first question

I guess this is just an info and not a new feature of C#7 because we can do so in pre C#7.0 too.

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

The novelty is that you dont have to declare _ anymore inside or outside the expression and you can just type

int.TryParse(s, out _);

Try to do this one liner pre C#7:

private void btnDialogOk_Click_1(object sender, RoutedEventArgs e)
{
     DialogResult = int.TryParse(Answer, out _);
}
Plio answered 21/3, 2017 at 7:26 Comment(3)
To add: The underscore works really well for methods with multiple out parameters, e.g., SomeMethod(out _, out _, out three) has 3 out parameters, but I'm throwing away the first two without having to create variables like unused1, unused2 etc.Tonsure
@MichaelStum: What's happening here? if (SomeMethod(out _, out _, out _)) _ = 5; Which _ is it referring to?Golden
@NikhilAgrawal There wouldn't be a _ variable at all, even if you'd use out var _. It seems that the underscore is special cased to throw away the result.Tonsure
C
0

In C# 7.0 (Visual Studio 2017 around March 2017), discards are supported in assignments in the following contexts:


Other useful notes

  • discards can reduce memory allocations. Because they make the intent of your code clear, they enhance its readability and maintainability
  • Note that _ is also a valid identifier. When used outside of a supported context

Simple example : here we do not want to use the 1st and 2nd params and only need the 3rd param

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

Advanced example in switch case which used also modern switch case pattern matching (source)

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;

}

Carlock answered 7/10, 2019 at 8:17 Comment(0)
G
0

Q: ... we can do so in pre C#7.0 too:

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

or am I missing something here?

That isn't the same thing.
Your code is making an assignment.

In C# 7.0 _ is not a variable, it tells the compiler to discard the value
(unless you have declared _ as a variable... if you do that the variable is used instead of the discard symbol)

Example: you can use _ as a sting and an int in the same line of code:

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

//...

void Test(out string a, out int b)
{
   //...
}
Greenhouse answered 30/10, 2019 at 14:40 Comment(2)
Chris - close... the _ as a variable also becomes a discard :)Lucerne
@DavidV.Corbin Yeah... technically. I thought "_ is not a variable, unless you have declared _ as a variable" is better instruction for a person who doesn't understand discards. I assume your smiley face means that you get that :)Greenhouse

© 2022 - 2024 — McMap. All rights reserved.