namespace System.Immutable {
using System.Collections;
public interface IImmutable { };
class Base: IEquatable<Base>, IImmutable {
public Base(int X, int Y) {
protected virtual bool ValueEquals(object obj) { Console.WriteLine(" Base.ValueEqual(object)"); return obj is Base o && X.Equals(o.X) && Y.Equals(o.Y); }
public virtual int CalcHashCode() {
Console.WriteLine(" Base.CalcHashCode");
return Tuple.Create(X, Y).GetHashCode();
public override bool Equals(object obj) => ReferenceEquals(this, obj) || !(obj is null) && GetType() == obj.GetType() && GetHashCode()==obj.GetHashCode() && ValueEquals(obj);
public bool Equals(Base o) { Console.WriteLine(" Base.Equals(Base)"); return object.Equals(this, o); }
public static bool operator ==(Base o1, Base o2) { Console.WriteLine(" Base=="); return object.Equals(o1, o2); }
public static bool operator !=(Base o1, Base o2) { Console.WriteLine(" Base!="); return !object.Equals(o1, o2); }
protected int? hashCache;
public override int GetHashCode() {
Console.WriteLine(" Base.GetHashCode");
if (hashCache is null) hashCache=CalcHashCode();
class Derived : Base, IEquatable<Derived>, IImmutable {
public Derived(int X, int Y, int Z, int K) : base(X, Y) {
protected override bool ValueEquals(object obj) {
Console.WriteLine(" Derived.ValueEqual(object)");
return obj is Derived o && base.ValueEquals(o) && Z.Equals(o.Z) && K.Equals(o.K);
public override int CalcHashCode() {
Console.WriteLine(" Derived.CalcHashCode");
return Tuple.Create(base.CalcHashCode(), Z, K).GetHashCode();
public bool Equals(Derived o) { Console.WriteLine(" Derived.Equals(Derived)"); return object.Equals(this, o); }
public static void Main()
Derived d1 = new Derived(0, 1, 2, 3), d2 = new Derived(0, 1, 4, 5), d3 = new Derived(0, 1, 2, 3), d4 = null;
Base b1 = d1, b2 = d2, b3 = d3, b4 = d4;
Console.WriteLine("d1==d2"); Console.WriteLine(" "+ (d1==d2));
Console.Write("d1.Equals(d2)"); Console.WriteLine(" " + d1.Equals(d2)+"\n");
Console.WriteLine("d1==d3"); Console.WriteLine(" "+ (d1==d3));
Console.Write("d1.Equals(d3)"); Console.WriteLine(" " + d1.Equals(d3)+"\n");
Console.WriteLine("d1==d4"); Console.WriteLine(" "+ (d1==d4));
Console.Write("d1.Equals(d4)"); Console.WriteLine(" " + d1.Equals(d4)+"\n");
Console.WriteLine("b1==b2"); Console.WriteLine(" "+ (b1==b2));
Console.Write("b1.Equals(b2)"); Console.WriteLine(" " + b1.Equals(b2)+"\n");
Console.WriteLine("b1==b3"); Console.WriteLine(" "+ (b1==b3));
Console.Write("b1.Equals(b3)"); Console.WriteLine(" " + b1.Equals(b3)+"\n");
Console.WriteLine("b1==b4"); Console.WriteLine(" "+ (b1==b4));
Console.Write("b1.Equals(b4)"); Console.WriteLine(" " + b1.Equals(b4)+"\n");
Console.WriteLine("b==d1"); Console.WriteLine(" "+ (b==b1));
Console.Write("b.Equals(d1)"); Console.WriteLine(" " + b.Equals(d1)+"\n");