Why Does the Equals Implementation for Anonymous Types Compare Fields

Why does the Equals implementation for anonymous types compare fields?

Anonymous type instances are immutable data values without behavior or identity. It doesn't make much sense to reference-compare them. In that context I think it is entirely reasonable to generate structural equality comparisons for them.

If you want to switch the comparison behavior to something custom (reference comparison or case-insensitivity) you can use Resharper to convert the anonymous type to a named class. Resharper can also generate equality members.

There is also a very practical reason to do this: Anonymous types are convenient to use as hash keys in LINQ joins and groupings. For that reason they require semantically correct Equals and GetHashCode implementations.

Equality for anonymous types

== doesn't call Equals, it looks for == overloaded operator. Since anonymous types doesn't have overloaded == operator, so C# uses reference comparison for it.

But with Equals it compares field values. That is why the result between == and Equals differ.

Anonymous Types (C# Programming Guide)

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.

How are Equals and GetHashCode implemented on anonymous types?

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.

Anonymous Type Member Equality

I believe the following code should generate two instances of the same anonymous type

No, it generates two instances of List<T>, with the same content.

So when you execute this:

if (letterFreq1.Equals(letterFreq2))

you're invoking the .Equals method on List<T> objects, which does not override the method inherited from System.Object, and thus do reference comparison.

You're right, however, in that the anonymous types would compare equal, and the two lists does in fact have the same content, but the list objects doesn't do content comparison by themselves, so they will compare as different.

If you were to coax the compiler into converting the two into the same type of collection, such as:

var letterFreq1 = CountLetters("aabbbc") as IEnumerable<object>;
var letterFreq2 = CountLetters("aabbbc") as IEnumerable<object>;

then you could compare their contents:

if (letterFreq1.SequenceEqual(letterFreq2))

but then you need to first know that they are collections, so depending on how general/generic your code is supposed to be, this may or may not be a solution for you.


My real advice, however, would be to avoid using anonymous types in this case. They're nice when used with local variables, but as you notice, when they escape the confines of a method they become very cumbersome to work with.

A better replacement would be a tuple:

void Main(string[] args)
{
var letterFreq1 = CountLetters("aabbbc");
var letterFreq2 = CountLetters("aabbbc");

if (letterFreq1.SequenceEqual(letterFreq2))
Console.WriteLine("Is anagram");
else
Console.WriteLine("Is not an anagram");
}

public static List<(char Letter, int Count)> CountLetters(string input)
=> input.ToCharArray()
.GroupBy(x => x)
.Select(x => (Letter: x.Key, Count : x.Count()))
.OrderBy(x => x.Letter)
.ToList();

An even better solution would be to create a named type for this, but again, depending on your situation this may or may not be a good solution for you.

Calling Equals on anonymous type depends on which assembly the object was created in

It's an implementation detail that you don't understand.

If you use an anonymous type, the compiler has to generate a new type (with an unspeakable name, such as <>f__AnonymousType0<<A>j__TPar>), and it generates this type in the assembly which uses it.

It will use that same generated type for all usages of anonymous types with the same structure within that assembly. However, each assembly will have its own anonymous type definitions: there's no way to share them across assemblies. Of course the way around this, as you discovered, is to pass them around as object.

This restriction is one of the main reasons why there's no way of exposing anonymous types: you can't return them from methods, have them as fields etc. It would cause all sorts of issues if you could pass them around between assemblies.

You can see that at work in SharpLab, where:

var x = new { A = 1 };

causes this type to be generated in the same assembly:

internal sealed class <>f__AnonymousType0<<A>j__TPar>
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly <A>j__TPar <A>i__Field;

public <A>j__TPar A
{
get
{
return <A>i__Field;
}
}

[DebuggerHidden]
public <>f__AnonymousType0(<A>j__TPar A)
{
<A>i__Field = A;
}

[DebuggerHidden]
public override bool Equals(object value)
{
global::<>f__AnonymousType0<<A>j__TPar> anon = value as global::<>f__AnonymousType0<<A>j__TPar>;
if (anon != null)
{
return EqualityComparer<<A>j__TPar>.Default.Equals(<A>i__Field, anon.<A>i__Field);
}
return false;
}

[DebuggerHidden]
public override int GetHashCode()
{
return -1711670909 * -1521134295 + EqualityComparer<<A>j__TPar>.Default.GetHashCode(<A>i__Field);
}

[DebuggerHidden]
public override string ToString()
{
object[] obj = new object[1];
<A>j__TPar val = <A>i__Field;
obj[0] = ((val != null) ? val.ToString() : null);
return string.Format(null, "{{ A = {0} }}", obj);
}
}

ValueTuple had the same challenges around wanting to define types anonymously but still pass them between assemblies, and solved it a different way: by defining ValueTuple<..> in the BCL, and using compiler magic to pretend that their properties have names other than Item1, Item2, etc.

Asserting for equality anonymous types

Even though the anonymous types are accessible in your test project, that doesn't mean they'll be used when you write new { ... }.

