159 lines
No EOL
4.5 KiB
Markdown
159 lines
No EOL
4.5 KiB
Markdown
|
|
# C# Language Design Meeting for Jan. 15th, 2020
|
|
|
|
## Agenda
|
|
|
|
1. Working with data
|
|
2. Record feature breakdown
|
|
|
|
## Discussion
|
|
|
|
### Working with data
|
|
|
|
As we discuss records, we want to go over a design document we produced a number of years ago
|
|
called "working with data." This document lays out how, when we design features, we inherently
|
|
express a "path of least resistance," which consists of the features that seem easiest or
|
|
shortest to use to accomplish a given problem.
|
|
|
|
Link: https://github.com/dotnet/csharplang/issues/3107
|
|
|
|
The document argues that, as we find particular patterns to be more effective at building
|
|
software, we should make the forms we find to be more effective simpler or shorter to express in
|
|
the language. We should not "change" our opinions, meaning make old syntax illegal, but we should
|
|
"level the playing field" by making other forms simpler.
|
|
|
|
The conclusion of the design document is that we should favor
|
|
|
|
1. Immutable members in records by default
|
|
1. Any features from records that we separate should not make the simple syntax longer
|
|
|
|
### Record feature breakdown
|
|
|
|
We've also been working on breaking down the individual features
|
|
of records and determining how independent they can or should be.
|
|
|
|
Notes: https://github.com/dotnet/csharplang/issues/3137
|
|
|
|
There seem to be the following somewhat separable parts of records
|
|
|
|
1. Value-based equality
|
|
2. Construction boilerplate
|
|
3. Object initializers
|
|
4. Nondestructive mutation
|
|
5. Data-friendly defaults
|
|
|
|
#### Value equality
|
|
|
|
It's been proposed that a `key` modifier could be applied to signal that value-based equality is
|
|
being generated based on the members which have it. This works in many cases,
|
|
but if the absence of the `key` modifier means inherited equality, we're not sure
|
|
that's the semantics we want. It would also not allow value-based equality to be
|
|
"specified" in the base class in some sense, enforcing value equality for the deriving
|
|
type. Whether this is valuable or blocking is an open question.
|
|
|
|
#### Construction boilerplate
|
|
|
|
Creating a constructor to assign members of a container is one of the largest sources
|
|
of repetitive boilerplate, e.g.
|
|
|
|
```C#
|
|
public class Point
|
|
{
|
|
public int X { get; }
|
|
public int Y { get; }
|
|
public Point(int x, int y)
|
|
{
|
|
X = x;
|
|
Y = y;
|
|
}
|
|
}
|
|
```
|
|
|
|
You can imagine various points on this spectrum to simplify the boilerplate,
|
|
|
|
```C#
|
|
public class Point
|
|
{
|
|
public key int X { get; }
|
|
public key int Y { get; }
|
|
public Point(X, Y); // name matching and type absence implies initialization
|
|
}
|
|
```
|
|
|
|
Which removes the duplication of naming the same elements multiple times or,
|
|
|
|
```C#
|
|
public class Point(X, Y)
|
|
{
|
|
public key int X { get; }
|
|
public key int Y { get; }
|
|
}
|
|
```
|
|
|
|
Which removes the constructor name duplication and we could go further to remove
|
|
property name duplication,
|
|
|
|
```C#
|
|
public class Point(
|
|
public key int X { get; }
|
|
public key int Y { get; }
|
|
);
|
|
```
|
|
|
|
Going all the way to the original position deconstruction
|
|
|
|
```C#
|
|
public class Point(int X, int Y);
|
|
```
|
|
|
|
Where we pick a point in this space seems to correspond to the perceived benefits
|
|
of the orthogonality of the feature. If the construction shorthand is useful for
|
|
many scenarios outside of the record scenarios, it's practical to expand it.
|
|
|
|
### Object initializers
|
|
|
|
One benefit to object initializers is that they don't refer to a constructor directly,
|
|
only to the properties. This sidesteps a weakness in C#, where constructor initialization in inheritance requires repetition. Without constructors the simple
|
|
relation
|
|
|
|
```C#
|
|
public abstract class Person
|
|
{
|
|
public string Name { get; }
|
|
}
|
|
public class Student : Person
|
|
{
|
|
public string ID { get;}
|
|
}
|
|
```
|
|
|
|
has no repetition. Each class states only the properties that are essential, and
|
|
for derived classes all the base properties are inherited without repetition.
|
|
Once you add constructors this breaks down
|
|
|
|
```C#
|
|
public abstract class Person
|
|
{
|
|
public string Name { get; }
|
|
public Person(string name)
|
|
{
|
|
Name = name;
|
|
}
|
|
}
|
|
public class Student : Person
|
|
{
|
|
public string ID { get; }
|
|
public Student(string id, string name)
|
|
: base(name)
|
|
{
|
|
Id = id;
|
|
}
|
|
}
|
|
```
|
|
|
|
Now the derived classes have to repeat everything from the base, causing brittleness
|
|
along the boundary. If we were to imagine some improvement to object initializers,
|
|
then defining a constructor would not be required.
|
|
|
|
On the other hand, this also removes one of the main benefits for having a constructor,
|
|
namely that you can validate the whole state of the object before producing it. |