4 KiB
C# Language Design Notes for Aug 16, 2017
Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!
"It's an open question whether we go out with a bang!
"
Agenda
The dammit operator
Proposal:
- Target typed:
e!
implicitly converts toT
ife
does, but without nullability warningsstring s = null!;
string s = default!
string s = GetNameOrNull()!;
List<string> l = GetList<string?>()!;
List<string?> l = GetList<string>()!;
- Inherent type: if the type of
e
is a nullable reference typeT?
, then the inherent type ofe!
isT
var s = GetNameOrNull()!;
GetNameOrNull()!.Length;
- Default expressions: if
T
is a non-nullable reference type, thendefault(T)!
suppresses the warning normally given bydefault(T)
For 2, an alternative is to have a dedicated !.
and ![...]
operator, cousins of ?.
and ?[...]
. Then you wouldn't get to factor out to a local with var
.
3 is a bit of a corner case. Most people would choose to just rewrite it to something else - there are plenty of options. But default(T)
is a good strategy for code generators.
We could generalize to !
silencing all nullability warnings even in subexpressions. No.
If !
is applied in a place that yields no nullability warnings, does that lead to a warning? No.
We can make !!
and error. If you really want to (we don't believe there's any scenario, other than swearing) you can parenthesize, (e!)!
.
An alternative is to make the type of e!
oblivious, if we choose to embrace a notion of oblivious. That's attractive in that it makes a type for "something that doesn't yield warnings", but it's also viral - could lead to many things not being checked. Option to be considered in future.
Discussion about var and the type of nullable things known not to be null
T t = ...;
if (t != null)
{
int s = t.ToString();
}
string? s = ...;
var s1 = s;
if (s != null)
{
var s2 = s;
int l = s.Length;
}
string s1 = "Hello";
var s2 = s1; //
This could be safe:
void M1(ref string s);
void M2(ref string? s);
var s = "foo";
if (s == null);
{
M1(ref s);
}
else
{
M2(ref s);
}
Three discussions:
- What is the type of the value of a local variable
string? s
in a scope where it is known to be non-null? Does it contribute the typestring
to e.g. type inference, or does it remainstring?
- What is the meaning of
var
? Does it infer its nullability along with the rest of its type, from the initializer, or does it remain able to be assignednull
even when initializer is not-null? - Should we reconsider completely abandoning the notion of nullness being part of a local's type at the top level?
- could even consider for value parameters
T M<T>(T t);
string? s = "abc";
var l = M(s).Length;
Type inference
Proposal:
- Consider nullness an orthogonal aspect to the rest of the type being inferred
- If any contributing type is nullable, that should contribute nullness to the inference
- A
null
literal expression should contribute nullness to the inference, even though it doesn't otherwise contribute to the type
Structs with fields of non-nullable type
Structs can be created without going through a declared constructor, all fields being set to their default value. If those fields are of non-nullable reference type, their default value will still be null!
It seems we can chase this in three ways:
- Not at all. We just aren't that ambitious.
- We warn on all fields of structs that have non-nullable reference types. That's a lot! How do you "fix" it? Make them nullable? No version of the
!
operator works here, since the whole point is you don't control initialization from user code. - We warn whenever a struct that has such fields is created as a default value. In other words, we treat the type the same as a non-null reference type, recursively. (And we warn on passing for a type parameter constrained by
new()
orstruct
?)