If you look at actionResult.Value.GetType() and expectedActionResult.Value.GetType() I strongly suspect you'll see that they're different types from different assemblies.

The simplest workaround in this case is probably just to compare the resulting JSON instead.

Why does compiler generate different classes for anonymous types if the order of fields is different

So the reason for the design decision was ToString. An anonymous type returns a different string accoding to the order. Read Eric Lippert's blog.

{ a = 5, b = 7 }
{ b = 7, a = 6 }

Demo

Is it safe to use GetHashCode to compare identical Anonymous types?

Interesting question. The specification defines that Equals and GetHashcode (note the typo in the specification!) methods will behave for instances of the same type, however the implementation is not defined. As it happens, the current MS C# compiler implements this using magic numbers like a seed of -1134271262 and a multiplier of -1521134295. But that is not part of the specification. Theoretically that could change radically between C# compiler versions and it would still meet what it needs to. So if the 2 assemblies are not compiled by the same compiler, there is no guarantee. Indeed, it would be "valid" (but unlikely) for the compiler to think up a new seed value every time it compiles.

Personally, I would look at using IL or Expression techniques to do this. Comparing similarly-shaped objects member-wise by name is fairly easy to do with Expression.

For info, I've also looked at how mcs (the Mono compiler) implements GetHashCode, and it is different; instead of seed and multiplier, it uses a combination of seed, xor, multiplier, shifts and additions. So the same type compiled by Microsoft and Mono will have very different GetHashCode.

static class Program {
static void Main() {
var obj = new { A = "abc", B = 123 };
System.Console.WriteLine(obj.GetHashCode());
}
}
  • Mono: -2077468848
  • Microsoft: -617335881

Basically, I do not think you can guarantee this.


How about:

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
class Foo
{
public string A { get; set; }
public int B; // note a field!
static void Main()
{
var obj1 = new { A = "abc", B = 123 };
var obj2 = new Foo { A = "abc", B = 123 };
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // True

obj1 = new { A = "abc", B = 123 };
obj2 = new Foo { A = "abc", B = 456 };
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // False

obj1 = new { A = "def", B = 123 };
obj2 = new Foo { A = "abc", B = 456 };
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // False
}

}

public static class MemberwiseComparer
{
public static bool AreEquivalent(object x, object y)
{
// deal with nulls...
if (x == null) return y == null;
if (y == null) return false;
return AreEquivalentImpl((dynamic)x, (dynamic)y);
}
private static bool AreEquivalentImpl<TX, TY>(TX x, TY y)
{
return AreEquivalentCache<TX, TY>.Eval(x, y);
}
static class AreEquivalentCache<TX, TY>
{
static AreEquivalentCache()
{
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
var xMembers = typeof(TX).GetProperties(flags).Select(p => p.Name)
.Concat(typeof(TX).GetFields(flags).Select(f => f.Name));
var yMembers = typeof(TY).GetProperties(flags).Select(p => p.Name)
.Concat(typeof(TY).GetFields(flags).Select(f => f.Name));
var members = xMembers.Intersect(yMembers);

Expression body = null;
ParameterExpression x = Expression.Parameter(typeof(TX), "x"),
y = Expression.Parameter(typeof(TY), "y");
foreach (var member in members)
{
var thisTest = Expression.Equal(
Expression.PropertyOrField(x, member),
Expression.PropertyOrField(y, member));
body = body == null ? thisTest
: Expression.AndAlso(body, thisTest);
}
if (body == null) body = Expression.Constant(true);
func = Expression.Lambda<Func<TX, TY, bool>>(body, x, y).Compile();
}
private static readonly Func<TX, TY, bool> func;
public static bool Eval(TX x, TY y)
{
return func(x, y);
}
}
}

Group By anonymous type is comparing by ref not by attributes

For the record, adding keyword 'Key' looks like:

dim objs = raw.GroupBy(Function (g) new With { 
Key.TiID = CInt(g("TransactionItemId")),
Key.Adj = g("AdjustmentType").ToString()}).
Select(Function (s) new With {.Key = s.Key, .Vals = s.ToList()}).ToList()

Arg object .Is.Equal with anonymous objects

Anonymous types do implement Equals and GetHashCode in a pretty normal way, calling GetHashCode and Equals for each of their submembers.

So this should pass:

Assert.AreEqual(new { code = "spam-and-eggs" },
new { code = "spam-and-eggs" });

In other words, I suspect you're looking for the problem in the wrong place.

Note that you have to specify the properties in exactly the right order - so new { a = 0, b = 1 } will not be equal to new { b = 1, a = 0 }; the two objects will be of different types.

EDIT: The anonymous type instance creation expressions have to be in the same assembly, too. This is no doubt the problem in this case.

If Equals allows you to specify an IEqualityComparer<T>, you could probably build one which is able to compare two anonymous types with the same properties by creating an instance of one type from the properties of an instance of the other, and then comparing that to the original of the same type. Of course if you were using nested anonymous types you'd need to do that recursively, which could get ugly...



Related Topics



Leave a reply



Submit