Add notes for Jan 8th, 2020
This commit is contained in:
Родитель
0513712252
Коммит
064e3b810a
|
@ -0,0 +1,188 @@
|
|||
|
||||
# C# Language Design Meeting for Jan. 8, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Unconstrained type parameter annotation
|
||||
2. Covariant returns
|
||||
|
||||
## Discussion
|
||||
|
||||
### Unconstrained type parameter annotation T??
|
||||
|
||||
You can only use `T?` when is constrained to be a reference type or a value type. On the other
|
||||
hand, you can only use `T??` when `T` is unconstrained.
|
||||
|
||||
The question is what to use for the following:
|
||||
|
||||
```C#
|
||||
abstract class A<T>
|
||||
{
|
||||
internal abstract void F<U>(ref U?? u) where U : T;
|
||||
}
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // Is ?? allowed or required?
|
||||
}
|
||||
class B2 : A<int>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // Is ?? allowed or required?
|
||||
}
|
||||
class B3 : A<int?>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // Is ?? allowed or required?
|
||||
}
|
||||
```
|
||||
|
||||
Our understanding is that this would be:
|
||||
|
||||
```C#
|
||||
abstract class A<T>
|
||||
{
|
||||
internal abstract void F<U>(ref U?? u) where U : T;
|
||||
}
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default;
|
||||
// We think the correct annotation is
|
||||
// void F<U>(ref U? u) where U : class
|
||||
// because the type parameter is no longer unconstrained. The `where U : class`
|
||||
// constraint is required, as U? would otherwise mean U : struct
|
||||
}
|
||||
// We may want to allow U?? even in the above case, so
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // allowed?
|
||||
// This would not require the `where U : class` constraint because `U??` cannot
|
||||
// be confused with `Nullable<U>`
|
||||
}
|
||||
class B2 : A<int>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default;
|
||||
// The correct annotation is
|
||||
// void F<U>(ref U u)
|
||||
// We could allow
|
||||
// void F<U>(ref U?? u)
|
||||
// although it would be redundant
|
||||
}
|
||||
class B3 : A<int?>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default;
|
||||
// The correct annotation is
|
||||
// void F<U>(ref U u)
|
||||
// We could allow
|
||||
// void F<U>(ref U?? u)
|
||||
// although it would be redundant
|
||||
```
|
||||
|
||||
In the above, we're wondering whether we should allow `??` without warning even in cases where
|
||||
there's existing syntax to represent the semantics. One benefit is that you could write
|
||||
|
||||
```C#
|
||||
abstract class A<T>
|
||||
{
|
||||
internal abstract F<U>(ref U?? u) where U : T;
|
||||
}
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override F<U>(ref U?? u) { }
|
||||
}
|
||||
```
|
||||
|
||||
Since the override cannot be confused with `U?`, there's no need for the `where U : class`. On the
|
||||
other hand, the benefit seems marginal and it's not needed to represent the semantics. It could
|
||||
also be added later.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We like the syntax `??` to represent the "maybe default" semantics. We think that `??` should be
|
||||
allowed in the cases where we have other syntax to represent the semantics. A warning will be
|
||||
provided and hopefully a code fix to move the code to the "more canonical" form. The syntax `??`
|
||||
is only legal on type parameters in a nullable-enabled context.
|
||||
|
||||
We considered using the `?` syntax the represent the same semantics, but ruled it out for a couple reasons:
|
||||
|
||||
1. It's technically difficult to achieve. There are two technical limitations in the compiler.
|
||||
The first is design history where a type parameter ending in `?` is assumed to be a struct. This
|
||||
has been true in the compiler all the way until nullable reference types in the last release. The
|
||||
second problem is that many pieces of C# binding are very sensitive to being asked if a type is a
|
||||
value or reference type and asking the question can often lead to cycles if asked before the answer
|
||||
is absolutely necessary. However, `T?` means different things if `T` is a struct or unconstrained, so
|
||||
finding the safe place to ask is difficult.
|
||||
|
||||
2. Unresolved design problems. In overriding `T?` means `where T : struct`, going back to the beginning of
|
||||
`Nullable<T>`. This already caused problems with `T? where T : class` in C# 8, which is why we introduced
|
||||
a feature where you could specify `T? where T : class` in an override, contrary to past C# design where
|
||||
constraints are not allowed to be re-specified in overrides. To accommodate overloads for unconstrained
|
||||
`T?` we would have to introduce a new type of explicit constraint meaning *unconstrained*. We don't have
|
||||
a syntax for that, and don't particularly want one.
|
||||
|
||||
3. Confusion with a different feature. If we use the `T?` syntax, the following would be legal:
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public T? M<T>() where T : notnull { ... }
|
||||
}
|
||||
```
|
||||
|
||||
what you may think is that `M` returns a nullable reference type if `T` is a reference type, and a nullable
|
||||
value type if `T` is a value type (i.e., `Nullable<T>`). However, that's *not* what this feature is. This
|
||||
feature is essentially *maybe default*, meaning that `T?` may contain the value `default(T)`. For a reference
|
||||
type this would be `null`, but for a value type this would be the zero value, not `null`.
|
||||
|
||||
Moreover, this seems like a useful feature, that would be ruled out if we used the syntax for something else.
|
||||
Consider a method similar to `FirstOrDefault`, `FirstOrNull`:
|
||||
|
||||
```C#
|
||||
public static T? FirstOrNull<T>(this IEnumerable<T> e) where T : notnull
|
||||
```
|
||||
|
||||
The benefit is that there is a single sentinel value, `null`, and that the full set of values in a struct
|
||||
could be represented. In `FirstOrDefault`, if the input is `IEnumerable<int>` there is no way to distinguish
|
||||
a non-empty enumerable with first value of `0`, or an empty enumerable. With `FirstOrNull` you can distinguish
|
||||
these cases in a single call, as long as `null` is not a valid value in the type.
|
||||
|
||||
Due to CLR restrictions it is not possible to implement this feature in the obvious way, so this feature may
|
||||
never be implemented, but we would like to prevent confusion and keep the syntax available in case we ever
|
||||
figure out how we'd like to implement it.
|
||||
|
||||
### Covariant returns
|
||||
|
||||
We looked at the proposal in #2844.
|
||||
|
||||
There's a proposal that for the following
|
||||
|
||||
```C#
|
||||
interface I1
|
||||
{
|
||||
object M();
|
||||
}
|
||||
interface I2 : I1
|
||||
{
|
||||
string M() => null;
|
||||
}
|
||||
```
|
||||
|
||||
`I2.M` would be illegal as it is, because this is an interface *implementation*, not an
|
||||
*override*. Interface implementation would not change return types, while overriding would. Thus
|
||||
we would allow
|
||||
|
||||
```C#
|
||||
interface I1
|
||||
{
|
||||
object M();
|
||||
}
|
||||
interface I2 : I1
|
||||
{
|
||||
override string M() => null;
|
||||
}
|
||||
```
|
||||
|
||||
which would allow the return type to change. However, it would override *all* `M`s, since
|
||||
explicit implementation is not allowed.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We like it in principle and would like to move forward. There may be some details around
|
||||
interface implementation vs. overriding to work out.
|
|
@ -30,16 +30,17 @@
|
|||
|
||||
- Records: Paging back in the previous proposal (Andy)
|
||||
|
||||
## Jan 8, 2020
|
||||
|
||||
- https://github.com/dotnet/csharplang/pull/3035 Unconstrained type parameter annotation: `T??` (Chuck)
|
||||
- https://github.com/dotnet/csharplang/projects/4#column-4649189 Triage recently championed features
|
||||
- https://github.com/dotnet/csharplang/issues/2844 Covariant Return Types (Neal)
|
||||
|
||||
# C# Language Design Notes for 2020
|
||||
|
||||
Overview of meetings and agendas for 2020
|
||||
|
||||
## Jan 8, 2020
|
||||
|
||||
[C# Language Design Notes for Jan 8, 2020](LDM-2020-01-08.md)
|
||||
|
||||
1. Unconstrained type parameter annotation
|
||||
2. Covariant returns
|
||||
|
||||
## Jan 6, 2020
|
||||
|
||||
[C# Language Design Notes for Jan 6, 2020](LDM-2020-01-06.md)
|
||||
|
|
Загрузка…
Ссылка в новой задаче