132 lines
3.9 KiB
Markdown
132 lines
3.9 KiB
Markdown
|
# C# Language Design Notes for September 10, 2018
|
||
|
|
||
|
## Agenda
|
||
|
|
||
|
1. Nullability of constraints in inheritance and interface implementation
|
||
|
2. Static local functions
|
||
|
|
||
|
# Discussion
|
||
|
|
||
|
QOTD: *Don't open your mind so much the language falls out*
|
||
|
|
||
|
## Nullability of constraints in overriding
|
||
|
|
||
|
In C# we don't allow override methods to re-specify generic constraints.
|
||
|
The question then is how to treat constraints that are inherited from
|
||
|
a base method with a different `NonNullTypes` attribute. For example:
|
||
|
|
||
|
```C#
|
||
|
// Assembly 1
|
||
|
[NonNullTypes(true)]
|
||
|
class Base
|
||
|
{
|
||
|
virtual void M<T>() where T : C?
|
||
|
{ }
|
||
|
}
|
||
|
|
||
|
---
|
||
|
// Assembly 2
|
||
|
|
||
|
[NonNullTypes(false)]
|
||
|
class Derived : Base
|
||
|
{
|
||
|
override void M<T>()
|
||
|
{ }
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Does using `T` as a non-null type produce a warning, even though we've said
|
||
|
`[NonNullTypes(false)]` in the derived class?
|
||
|
|
||
|
**Conclusion**
|
||
|
|
||
|
When the base context is `true` and the derived is `false`, suppress related
|
||
|
warnings. When the base is false and the derived is true, treat unannotated
|
||
|
constraints as oblivious.
|
||
|
|
||
|
|
||
|
### Inheritance and explicit interface implementation
|
||
|
|
||
|
Similar to the above case, there's a question about how "sticky" nullability
|
||
|
is through interface implementation and inheritance. For instance, the following
|
||
|
class uses a constructed inherited member to explicitly implement an interface.
|
||
|
Does nullability on the type parameters have to match?
|
||
|
|
||
|
Here is the correct implementation:
|
||
|
|
||
|
```C#
|
||
|
interface I1<T>
|
||
|
{
|
||
|
void M<U>() where U : T
|
||
|
}
|
||
|
|
||
|
class C : I1<C?>
|
||
|
{
|
||
|
void I1<C?>.M<X>() {}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
And what if you remove the question mark in the explicit implementation?
|
||
|
|
||
|
```C#
|
||
|
class C : I1<C?>
|
||
|
{
|
||
|
void I1<C>.M<X>() {}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
**Conclusion**
|
||
|
|
||
|
If the member and the implementing type share the same `NonNullTypes` context,
|
||
|
the annotations must match, so the example directly above that mismatches
|
||
|
`C?` with `C` would be an error stating that the class `C` does not implement
|
||
|
the interface `I1<C?>`.
|
||
|
|
||
|
If the `NonNullTypes` context differs and the context on the outside is
|
||
|
`false` and the inside is `true`, there is no error or warning about the
|
||
|
mismatch, but a warning that `?` is disallowed in `NonNullTypes(false)`
|
||
|
context. If the context on the inside is `false` and the `outside` is true,
|
||
|
there is also no error or warning, this time because the interior context
|
||
|
causes `C` to be treated as oblivious, which can match with `C?`.
|
||
|
|
||
|
## Static local functions
|
||
|
|
||
|
Proposal: https://github.com/dotnet/csharplang/issues/1565
|
||
|
|
||
|
We like this proposal. One potentially confusing part is that static local
|
||
|
functions cannot capture local variables, even in containing static methods.
|
||
|
This is appropriate for the design, and we intend to keep it, but acknowledge
|
||
|
that it may be confusing at first.
|
||
|
|
||
|
**Q & A**
|
||
|
|
||
|
*Q:* If the proposal is accepted we would allow two states to be enforced: all
|
||
|
capturing is allowed and no capturing is allowed. Do we want to allow
|
||
|
intermediate states with something like capture lists?
|
||
|
|
||
|
**A:** No, this is as far as we go.
|
||
|
|
||
|
*Q:* Should we allow attributes too? That could be useful for related scenarios,
|
||
|
like P/Invoke.
|
||
|
|
||
|
**A:** Yes, we liked the idea originally, but it didn't make C# 7.0. We'd like
|
||
|
to finalize support for this.
|
||
|
|
||
|
*Q:* Should we relax shadowing rules? This isn't strictly related to the
|
||
|
proposal, but it seems like the restriction is more draconian with static local
|
||
|
functions because you cannot capture variables and instead have to come up with
|
||
|
new names if they are being passed as arguments.
|
||
|
|
||
|
**A:** We dislike differing shadowing behavior between static and non-static
|
||
|
local functions. We're warm to the idea of allowing all local function parameters
|
||
|
shadow locals in parent scopes. We're also interested in allowing shadowing in
|
||
|
lambdas. We would like to see a separate proposal on this to document the details.
|
||
|
|
||
|
*Q:* Can static local functions capture type parameters?
|
||
|
|
||
|
**A:** Yes.
|
||
|
|
||
|
*Q:* Do we want to allow "static lambdas"?
|
||
|
|
||
|
**A:** The value seems much lower since lambdas are usually much shorter. It's
|
||
|
also a more intrusive syntax inline to the call. Rejected.
|