Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Object.Equals() return false for identical anonymous types when they're instantiated from different assemblies?

I have some code that maps strongly-typed business objects into anonymous types, which are then serialized into JSON and exposed via an API.

After restructuring my solution into separate projects, some of my tests started to fail. I've done a bit of digging and it turns out that Object.Equals behaves differently on anonymous types that are returned by code from a different assembly - and I'm not sure why, or what I can do to work around it.

There's full repro code at https://github.com/dylanbeattie/AnonymousTypeEquality but the bit that's actually breaking is below. This code is in the Tests project:

[TestFixture]
public class Tests {
    [Test]
    public void BothInline() {
        var a = new { name = "test", value = 123 };
        var b = new { name = "test", value = 123 };
        Assert.That(Object.Equals(a,b)); // passes
    }

    [Test]
    public void FromLocalMethod() {
        var a = new { name = "test", value = 123 };
        var b = MakeObject("test", 123);
        Assert.That(Object.Equals(a, b)); // passes
    }

    [Test]
    public void FromOtherNamespace() {
        var a = new { name = "test", value = 123 };
        var b = OtherNamespaceClass.MakeObject("test", 123);
        Assert.That(Object.Equals(a, b)); // passes
    }


    [Test]
    public void FromOtherClass() {
        var a = new { name = "test", value = 123 };
        var b = OtherClass.MakeObject("test", 123);

        /* This is the test that fails, and I cannot work out why */
        Assert.That(Object.Equals(a, b));
    }

    private object MakeObject(string name, int value) {
        return new { name, value };
    }
}

and then there is a separate class library in the solution containing only this:

namespace OtherClasses {
  public static class OtherClass {
    public static object MakeObject(string name, int value) {
      return new { name, value };
    }
  }  
}

According to MSDN, "two instances of the same anonymous type are equal only if all their properties are equal." (my emphasis) - so what controls whether two instances are of the same anonymous type for comparison purposes? My two instances have equal hash codes, and both appear to be <>f__AnonymousType0`2[System.String,System.Int32] - but I'm guessing that equality for anonymous types must take the fully qualified type name into account and therefore moving code into a different assembly can break things. Anyone got a definitive source / link on exactly how this is implemented?

like image 971
Dylan Beattie Avatar asked Aug 31 '16 12:08

Dylan Beattie


1 Answers

Anonymous types are inherently scoped. Your example breaks that scoping, so the types are different. In current C# compilers, anonymous types cannot transcend assemblies (or modules, to be more exact). Even if two anonymous types from two different assemblies have the same properties, they are two different types (and they are internal, so beware of the security implications). The second you downcast an anonymous type to object, you know you're doing it wrong.

TL; DR: You're abusing anonymous types. Don't be surprised it bites you.

like image 87
Luaan Avatar answered Oct 02 '22 13:10

Luaan