Variables? We Don’t Need No Stinking Variables - C# Discards

C# 7.0 introduced the concept of discards. Discards are intentionally unused, temporarily dummy variables that we don’t care about and don’t want to use.

For example, the following shows the result of an addition being discarded:

_ = 1 + 1;

Note the underscore _ this is the discard character.

Given the preceding example, you cannot access the result of this addition, for example:

WriteLine(_); // Error CS0103  The name '_' does not exist in the current context 

Using C# Discards with Out Parameters

A more useful example is when you are working with a method that has one or more out parameters and you don’t care about using the outputted value.

As an example, consider one of the many TryParse methods in .NET such as int.TryParse. The following code show a method that writes to the console whether or not a string can be parsed as an int:

static void ParseInt()
{
    WriteLine("Please enter an int to validate");
    string @int = ReadLine();
    bool isValidInt = int.TryParse(@int, out int parsedInt);
    
    if (isValidInt)
    {
        WriteLine($"{@int} is a valid int");
    }
    else
    {
        WriteLine($"{@int} is NOT a valid int");
    }
}

The preceding method can be written using a discard because the out int parsedInt value is never used:

static void ParseIntUsingDiscard()
{
    WriteLine("Please enter an int to validate");
    string @int = ReadLine();

    if (int.TryParse(@int, out _))
    {
        WriteLine($"{@int} is a valid int");
    }
    else
    {
        WriteLine($"{@int} is NOT a valid int");
    }
}

For example we could create an expression bodied method using a similar approach:

static bool IsInt(string @int) => int.TryParse(@int, out _);

If you have a method that returns a lot of out values such as:

private static void GenerateDefaultCity(out string name, out string nickName, out long population, out DateTime founded)
{
    name = "London";
    nickName = "The Big Smoke";
    population = 8_000_000;
    founded = new DateTime(50, 1, 1);
}

In this case you might only care about the returned population value so you could discard all the other out values:

GenerateDefaultCity(out _,out _, out var population, out _);
WriteLine($"Population is: {population}");

Using C# Discards with Tuples

Another use for discards is where you don’t care about all the fields of a tuple. For example the following method returns a tuple containing a name and age:

static (string name, int age) GenerateDefaultPerson()
{
    return ("Amrit", 42);
}

If you only cared about the age you could write:

var (_, age) = GenerateDefaultPerson();
WriteLine($"Default person age is {age}");

Simplifying Null Checking Code with Discards

Take the following null checking code:

private static void Display(string message)
{
    if (message is null)
    {
        throw new ArgumentNullException(nameof(message));
    }
    WriteLine(message);
}

You could refactor this to make use of throw expressions:

private static void DisplayV2(string message)
{
    string checkedMessage = message ?? throw new ArgumentNullException(nameof(message));

    WriteLine(checkedMessage);
}

In the preceding version however, the checkedMessage variable is somewhat redundant, this could be refactored to use a discard:

private static void DisplayWithDiscardNullCheck(string message)
{
    _ = message ?? throw new ArgumentNullException(nameof(message));
    
    WriteLine(message);
}

Using C# Discards with Tasks

Take the following code:

// Warning CS1998  This async method lacks 'await' operators and will run synchronously.
Task.Run(() => SayHello());

Where the SayHello method is defined as:

private static string SayHello()
{
    string greeting = "Hello there!";
    return greeting;
}

If we don’t care about the return value and want to discard the result and get rid of the compiler warning::

// With discard - no compiler warning
_ = Task.Run(() => SayHello());

If there are any exceptions however, they will be supressed:

await Task.Run(() => throw new Exception()); // Exception thrown
_ = Task.Run(() => throw new Exception()); // Exception suppressed

Pattern Matching with Switch Statements and Discards

You can also use discards in switch statements:

