Add LDM notes for 2020-06-15
This commit is contained in:
parent
eb00bb077e
commit
a05203115d
197
meetings/2020/LDM-2020-06-15.md
Normal file
197
meetings/2020/LDM-2020-06-15.md
Normal file
|
@ -0,0 +1,197 @@
|
|||
|
||||
# C# Language Design Meeting for June 15, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. `modreq` for init accessors
|
||||
|
||||
1. Initializing `readonly` fields in same type
|
||||
|
||||
1. `init` methods
|
||||
|
||||
1. Equality dispatch
|
||||
|
||||
1. Confirming some previous design decisions
|
||||
|
||||
1. `IEnumerable.Current`
|
||||
|
||||
## Discussion
|
||||
|
||||
### `modreq` for init accessors
|
||||
|
||||
We've confirmed that the modreq design for `init` accessors:
|
||||
|
||||
- The modreq type `IsExternalInit` will be present in .NET 5.0, and will be recognized if
|
||||
defined in source
|
||||
|
||||
- The feature will only be fully supported in .NET 5.0
|
||||
|
||||
- Usage of the property (including the getter) will not be possible on older compilers, but
|
||||
if the compiler is upgraded (even if an older framework is being used), the getter will be
|
||||
usable
|
||||
|
||||
### Initializing `readonly` fields in same type
|
||||
|
||||
We previously removed `init` fields from the design proposal because we didn't think it was
|
||||
necessary for the records feature and because we didn't want to allow fields which were
|
||||
declared `readonly` before records to suddenly be settable externally in C# 9, contrary to
|
||||
the author's intent.
|
||||
|
||||
One extension would be to allow `readonly` fields to be set in an object initializer only inside
|
||||
the type. In this case you could still use object initializers to set readonly fields in
|
||||
static factories, but because they would be a part of your type you would always know the intent
|
||||
of the `readonly` modifier. For instance,
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public readonly string? ReadonlyField;
|
||||
|
||||
public static C Create()
|
||||
=> new C() { ReadonlyField = null; };
|
||||
}
|
||||
```
|
||||
|
||||
On the other hand, we may not need a new feature for many of these scenarios. An init-only
|
||||
property with a private `init` accessor behaves similarly.
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public string? ReadonlyProp { get; private init; }
|
||||
|
||||
public static C Create()
|
||||
=> new C() { ReadonlyProp = null; };
|
||||
}
|
||||
```
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We still think `readonly` fields are interesting, but we're not sure of the scenarios yet.
|
||||
Let's keep this on the table, but leave it for a later design meeting.
|
||||
|
||||
### `init` methods
|
||||
|
||||
We previously considered having `init` methods which could modify `readonly` members just
|
||||
like `init` accessors. This could enable scenarios like "immutable collection initializers",
|
||||
where members can be added via an `init` Add method.
|
||||
|
||||
The problem is that the vast majority of immutable collections in the framework today would be
|
||||
unable to adopt this pattern. Collection initializers are hardcoded to use the name `Add` today
|
||||
and almost all the immutable collections already have `Add` methods that are meant for a different
|
||||
purpose -- they return a copy of the collection with the added item.
|
||||
|
||||
If we naively extend `init` to collection initializers most immutable collections wouldn't be able
|
||||
to adopt them because they couldn't make their `Add` methods `init`-only, and no other method name
|
||||
is allowed in a collection initializer. In addition, some types, like ImmutableArrays, would be
|
||||
forced to implement init-only collection initializers very inefficiently, by declaring a new array
|
||||
and copying each item every time `Add` is called.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We're still very interested in the feature, but we need to determine how we can add it in a way
|
||||
that provides a path forward for our existing collections.
|
||||
|
||||
### Equality dispatch
|
||||
|
||||
We have an equality implementation that we think is functional, but we're not sure it's the most
|
||||
efficient implementation. Consider the following chain of types:
|
||||
|
||||
```C#
|
||||
class R1
|
||||
{
|
||||
public override bool Equals(object other)
|
||||
=> Equals(other as R1);
|
||||
public virtual bool Equals(R1 other)
|
||||
=> !(other is null) &&
|
||||
this.EqualityContract == other.EqualityContract
|
||||
/* && compare fields */;
|
||||
}
|
||||
class R2 : R1
|
||||
{
|
||||
public override bool Equals(object other)
|
||||
=> Equals(other as R2);
|
||||
|
||||
public override bool Equals(R1 other)
|
||||
=> Equals(other as R2);
|
||||
|
||||
public virtual bool Equals(R2 other)
|
||||
=> base.Equals((R1)other)
|
||||
/* && compare fields */;
|
||||
}
|
||||
class R3 : R2
|
||||
{
|
||||
public override bool Equals(object other)
|
||||
=> Equals(other as R3);
|
||||
|
||||
public override bool Equals(R1 other)
|
||||
=> Equals(other as R3);
|
||||
|
||||
public override bool Equals(R2 other)
|
||||
=> Equals(other as R3);
|
||||
|
||||
public virtual bool Equals(R2 other)
|
||||
=> base.Equals((R1)other)
|
||||
/* && compare fields */;
|
||||
}
|
||||
```
|
||||
|
||||
The benefit of the above strategy is that each virtual call goes directly
|
||||
to the appropriate implementation method for the runtime type. The drawback
|
||||
is that we're effectively generating a quadratic number of methods and overrides
|
||||
based on the number of derived records.
|
||||
|
||||
One alternative is that we could not override the Equals methods of anything
|
||||
except our `base`. This would cause more virtual calls to reach the implementation,
|
||||
but reduce the number of overrides.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We need to do a deep dive on this issue and explore all the scenarios. We'll come
|
||||
back once we've outlined all the options and come up with a recommendation.
|
||||
|
||||
### Affirming some previous decisions
|
||||
|
||||
Proposals for copy constructors
|
||||
|
||||
- Do not include initializers (including for user-written copy constructors)
|
||||
|
||||
- Require delegation to a base copy constructor or `object` constructor
|
||||
|
||||
- If the implementation is synthesized, this behavior is synthesized
|
||||
|
||||
Proposal for Deconstruct:
|
||||
|
||||
- Doesn't delegate to a base Deconstruct method
|
||||
|
||||
- Synthesized body is equivalent to a sequence of assignments of member
|
||||
accesses. If any of these assignments would be an error, an error is produced.
|
||||
|
||||
It's also proposed that any members which are either dispatched to in a derived record
|
||||
or expected to be overridden in a derived record will produce an error for synthesized
|
||||
implementations if the required base member is not found. This includes if the base
|
||||
member was not present in the immediate base, but was inherited instead. For some situations
|
||||
this may mean that the user can write a substituted implementation for that synthesized
|
||||
member, but for the copy constructor this effectively forbids record inheritance, since
|
||||
the valid base member must be present even in a user-defined implementation.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
All of the above decisions are upheld.
|
||||
|
||||
### Non-generic IEnumerable
|
||||
|
||||
Currently in the framework `IEnumerable.Current` (the non-generic interface) is annotated to
|
||||
return `object?`. This produces a lot of warnings in legacy code that `foreach` over the result
|
||||
with types like `string`, which is non-nullable. We have two proposals to resolve this:
|
||||
|
||||
- Un-annotate `IEnumerable.Current`. This will keep the member nullable-oblivious and no warnings
|
||||
will be generated, even if the property is called directly
|
||||
|
||||
- Special-case the compiler behavior for `foreach` on `IEnumerable` to suppress nullable warnings
|
||||
when calling `IEnumerable.Current`
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Overall, we prefer un-annotation. Since this interface is essentially legacy, we feel that
|
||||
providing nullable analysis is potentially harmful and rarely beneficial.
|
|
@ -32,17 +32,6 @@
|
|||
- `T??` parsing ambiguities: `(X?? x, Y?? y) t;`, `using (T?? t = u) { }`, `F((T?? t) => t);`
|
||||
- null-checked parameters syntax (https://github.com/dotnet/csharplang/issues/2145)
|
||||
|
||||
## Jun 15, 2020
|
||||
|
||||
- init-only: confirm metadata encoding (IsExternalInit modreq) with compat implications on records (Jared/Julien)
|
||||
- init-only: should `_ = new C() { readonlyField = null };` be allowed in a method on type C? (Jared/Julien)
|
||||
- init-only: init-only methods ? `init void Init()` (Jared/Julien)
|
||||
- https://github.com/dotnet/csharplang/issues/3214 Nullability of iteration variable in non-generic foreach (Stephen)
|
||||
|
||||
## Jun 10, 2020
|
||||
|
||||
- https://github.com/dotnet/csharplang/issues/1711 Roles and extensions (Mads)
|
||||
|
||||
## Jun 3, 2020
|
||||
|
||||
- allow suppression on `return someBoolValue!;` (issue https://github.com/dotnet/roslyn/issues/44080, Julien)
|
||||
|
@ -85,6 +74,30 @@
|
|||
|
||||
Overview of meetings and agendas for 2020
|
||||
|
||||
## Jun 15, 2020
|
||||
|
||||
[C# Language Design Notes for June 15, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-15.md)
|
||||
|
||||
Record:
|
||||
|
||||
1. `modreq` for init accessors
|
||||
|
||||
1. Initializing `readonly` fields in same type
|
||||
|
||||
1. `init` methods
|
||||
|
||||
1. Equality dispatch
|
||||
|
||||
1. Confirming some previous design decisions
|
||||
|
||||
1. `IEnumerable.Current`
|
||||
|
||||
## Jun 10, 2020
|
||||
|
||||
[C# Language Design Notes for June 15, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-15.md)
|
||||
|
||||
- https://github.com/dotnet/csharplang/issues/1711 Roles and extensions
|
||||
|
||||
## Jun 1, 2020
|
||||
|
||||
[C# Language Design Notes for June 1, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-01.md)
|
||||
|
|
Loading…
Reference in a new issue