Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ToString and PrintMembers to records #3762

Merged
merged 10 commits into from
Aug 7, 2020
120 changes: 116 additions & 4 deletions proposals/csharp-9.0/records.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ record R2(T1 P1, T2 P2) : R1(P1);
record R2(T1 P1, T2 P2, T3 P3) : R2(P1, P2);
```

For those record types, the synthesized members would be something like:
For those record types, the synthesized equality members would be something like:
```C#
class R1 : IEquatable<R1>
{
public T1 P1 { get; set; }
public T1 P1 { get; init; }
protected virtual Type EqualityContract => typeof(R1);
public override bool Equals(object? obj) => Equals(obj as R1);
public virtual bool Equals(R1? other)
Expand All @@ -134,7 +134,7 @@ class R1 : IEquatable<R1>

class R2 : R1, IEquatable<R2>
{
public T2 P2 { get; set; }
public T2 P2 { get; init; }
protected override Type EqualityContract => typeof(R2);
public override bool Equals(object? obj) => Equals(obj as R2);
public sealed override bool Equals(R1? other) => Equals((object?)other);
Expand All @@ -152,7 +152,7 @@ class R2 : R1, IEquatable<R2>

class R3 : R2, IEquatable<R3>
{
public T3 P3 { get; set; }
public T3 P3 { get; init; }
protected override Type EqualityContract => typeof(R3);
public override bool Equals(object? obj) => Equals(obj as R3);
public sealed override bool Equals(R2? other) => Equals((object?)other);
Expand Down Expand Up @@ -197,6 +197,118 @@ If the containing record is abstract, the synthesized clone method is also abstr
If the "clone" method is not abstract, it returns the result of a call to a copy constructor.


### Printing members: print and ToString methods

If the record is derived from `object`, the record includes a synthesized "print" method equivalent to a method declared as follows, but with a compiler-reserved name:
jcouv marked this conversation as resolved.
Show resolved Hide resolved
```C#
bool print(System.StringBuilder builder);
```
The method is `private` if the record type is `sealed`. Otherwise, the method is `virtual` and `protected`.

The method:
1. for each of the record's printable members (public field and property members), appends that member's name followed by " = " followed by the member's value: `this.member`, separated with ", ",
2. return true if the record has printable members.

If the record type is derived from a base record `Base`, the record includes a synthesized override equivalent to a "print" method declared as follows:
```C#
protected override bool print(StringBuilder builder);
```

If the record has no printable members, the method calls the base "print" method with one argument (its `builder` parameter) and returns the result.

Otherwise, the method:
1. calls the base "print" method with one argument (its `builder` parameter),
2. if the "print" method returned true, append ", " to the builder,
3. for each of the record's printable members, appends that member's name followed by " = " followed by the member's value: `this.member`, separated with ", ",
4. return true.

The record includes a synthesized method equivalent to a method declared as follows:
333fred marked this conversation as resolved.
Show resolved Hide resolved
```C#
public override string ToString();
```
jcouv marked this conversation as resolved.
Show resolved Hide resolved

The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or if the explicit declaration doesn't allow overiding it in a derived type and the record type is not `sealed`. It is an error if either synthesized, or explicitly declared method doesn't override `object.ToString()` (for example, due to shadowing in intermediate base types, etc.).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or if the explicit declaration doesn't allow overiding it in a derived type and the record type is not `sealed`. It is an error if either synthesized, or explicitly declared method doesn't override `object.ToString()` (for example, due to shadowing in intermediate base types, etc.).
The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or if the explicit declaration doesn't allow overiding it in a derived type and the record type is not `sealed`. It is an error if either synthesized or explicitly declared method doesn't override `object.ToString()` (for example, due to shadowing in intermediate base types, etc.).


The synthesized method:
1. creates a `StringBuilder` instance,
jcouv marked this conversation as resolved.
Show resolved Hide resolved
2. appends the record name to the builder, followed by " { ",
jcouv marked this conversation as resolved.
Show resolved Hide resolved
3. invokes the record's "print" method giving it the builder,
4. appends " }",
3. returns the builder's contents with `builder.ToString()`.

For example, consider the following record types:

``` csharp
record R1(T1 P1);
record R2(T1 P1, T2 P2, T3 P3) : R1(P1);
```

For those record types, the synthesized printing members would be something like:

```C#
class R1 : IEquatable<R1>
{
public T1 P1 { get; init; }

protected virtual bool print(StringBuilder builder)
{
builder.Append(nameof(P1));
builder.Append(" = ");
builder.Append(this.P1);
jcouv marked this conversation as resolved.
Show resolved Hide resolved

return true;
}

public override string ToString()
{
var builder = new StringBuilder();
builder.Append(nameof(R1));
builder.Append(" { ");

print(builder);

builder.Append(" }");
return builder.ToString();
}
}

class R2 : R1, IEquatable<R2>
{
public T2 P2 { get; init; }
public T3 P3 { get; init; }

protected override void print(StringBuilder builder)
{
if (base.print(builder))
builder.Append(", ");

builder.Append(nameof(P2));
builder.Append(" = ");
builder.Append(this.P2);

builder.Append(", ");
jcouv marked this conversation as resolved.
Show resolved Hide resolved

builder.Append(nameof(P3));
builder.Append(" = ");
builder.Append(this.P3);

return true;
}

public override string ToString()
{
var builder = new StringBuilder();
builder.Append(nameof(R2));
builder.Append(" { ");

print(builder);

builder.Append(" }");
return builder.ToString();
}
}
```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra newline.

## Positional record members

In addition to the above members, records with a parameter list ("positional records") synthesize
Expand Down