Udostępnij za pośrednictwem


C# 7 Series, Part 4: Discards

Sometimes we want to ignore values returned from a method, especially those out arguments, a typical example is to check whether a string can be parsed to another type:

 bool parsedValue;
if (bool.TryParse("TRUE", out parsedValue)) { /* Do your stuff */ }

Here we want to ignore parsedValue. We also want to make this variable inaccessible so developers cannot reference it.

The C# 7.0 has a new feature called discards, which can be used to achieve our goal for this scenario.

Discards

Discards are local variables which you can assign but cannot read from. i.e. they are “write-only” local variables. They don’t have names, instead, they are represented as a _ (underscore.) _ is a contextual keyword, it is very similar to var, and _ cannot be read (i.e. cannot appear on the right side of an assignment.)

If we apply the discard to above code, it will look like this:

 if (bool.TryParse("TRUE", out bool _)) { /* Do your stuff */ }

Because _ is unreadable, it will not appear in IntelliSense nor compile the code.

image

image

Places Discards Are Applicable

  • A declaration expression with an out modifier, for example: bool.TryParse(“string”, out _)
  • Pattern matching clauses, such as case int _ or if (x is string _)
  • Deconstructions:
    • In declaration: such as var (a, _, c) = myObj
    • In assignment: such as var a, b; (a, b) = myObj
    • Value Tuple deconstructions: such as var (a, _, _) = (1, 2, 3)

The _ Keyword

Please always remember that the _ is a contextual keyword, like var, that means if you have already declared a local variable _ in the current context and it is in scope, the _ will not be a discard and will refer to that local variable in scope.

image

More interestingly, look the following code:

 bool _ = false, v = false;
if (bool.TryParse("TRUE", out var _))
{
     /* Do your stuff */
     v = _;
}

What is the value of v?

The answer is false. The if test is true, and string “TRUE” can parse as a Boolean value true, but we have out var _, this overrides the scope of the previously declared variable _, and it is a discard. Then the assignment v = _ inside if statement just reads the value of previously declared local variable _ (which is false,) and assigns to v, hence the value of v is false. If we remove the var to change the code to out _, the value of v will be true because _ is on longer a discard and it holds the value of the parsed Boolean.

Conclusion

The discards in C# enables a way to ignore some local variables, it is a design time feature, the local variable may still be required at runtime and compiler may also generate a name for it. Since the _ keyword is a contextual keyword, you need to set a code policy to avoid declaring local variables with the name _ to reduce confusions. This feature is compatible with previous versions of .NET platforms as it does not require a CLR change.

Comments

  • Anonymous
    June 28, 2017
    Nice feature, seems useful in some situations where you don't really care about the actual value. But somehow I find the syntax of using underscore a little ugly, maybe it would have been cooler if they had introduced a special keyword like "discard", then the usage would be more meaningful and straightforward.Example: bool.TryParse(value, discard result) { ... }
    • Anonymous
      June 28, 2017
      This is actually similar to what we use for javascript. ES6 linter ignore any variable/parameter start with "_" for "unused variable" warning.
  • Anonymous
    July 04, 2017
    There is one more context that allows discards: on the left of an assignment. For example: _ = M();
    • Anonymous
      July 24, 2017
      ??
  • Anonymous
    July 06, 2017
    Any reason why discards were not allowed in lambda declarations?Func example = (_, _) => 10; // The first _ is just a parameter name, not a discard, and so the second _ is an illegal redef
    • Anonymous
      July 06, 2017
      Coment system ate my brackets. That was Func<int, int, int>
  • Anonymous
    July 11, 2017
    The comment has been removed
  • Anonymous
    July 24, 2017
    When writing a series of posts, it is useful to add a list of links to all the posts in the series at the start of each post, even if it means updating the first posts in the series when new posts are added. It makes reading the posts as a series much easier.
  • Anonymous
    September 06, 2017
    Hey, this feature seems really interesting. Will it force loading lazy fields or properties, even if these values are discarded? For example:: I have a class customer, it contains simple properties like name, age, address, but also a List of all the orders, that I made Lazy. If I want to deconstruct that customer, so I can a tuple of all the personal data and the orders[var (person, _) = customer1; ], could I discard the orders and therefore not go through with the loading of all the orders?