
# C# Language Design Meeting for Dec. 11, 2019

1. Design review feedback

## Discussion

We got feedback from the design review that we shouldn't try to conflate too many problems. If
we want to make it easier to support structural equality, we should see if it's possible to
address directly, without requiring the other features of records. One suggestion was to take
inspiration from `VB`, which allows the `key` modifier to be added to VB anonymous types to
indicate structural equality with the members used as the keys.

We took that advice and looked at a sketch of what that could look like:

```C#
class C
{
    public key string Item1 { get; }
    public string Item2 { get; }
}
```

The `key` modifier would be used to control generated equality, such that all members marked
`key` would be compared for equality in an `Equals` override (using the same pattern as in the
original records proposal).

The above code sample certainly looks simple, but unfortunately it's not sufficient for
real-world code. Both `Item1` and `Item2` are `get`-only autoproperties, meaning that as-is there
is no way to initialize those members. A working example looks more like:

```C#
class C
{
    public key string Item1 { get; }
    public string Item2 { get; }
    public C(string item1, string item2)
    {
        Item1 = item1;
        Item2 = item2;
    }
}
```

This is significantly longer than the original sample, grows with the number of properties, and
is repetitive. The latter is particularly problematic, as repetitive boilerplate is often a source
of hard-to-see bugs or typos.

Worse, we've seen that when construction becomes laborious, users resort to making their types
mutable instead of writing out the constructor, e.g.

```C#
class C
{
    public key string Item1 { get; set; }
    public string Item2 { get; }
}
```

This is unfortunate in most code, but it's worrying when combined with structural equality. When
used in Dictionaries, mutable types with structural equality are an anti-pattern because the hash
code of the type changes, causing the type to "disappear" from the Dictionary. It's one thing if
the user opts-in to this risk, knowing that they need to be careful, and a completely different
situation if the language encourages a dangerous pattern.

**Conclusion**

This is an interesting design point that we think we'll incorporate. We've also agreed that we
have a hard design requirement: if we provide a feature for easy structural equality, we must
provide a more convenient syntax for constructing immutable types in the same release. To do
otherwise would be to effectively make a trap for users.

## Nominal vs Positional

We also continued to explore the space of nominal vs. positional records. An insight from the
previous discussion is that nominal vs. positional records are really about improving syntax
for type construction. Positional records are about taking the existing type construction,
constructors, and giving them a shorter syntax. Nominal records are about identifying some
weaknesses of the existing construction system and providing a new feature to support them. In
both cases, though, the proposed features shorten construction by avoiding repeating the member
declarations and the assignments.

We also had the following notes, in no particular order:

* For positional constructors, it's important to consider primary constructors. We have one
proposal for primary constructors, but have not had a discussion on whether we want them, and if
the syntax is worth taking away from the record syntax.

* If the `initonly` keyword is required, even for common cases, this is about
as expensive syntax-wise as a setter. It may be a few more characters, but it
keeps things on one line, as opposed to the multi-line constructors that are
required right now.

* There's an opposition between evolving existing data types and picking and choosing features when
you need them, but also providing a simple syntax for the most common case. Certainly positional
records solve a common case. The question is how common that case is.

* Positional records use existing initialization strategy (construction) which is fairly
well-understood. The initonly feature, by contrast, will force us to reexamine some assumptions.
For instance, constructors take all inputs at once, meaning that you can enforce requirements
between them at construction time. `initonly` overrides properties after construction, and
one-by-one, so it's not possible, or not obvious, how to provide this functionality.

* There are mixed feelings on the requirements of what features will be available individually, and
which are separable. Some people feel that addressing the most common case is sufficient for
providing the value of the feature, namely that there can be a single "record" which provides
immutable construction and value equality, and that is the only way to access these features.
Others think that the features need to be adoptable independently: existing types need to be able
to adopt generated equality without adopting immutable construction, while immutable construction
needs to be available without structural equality.

One conclusion is that no proposal will solve all record-related problems. This is fine. We also
have general agreement that there should be a convenient shorthand for the combination of most or
all of the features (simple immutable construction, structural equality, etc.). Notably we don't
all agree on whether or not structural equality will be the most common type of equality for
records, but the shortest record form may include structural equality for other language design
reasons.