93

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?

Nikhil Agrawal
  • 47,018
  • 22
  • 121
  • 208
  • 17
    in `out _` `_` is not a variable, you do not declare it and you cannot use it by name. In `int _` that is a variable. – Evk Mar 21 '17 at 07:31
  • 9
    The asterisk wildcard did not make it into the final release of C# 7 it seems. – Michael Stum Mar 21 '17 at 07:33
  • @Evk: `you cannot use it by name`. I don't think so. I can do `if (int.TryParse(str, out var _)) _ = 8;` – Nikhil Agrawal Mar 21 '17 at 07:43
  • Regarding the `out *`, there is [a comment to the blog](https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/#div-comment-250916) mentioning this, so this was probably just an error in the post. – poke Mar 21 '17 at 07:43
  • 3
    @NikhilAgrawal but that is different syntax. In your question you use `out _`, without `var`. With `var` it's indeed the same as before. – Evk Mar 21 '17 at 07:44
  • @Evk: My bad. I meant this. `if (int.TryParse(str, out _)) _ = 8;` – Nikhil Agrawal Mar 21 '17 at 07:45
  • 2
    @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. – Evk Mar 21 '17 at 07:51
  • 3
    [I’ve opened a bug on this observation](https://github.com/dotnet/roslyn/issues/18015). – poke Mar 21 '17 at 08:16
  • 1
    Follow-up from that bug: `_` is a general purpose discard now, and the behavior is fully intended. – poke Mar 22 '17 at 13:58
  • 1
    @Evk `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. – Julien Couvreur Jul 08 '17 at 18:40

6 Answers6

135

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.

Palec
  • 12,743
  • 8
  • 69
  • 138
David Arno
  • 42,717
  • 16
  • 86
  • 131
  • 1
    I presume this is implied, but the point of the discard is that its would-be value is never actually stored? – Sinjai Jul 31 '17 at 19:55
  • 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. – Jeroen Mostert Nov 27 '18 at 09:40
  • @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. – David Arno Nov 27 '18 at 09:45
  • 1
    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). – Jeroen Mostert Nov 27 '18 at 09:49
  • @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. – David Arno Nov 27 '18 at 09:56
34

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 ... _.

smn.tino
  • 2,272
  • 4
  • 32
  • 41
Cyber Progs
  • 3,656
  • 3
  • 30
  • 39
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.

Sid
  • 14,176
  • 7
  • 40
  • 48
  • 3
    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). – poke Mar 21 '17 at 11:37
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 _);
}
poke
  • 369,085
  • 72
  • 557
  • 602
Serve Laurijssen
  • 9,266
  • 5
  • 45
  • 98
  • 7
    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. – Michael Stum Mar 21 '17 at 07:36
  • @MichaelStum: What's happening here? `if (SomeMethod(out _, out _, out _)) _ = 5; ` Which `_` is it referring to? – Nikhil Agrawal Mar 21 '17 at 07:55
  • 4
    @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. – Michael Stum Mar 21 '17 at 07:58
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;

}

Iman
  • 17,932
  • 6
  • 80
  • 90
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)
{
   //...
}
J. Chris Compton
  • 538
  • 1
  • 6
  • 25
  • Chris - close... the _ as a variable also becomes a discard :) – David V. Corbin Jul 26 '22 at 12:33
  • @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 :) – J. Chris Compton Jul 26 '22 at 13:28