Add design notes

This commit is contained in:
Mads Torgersen 2017-08-15 17:33:02 -07:00
parent ae933d104e
commit 670c79812c
2 changed files with 132 additions and 0 deletions

View file

@ -0,0 +1,123 @@
# C# Language Design Notes for Aug 14, 2017
## Agenda
We looked at the interaction between generics and nullable reference types
1. Unconstrained type parameters
2. Nullable constraints
3. Conversions between constructed types
# Unconstrained type parameters
Unconstrained type parameters should allow nullable reference types as type arguments. One way to look at it is that the default constraint is no longer `object` but `object?`. This is important, so that existing unconstrained generic types work with nullable reference types; e.g. `List<string?>`.
This means that the body of a generic type or method has to deal with type parameters that can be instantiated with both nullable and nonnullable reference types. To be safe, it must impose both nullable and non-nullable restrictions:
``` c#
var s = t.ToString(); // warning: dereference without null check
T t = default(T); // warning: may be creating null value of non-nullable ref type
```
## Dereferencing
The rules around dereferencing values of unconstrained generic type should be no different than for nullable reference types: The compiler needs to see you check for null, or else you get a warning.
This is not likely to happen a lot, as there aren't that many members available on unconstrained type parameters; only the `object` ones.
## Default expressions
`default(T)` is of type `T`, and in and of itself yields a warning, because it may create a null value of a non-nullable type. This is just like `default(string)`, as per previous meeting's decisions.
How can you fix this warning? Well there are no super good options. After all, you are writing code that will create a null value of a non-nullable type. We should make sure that e.g. the `!` operator is capable of silencing the warning:
``` c#
T t = default(T)!; // if that's allowed, or some version of it
T t = default!; // if that's allowed
```
## Defaultable types
Another option is to introduce a notion of "defaultable types" that can be applied to an unconstrained type parameter. For all type arguments it means the type itself, except for non-nullable reference types, where it is the nullable counterpart. We may want to overload the `?` syntax for it:
``` c#
T? t = default(T?); // if that's allowed
```
With the decisions above, this is more of a "side feature", and doesn't impose itself on users who don't ask for it. This may be important, as it is on the complex side. The previous notes had examples of some confusing aspects of this type constructor.
This feature would also be useful to express patterns of the `FirstOrDefault` ilk:
``` c#
T? FirstOrDefault<T>(this IEnumerable<T> src)
```
Similarly, we can imagine someone out there who wants to express occasional APIs that take null as an argument, even when their type argument does not allow it. You may have a more local contract that allows null even when the overall type or generic context does not.
Let's embrace `T?` in the prototype and look out for confusion, usage, implementation issues, etc.
Somebody will need to work on type inference rules in the presence of `T?` in signatures.
## TryGet
If we embrace defaultable `T?`, it would technically be a correct type for the out parameter of `TryGet` methods:
``` c#
bool TryGet(out T? value); // correct, but weak
```
It's probably not useful, though. Consumers only very occasionally look at the output when the result is fall. The majority, instead, would be annoyed that they cannot assume the value is non-null when they checked the bool and found it to be true.
Let's put off decisions around this for a bit. There should probably be a special mechanism for this scenario.
# Nullable constraints
Regardless of whether we allow nullable reference constraints explicitly, they will exist anyway:
- unconstrained type parameters are like having the `object?` constraint
- inherited constraints on overrides may implicitly be nullable
So we might as well embrace nullable reference types as constraint. The rule is that if any constraint is non-nullable, then instantiations with nullable reference types yield warnings.
## object as constraint
Currently `object` is disallowed as an explicit constraint, since it is implied. We should start allowing `object` as a constraint.
## The class constraint
Should the `class` constraint allow type arguments that are nullable reference types? If yes, how do you ask for only the non-nullable ones? If no, how do you allow the nullable ones?
It seems we have a couple of syntactic options:
1. `class` allows nullable, `class, object` does not
2. `class?` allows nullable, `class` does not
3. `class` allows nullable, `class!` does not
The 3rd option adds new syntax that isn't warranted. The 1st option adds no new syntax, but it is tedious to specify a non-nullable reference constraint.
The 2nd option seems most in line with the general direction. However, there is a slight concern that people today might be using the `class` constraint *specifically* so that they can use `null`. All of those will have to add `?` to their `class` constraints.
We will still go with option 2 on general principle.
# Conversions between constructed types
There's an identity conversion between constructed types that differ only in the nullness of their type arguments.
But sometimes there's a warning. When? How do you get rid of it?
``` c#
Dictionary<string, string?> d = new Dictionary<string?, string>(); // warning in both directions
```
Essentially the rule is as follows:
- Generally warn on non-matching nullability in both directions.
- For covariant type parameters, don't warn going from `T` to `T?`
- For contravariant type parameters, don't warn going from `T?` to `T`
We would like the `!` operator to be able to silence this, when there is an expression. An explicit cast should also work, but may "do too much".
This may be quite complex to implement in current compiler, but we think it is important and do want to push on it.

View file

@ -606,3 +606,12 @@ We discussed how nullable reference types should work in a number of different s
4. Unconstrained type parameters
## Aug 14, 2017
[C# Language Design Notes for Aug 14, 2017](LDM-2017-08-14.md)
We looked at the interaction between generics and nullable reference types
1. Unconstrained type parameters
2. Nullable constraints
3. Conversions between constructed types