private static void SwitchExample(object o)
{
    switch (o)
    {
        case null:
            WriteLine("o is null");
            break;
        case string s:
            WriteLine($"{s} in uppercase is {s.ToUpperInvariant()}");
            break;
        case var _:
            WriteLine($"{o.GetType()} type not supported.");
            break;
    }
}

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Comments (12) -

  • Duncan

    3/24/2020 4:34:11 PM | Reply

    I've been using _ when writing LINQ - "(_ => _.Property == value)". I got push-back from colleagues because they found the _ character confusing, but I left it for a year and came back to it, no problems now.

    • Jason Roberts

      3/25/2020 6:47:21 AM | Reply

      Thanks for sharing Duncan Smile

  • Jeremy Ferreira

    3/25/2020 8:54:50 AM | Reply

    Never saw an example with a lambda expression that contains two or more parameters. Am I missing something or the discard cannot be set on all of the parameters ?
    I've doing somethings like "__" and so on, which to me, is ugly.

    • Jason Roberts

      3/26/2020 3:06:05 AM | Reply

      Hi Jeremy, I'm sorry I don't understand the question - which bit of the example code are you referring to? You can have a lambda that has multiple params Smile

  • Brian Schroer

    3/26/2020 3:44:09 AM | Reply

    Regarding Jeremy's question: "_" can't be used multiple times for different discarded variables. I hadn't seen a code example with the technique Jeremy mentioned: using one underscore for the first, two underscores for the second, etc. until fairly recently, and that's the technique I use now, e.g.:

      (string name, string title, int age) GenerateDefaultPerson()
      {
        return ("Amrit", "Blogger", 42);
      }
      var (_, __, age) = GenerateDefaultPerson();

    I don't think the two-underscore variable is technically a discard though. I believe "__" is a valid C# variable name, and the in my example above, I can Console.Write it without error.

    • Jason Roberts

      3/26/2020 4:19:10 AM | Reply

      Thanks Brian,
      You can use multiple discards for out params such as: GenerateDefaultCity(out _,out _, out var population, out _); and also with tuples,

      For example:
      static (string name, int age, string nickName) GenerateDefaultPersonV2()
      {
          return ("Amrit", 42, "Mr A");
      }

      could be called with the following to discard everything except the nickname:
      var (_, _, nickName) = GenerateDefaultPersonV2();

      WriteLine($"Default person  nickname is {nickName}");

  • Christopher Baker

    3/26/2020 10:12:49 AM | Reply

    Is this something we would want to use? I mean in your example, all be it contrived, but does show the use case:
    private static void GenerateDefaultCity(out string name, out string nickName, out long population, out DateTime founded)
    {
        name = "London";
        nickName = "The Big Smoke";
        population = 8_000_000;
        founded = new DateTime(50, 1, 1);
    }
    then to use it to only get the population:
    GenerateDefaultCity(out _,out _, out var population, out _);
    WriteLine($"Population is: {population}");
    IMHO, only highlights a dstinct lack of design (and breaks SRP), that I would consider a serious code smell.
    I've never used it personally, although I have seen other devs use it in linq, and I think I can live wothout it

  • Steve Naidamast

    3/26/2020 4:53:14 PM | Reply

    Though I can understand the use of such a feature, realistically speaking, this is one of the more ridiculous features I have seen implemented in a language.
    As local variables go out of scope once a method exits to be discarded by the Garbage Collector, this leaves only certain types of variables that may see an advantage of using this type of feature such as method parameters.

    However, such features only add to the growing gibberish that is being implemented in our language environments, like the use of emojis in smart phone messages that have little to no text.

    In the long run, such features only serve to decrease any langauge's intelligibility .  To provide a classic example, Nantucket Software was once the famous Clipper compiler in the 1980s.  In their version 5.0 of their product they added an internal construct called the Code-Block, which was primarily originated for the internals developers.  Though there were other factors that contributed to the demise of this company, the stupidity of the Code-Block was certainly one of them.

    All development languages should be simple to code with and easy to read.  However, recent developments seem to show a trend towards greater usage of arcane constructs that appear to look more like hieroglyphics than a development language.  Of course, such features can now be implemented as our software becomes more advanced.  But seriously, do we need all this gibberish nonsense just to code our applications?

  • Jim Lonero

    3/28/2020 5:43:48 PM | Reply

    In the last example, case var _:  is this the same as using a default?

  • Gunnar Peipman

    3/29/2020 9:42:48 AM | Reply

    This feature is just an illusion. Behind the scenes compiler still creates variable and uses it. The fact that variable is created is hidden from us but there is still variable.

    • Jason Roberts

      3/30/2020 4:39:15 AM | Reply

      Hi Gunnar, thanks for the comment  Smile It's more about not needing to have to define variables in our own code that we don't want to make use of.

  • luke

    7/2/2020 10:50:30 AM | Reply

    Is it just me who finds this:

      if (message is null)
               throw new ArgumentNullException(nameof(message));

    clearer and more readable than this:

    _ = message ?? throw new ArgumentNullException(nameof(message));

    Seriously, I don't see how discard improves the code in the above example.

Pingbacks and trackbacks (1)+

Add comment

Loading