In C#, how do you get recursive value equality for nested anonymous arrays and objects?

Viewed 924

Note: For Assert, using Xunit

Anonymous objects use value equality:

Assert.Equal(
  new { foo = "bar" },
  new { foo = "bar" }
);

Anonymous arrays with anonymous objects use value equality:

Assert.Equal(
  new [] { new { foo = "bar" } },
  new [] { new { foo = "bar" } }
);

Anonymous objects with nested arrays seem to use reference equality (this fails):

Assert.Equal(
  new { baz = new [] { new { foo = "bar" } },
  new { baz = new [] { new { foo = "bar" } }
);

But this works (presumably because there's now reference equality):

var baz = new [] { new { foo = "bar" } };
Assert.Equal(
  new { baz },
  new { baz }
);

And this works (so it appears I can recursively nest anonymous objects with value equality preserved):

Assert.Equal(
  new { baz = new { qux = new { foo = "bar" } } },
  new { baz = new { qux = new { foo = "bar" } } }
);

I'm not sure how equality is being conducted here, but I'd like to be able to nest objects and arrays and have infinite-depth value equality. How might I go about doing that?

Update

Looks like Fluent Assertions solved my problem (this test passes):

(new { baz = new[] { new { foo = "bar" } } })
    .Should()
    .BeEquivalentTo(new { baz = new [] { new { foo = "bar" } } });
3 Answers

For anonymous types, their Equal and GetHashCode implementation is the result of recursively running Equal or GetHashCode on their properties. So if you have two anonymous classes with similar value-type properties with the same values, then they compare as equal.

However, if any property is a reference-type, and it does not point to the same object, then they won't be equal.

Here is the snippet from Microsoft docs [1]:

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.

Now, as why arrays are equal, I would bet it is the Assert.Equal implementation that checks if the objects are enumerable, then it check their items equality.

I'd recommend you to use FluentAssertions nuget package. It will let you compare any objects and collections by properties including nested objects/collections, like :

result.Should().BeEquivalentTo(expected);

It also has extensive options, allowing you to compare by runtime type instead of compile time type, or to exclude certain properties from comparison.

Example of respecting runtime type, should work for those anonymous objects:

result.Should().BeEquivalentTo(expected, options => 
    options.RespectingRuntimeTypes());

The generated Equals method of an anonymous object compare it's fields one by one with a default comparer (EqualityComparer.Default)

This comparer uses Equals method to compare objects, so for arrays it compares the references.

Because anonymous object cannot implement any interface, if you want to keep 2 anonymous objects equality, you may write anonymous object only, or use your 4th example to include arrays.

About the 2nd example, the 2 arrays are equal because the library implements the comparsion method.

Related