120 lines
No EOL
4.5 KiB
Markdown
120 lines
No EOL
4.5 KiB
Markdown
# C# Language Design Notes for May 14, 2018
|
|
|
|
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
|
|
|
|
## Agenda
|
|
|
|
We discussed reactions and feedback from talk and booth at the BUILD conference.
|
|
|
|
|
|
# Nullable
|
|
|
|
## Special methods
|
|
|
|
We're on the right track
|
|
|
|
## External annotations
|
|
|
|
We probably need them, we'll get back to it
|
|
|
|
## Incoming values to public API
|
|
|
|
The feature doesn't help people remember to check non-null parameters for null. We could make it so that the null state of such parameters starts out as MaybeNull.
|
|
|
|
This would definitely help people follow the current practice of aggressively protecting against unwanted null parameters. If in the future callers become more "reliable" on average, due to widespread adoption of the feature, then this may seem too harsh.
|
|
|
|
Of course this wouldn't help when somebody passes you a `string[]` with nulls in it. And unless the parameter is actually dereferenced (or passed as non-nullable) inside of the method, you'd still get no warning. This speaks to maybe having a separate analyzer to help you remember to check, instead of building it into the nullable feature?
|
|
|
|
"Public" could be `public`, `protected` and `protected internal`, or could be more dialable. Oftentimes people use public without intending it to be an API. They may be forced to by some other circumstance: interface implementation, or databinding etc.
|
|
|
|
It's also a problem if multiple overloads leave the checking to just one of them, that they all delegate to. Now they will get warnings when they just pass on the parameter.
|
|
|
|
There are lots of people who do not do argument checking, but instead leave the parameter to NRE on first occasion. This would work against that pattern.
|
|
|
|
This rule feel somewhat more ad hoc than the other nullability rules, especially as it depends on accessibility.
|
|
|
|
### Conclusion
|
|
|
|
Definitely worth discussing the scenario. There are many counterexamples to doing it like this, but it also seems reasonable to help people who want to do argument validation remember to do so. We're leaning to where this is an analyzer, not part of the feature itself.
|
|
|
|
|
|
# Index and Range
|
|
|
|
Generally the approach was approved of. It deals with some of the limitations in Python, for instance. The full generality of index expressions (working outside of indexing, having a natural type) wasn't always completely appreciated, but also wasn't considered harmful. The `^` glyph is a bit foreign at first, but nothing worse than that.
|
|
|
|
## Conclusion
|
|
|
|
We're on the right track.
|
|
|
|
|
|
# Default interface member implementations
|
|
|
|
Some reaction against it, where it just seems really wrong to put code inside of an interface. Also, many people don't have the problem of evolving interfaces: if those interfaces are only implemented in their own code bases, they can just fix things up. So the motivation doesn't really apply to them.
|
|
|
|
This is important if you are building public API, and also for consuming API from other languages, with Java and Swift interop in Xamarin being a prime example.
|
|
|
|
## Conclusion
|
|
|
|
We still think this is an important feature.
|
|
|
|
|
|
# Records and discriminated unions
|
|
|
|
The main motivation for people who ask for these features seems to be simply conciseness. Exhaustiveness checking is interesting, but not nearly as important.
|
|
|
|
Exhaustiveness is in fact not obvious:
|
|
|
|
``` c#
|
|
sealed class Animal
|
|
{
|
|
sealed class Dog: Animal {}
|
|
sealed class Cat: Animal {}
|
|
}
|
|
|
|
int M(Animal a)
|
|
{
|
|
return a switch
|
|
{
|
|
Cat c => 1,
|
|
Dog d => 2,
|
|
}
|
|
}
|
|
int M(Box<Animal> b)
|
|
{
|
|
return b switch
|
|
{
|
|
Box(Cat c) => 1,
|
|
Box(Dog d) => 2,
|
|
}
|
|
}
|
|
```
|
|
These two switches may look exhaustive, but are actually missing null cases both of the `Box` and the `Animal`. To be exhaustive they should really be:
|
|
|
|
``` c#
|
|
int M(Animal a)
|
|
{
|
|
return a switch
|
|
{
|
|
Cat c => 1,
|
|
Dog d => 2,
|
|
null => 3
|
|
}
|
|
}
|
|
int M(Box<Animal> b)
|
|
{
|
|
return b switch
|
|
{
|
|
Box(Cat c) => 1,
|
|
Box(Dog d) => 2,
|
|
Box(null) => 3,
|
|
null => 3
|
|
}
|
|
}
|
|
```
|
|
|
|
We probably want to have different exhaustiveness checking depending on whether `a` is `Animal` or `Animal?`. But that means that missing null cases can only yield warnings, just like nullability does, since we don't want nullability to have any semantic effects or yield errors.
|
|
|
|
|
|
## Struct unions
|
|
|
|
We could work with the runtime to allow us to overlay different types inside of a struct. For unions where each variant is small, structs would probably be highly preferable from a performance perspective. |