
# 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.
