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

Support C# 9 records #2217

Closed
dgrunwald opened this issue Nov 12, 2020 · 2 comments
Closed

Support C# 9 records #2217

dgrunwald opened this issue Nov 12, 2020 · 2 comments
Labels
Enhancement Areas for improvement

Comments

@dgrunwald
Copy link
Member

dgrunwald commented Nov 12, 2020

ILSpy should output the short record R(int a, int b); syntax where possible.

Compiler-generated members in records

Example records:

    record R(int A)
    {
        public int B;
        public int C { get; set; }
    }

    record Derived(int X) : R(X * 2);

EqualityContract

Generated unless user-declared.

// R:
protected virtual Type EqualityContract
{
	[CompilerGenerated]
	get
	{
		return typeof(R);
	}
}
// Derived:
protected override Type EqualityContract
{
	[CompilerGenerated]
	get
	{
		return typeof(Derived);
	}
}

Copy Constructor

Generated unless user-declared.

protected R(R original)
{
	b = original.b;
	C = original.C;
}
protected Derived(Derived original)
	: base(original)
{
	X = original.X;
}

Clone method

Always generated; cannot be named in user code.

public virtual R <Clone>$()
{
	return new R(this);
}
public override R <Clone>$()
{
	return new Derived(this);
}

Equals methods

Equals(object?) overload: always generated; error if also user-declared.
Equals(R?) overload: generated unless user-declared.

// R
public override bool Equals(object? obj)
{
	return Equals(obj as R);
}
public virtual bool Equals(R? other)
{
	return (object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<int>.Default.Equals(A, other.A) && EqualityComparer<int>.Default.Equals(B, other.B) && EqualityComparer<int>.Default.Equals(C, other.C);
}
// Derived
public override bool Equals(object? obj)
{
	return Equals(obj as Derived);
}
public sealed override bool Equals(R? other)
{
	return Equals((object?)other);
}
public virtual bool Equals(Derived? other)
{
	return base.Equals(other) && EqualityComparer<int>.Default.Equals(X, other!.X);
}

GetHashCode

Generated unless user-declared.

// R:
public override int GetHashCode()
{
	return ((EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(A)) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(B)) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(C);
}
// Derived:
public override int GetHashCode()
{
	return base.GetHashCode() * -1521134295 + EqualityComparer<int>.Default.GetHashCode(X);
}

operator==

Always generated; error if also user-declared.

public static bool operator ==(R? r1, R? r2)
{
	return (object)r1 == r2 || (r1?.Equals(r2) ?? false);
}

public static bool operator !=(R? r1, R? r2)
{
	return !(r1 == r2);
}

public static bool operator ==(Derived? r1, Derived? r2)
{
	return (object)r1 == r2 || (r1?.Equals(r2) ?? false);
}

public static bool operator !=(Derived? r1, Derived? r2)
{
	return !(r1 == r2);
}

ToString

Generated if not user-declared.

public override string ToString()
{
	StringBuilder stringBuilder = new StringBuilder();
	stringBuilder.Append("R");
	stringBuilder.Append(" { ");
	if (PrintMembers(stringBuilder))
	{
		stringBuilder.Append(" ");
	}
	stringBuilder.Append("}");
	return stringBuilder.ToString();
}
public override string ToString()
{
	StringBuilder stringBuilder = new StringBuilder();
	stringBuilder.Append("Derived");
	stringBuilder.Append(" { ");
	if (PrintMembers(stringBuilder))
	{
		stringBuilder.Append(" ");
	}
	stringBuilder.Append("}");
	return stringBuilder.ToString();
}

PrintMembers

Generated if not user-declared.

// R:
protected virtual bool PrintMembers(StringBuilder builder)
{
	builder.Append("A");
	builder.Append(" = ");
	builder.Append(A.ToString());
	builder.Append(", ");
	builder.Append("B");
	builder.Append(" = ");
	builder.Append(B.ToString());
	builder.Append(", ");
	builder.Append("C");
	builder.Append(" = ");
	builder.Append(C.ToString());
	return true;
}
// Derived:
protected override bool PrintMembers(StringBuilder builder)
{
	if (base.PrintMembers(builder))
	{
		builder.Append(", ");
	}
	builder.Append("X");
	builder.Append(" = ");
	builder.Append(X.ToString());
	return true;
}

Primary Constructor

Generated only for positional records. Error if also user-declared.

public R(int A)
{
	this.A = A;
	base..ctor();
}
public Derived(int X)
{
	this.X = X;
	base..ctor(X * 2);
}

Deconstruct

Generated only for positional records. Error if also user-declared.

public void Deconstruct(out int A)
{
	A = this.A;
}
public new void Deconstruct(out int X)
{
	X = this.X;
}
@dgrunwald dgrunwald added the Enhancement Areas for improvement label Nov 12, 2020
@dgrunwald
Copy link
Member Author

For the decompiler this means:

  • some members must always be omitted: operator==, operator!=, Equals(object)
  • some members can be omitted if equal to the compiler-generated implementation
  • the compiler-generated implementation of Equals, GetHashCode and PrintMembers depends on the order of fields/properties.
    • the interleaving of fields and properties is unknown to ILSpy and needs to be inferred from the members
    • Equals/GetHashCode only compares fields and auto-properties; but PrintMembers also prints non-automatic properties.
  • primary ctor/deconstruct act on a subset of the properties
    • this subset appears at the start in the list of members being compared

@dgrunwald
Copy link
Member Author

dgrunwald commented Jan 1, 2021

Still missing:

  • Primary constructor syntax
  • with-expressions
  • Tooltips still show class instead of record

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Enhancement Areas for improvement
Projects
None yet
Development

No branches or pull requests

2 participants