csharplang/meetings/2018/LDM-2018-02-21.md
2018-03-20 09:03:16 -07:00

2.5 KiB

C# Language Design Notes for Feb 21, 2018

Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!

Agenda

Various big and small issues around nullable reference types, preparing for upcoming prototypes

How are explicit casts interpreted?

(object)null          // object?, with warning
(object?)string.Empty // object
(IEnumerable<object?>)new[]{string.Empty}    // IEnumerable<object> 
(IComparer<string?>)Comparer<object>.Default // IComparer<string>, with warning

For the top two, there are two approaches you can think of:

  • nullability should be inferred from the expression
  • the cast represents an intent and we should honor it

It's to some degree a tension between existing code or the right design for new code.

If we were to infer nullability (rather than take it from the type), would nullability problems always be caught later on?

object o = ...;
var o = (object)...;

class X { object[] o; }

If we make it more lax, then there's more of a disconnect between top-level nullability and nested nullability.

If we think of ? not so much as a type thing but an observation on what's there, it doesn't seem so onerous to have it tracked locally.

Another approach: Have it be a different warning. Then you can switch it off separately, for legacy purposes.

For the casts:

(string)null;   // A
M((string)null);// B 
M((string?)x);  // C
M((string?)F()) // D - F is unannotated, and I want to impose my understanding of what it is
M((string)F())  // E - F is unannotated, and I want to impose my understanding of what it is

In C you want to treat the argument as may-be-null, regardless of what is in x. In B you could have a warning that is off in legacy code

Assume M is generic, and we make type inference based on arguments. Then D and E both make sense - they influence the result type of M, maybe. We shouldn't wave these off, but they are in some sense separable.

In the prototype we could give you the choice. We could have it on by default, and the warning could tell you how to turn it off.

C is either useless, or it means "forget inferred nullability". And it won't occur in old code.

Let's expand B:

M((string)y); // 

Say this is legacy, and now M gets upgraded to take string?. And y is now possibly null. The warning on (string)y could be one of those that is turned off for legacy purposes.

Between two options:

A: locals are implicitly nullable B: locals need explicit annotations like everything else, there's a concession to legacy