namespace System.Immutable {
using System.Collections;
public interface IImmutable { };
public static partial class ExtensionMethods {
public static bool ImmutableEquals<TSrc>(this TSrc inst, object obj, Func<TSrc, bool> f) where TSrc : IEquatable<TSrc>, IImmutable {
if (object.ReferenceEquals(inst, obj)) return true;
if (obj is null) return false;
if (!(obj is TSrc o)) return false;
if (obj.GetType() != inst.GetType()) return false;
if (inst.GetHashCode() != o.GetHashCode()) return false;
public static int ImmutableHash<TSrc>(this TSrc inst, ref int? hashCache, IStructuralEquatable tuple) where TSrc : IEquatable<TSrc>, IImmutable {
Console.WriteLine(" " + typeof(TSrc)+".GetHashCode");
hashCache = tuple.GetHashCode();
class Base: IEquatable<Base>, IImmutable {
public Base(int X, int Y) {
public override bool Equals(object obj) {
Console.WriteLine(" Base.Equals(object)");
return this.ImmutableEquals(obj, o => X.Equals(o.X) && Y.Equals(o.Y));
public override int GetHashCode() {
Console.WriteLine(" Base.GetHashCode");
return this.ImmutableHash(ref hashCache, Tuple.Create(X, Y));
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); }
class Derived : Base, IEquatable<Derived>, IImmutable {
public Derived(int X, int Y, int Z, int K) : base(X, Y) {
public override bool Equals(object obj) {
Console.WriteLine(" Derived.Equals(object)");
return this.ImmutableEquals(obj, o => base.Equals(o as object) && Z.Equals(o.Z) && K.Equals(o.K));
public override int GetHashCode() {
Console.WriteLine(" Derived.GetHashCode");
return this.ImmutableHash(ref hashCache, Tuple.Create(base.GetHashCode(), Z, K));
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");