C# Language Design Notes, Nov 15, 2016 ====================================== > *Quote of the day*: "Bad people don't get good features!" Agenda ------ - Tuple name warnings - "Discards" Tuple name warnings =================== There are two kinds of warnings on the table regarding names in tuples: 1. A warning on tuple *literals* only, if the literal uses an element name that isn't in the target type 2. A warning on all expressions of tuple type if an element name in the expression is used for a *different* element in the target type The first warning helps get the names right when writing a tuple literal, and while maintaining it. It is already in the product: ``` c# (int x, int y) t = (a: 1, b: 2); // Oops, forgot to update tuple literal? Names a and b would be lost ``` The second warning would help guard against bugs due to reordering of tuple elements: ``` c# (string firstName, string lastName) GetName(); (string lastName, string firstName) name = GetName(); // Oops, forgot to swap the element names in name? ``` This warning is currently *not* in the product. We would like it to be, but don't have the runway to implement it. In the future it could be added as an analyzer, or as a compiler warning protected by warning waves (#1580). "Discards" ========== We got great feedback on "wildcards" at the MVP Summit, including changing its name to the term "discards", which is spreading in industry. We are also encouraged to use `_` as the discard character instead of `*`. It works better visually, and is what many other languages already use. There is a small matter of `_` already being a valid identifier. In order for it to coexist as a discard with existing valid uses we need to use a few tricks. Here are the essential rules: 1. A standalone `_` when no `_` is defined in scope is a discard 2. A "designator" `var _` or `T _` in deconstruction, pattern matching and out vars is a discard Discards are like unassigned variables, and do not have a value. They can only occur in contexts where they are assigned to. Examples: ``` c# M(out _, out var _, out int _); // three out variable discards (_, var _, int _) = GetCoordinates(); // deconstruction into discards if (x is var _ && y is int _) { ... } // discards in patterns ``` One use case is to silence warnings when dropping Tasks from async methods: ``` c# _ = FooAsync(); ``` We also want to allow discards as lambda parameters `(_, _) => 0`, but don't expect that to make it in C# 7.0. Today `_` is allowed as an ordinary identifier; the rule would be that it would become a discard if there's more than one. We can also consider allowing top-level discards in foreach loops: ``` c# foreach (_ in e) { ... } // I don't care about the values ``` But that does not seem too important, especially since you can use `var _` to the same effect. In general, whenever use of standalone `_` as a discard is precluded, either by syntactic restrictions or by `_` being declared in the scope, `var _` is often a fine substitute with the same meaning. However, if the declared `_` is a parameter or local, there are situations where you are out of luck: ``` c# public override void M(int _) // declaring _ to signal that this override doesn't care about it { _ = TryFoo(); // Error: cannot assign bool result to int variable _ var _ = TryFoo(); // Error: cannot declare local _ when one is already in scope } ``` These situations are always local to a member, so it is relatively easy to rewrite them, e.g. to rename the incoming parameter. We could consider accommodating them in the future: the error for a local redeclaration of `_` when one is already in scope could be changed to allowing it, but consider the second declaration a discard. This may be too subtle, and not useful enough, but it is worth considering post C# 7.0.