How are Equals and GetHashCode implemented on anonymous types?
Asked Answered
M

1

11

The Help says this:

Anonymous types are class types that derive directly from object, and that cannot be cast to any type except object. The compiler provides a name for each anonymous type, although your application cannot access it. From the perspective of the common language runtime, an anonymous type is no different from any other reference type.

If two or more anonymous object initializers in an assembly specify a sequence of properties that are in the same order and that have the same names and types, the compiler treats the objects as instances of the same type. They share the same compiler-generated type information.

Because the Equals and GetHashCode methods on anonymous types are defined in terms of the Equals and GetHashCode methods of the properties, two instances of the same anonymous type are equal only if all their properties are equal.

These things are true, but how? The reference source shows explicitly how objects are compared (ReferenceEquals) and a type that 'derives directly from object' cannot have this special behaviour. It doesn't match the behavour of Equals in ValueType either.

So how is it done? How can anonymous types override Equals() and GetHashCode() without any visible overrides?

Micmac answered 25/7, 2016 at 2:11 Comment(0)
I
15

The compiler generates the GetHashCode() and Equals() overrides for you. For example, from this code:

class Program
{
    static void Main(string[] args)
    {
        var a = new { Text = "foo", Value = 17 };

        Console.WriteLine(a);
    }
}

You can find the generated anonymous type in the compiled .exe, where the methods look like this (this is the output from dotPeek…there's also ToString()):

  [DebuggerHidden]
  public override string ToString()
  {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.Append("{ Text = ");
    stringBuilder.Append((object) this.\u003CText\u003Ei__Field);
    stringBuilder.Append(", Value = ");
    stringBuilder.Append((object) this.\u003CValue\u003Ei__Field);
    stringBuilder.Append(" }");
    return ((object) stringBuilder).ToString();
  }

  [DebuggerHidden]
  public override bool Equals(object value)
  {
    var fAnonymousType0 = value as \u003C\u003Ef__AnonymousType0<\u003CText\u003Ej__TPar, \u003CValue\u003Ej__TPar>;
    return fAnonymousType0 != null && EqualityComparer<\u003CText\u003Ej__TPar>.Default.Equals(this.\u003CText\u003Ei__Field, fAnonymousType0.\u003CText\u003Ei__Field) && EqualityComparer<\u003CValue\u003Ej__TPar>.Default.Equals(this.\u003CValue\u003Ei__Field, fAnonymousType0.\u003CValue\u003Ei__Field);
  }

  [DebuggerHidden]
  public override int GetHashCode()
  {
    return -1521134295 * (-1521134295 * 512982588 + EqualityComparer<\u003CText\u003Ej__TPar>.Default.GetHashCode(this.\u003CText\u003Ei__Field)) + EqualityComparer<\u003CValue\u003Ej__TPar>.Default.GetHashCode(this.\u003CValue\u003Ei__Field);
  }

Related reading:
How does ToString on an anonymous type work?
Why anonymous types Equals implementation compares fields?
Equality for anonymous types
Why is ValueType.GetHashCode() implemented like it is?

None of those directly address your question, but they do provide some relevant insights into the specific implementations of these overrides.

Informed answered 25/7, 2016 at 2:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.