Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
120 lines (79 sloc) 4.49 KB

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:

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:

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.

You can’t perform that action at this time.