为什么Object.Equals()对于从不同程序集重新实例化的相同匿名类型返回false ?

本文关键字:实例化 false 返回 类型 程序集 Equals Object 为什么 | 更新日期: 2023-09-27 18:05:10

我有一些代码将强类型业务对象映射为匿名类型,然后将其序列化为JSON并通过API公开。

在将我的解决方案重组为单独的项目后,我的一些测试开始失败。我已经做了一些挖掘,事实证明,Object.Equals的行为不同的匿名类型是由代码从不同的程序集返回-我不知道为什么,或者我可以做些什么来解决它。

在https://github.com/dylanbeattie/AnonymousTypeEquality上有完整的复制代码,但实际破坏的位在下面。此代码位于测试项目中:

[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 };
    }
}

,然后在解决方案中有一个单独的类库只包含这个:

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

根据MSDN,"相同匿名类型的两个实例只有在它们的所有属性相等时才相等。"(我的重点)-那么为了比较的目的,是什么控制两个实例是否具有相同的匿名类型?我的两个实例具有相同的哈希码,并且看起来都是<>f__AnonymousType0`2[System.String,System.Int32]—但是我猜测匿名类型的相等性必须考虑到完全限定类型名称,因此将代码移动到不同的程序集中可能会破坏事情。谁有确切的来源/链接,这是如何实现的?

为什么Object.Equals()对于从不同程序集重新实例化的相同匿名类型返回false ?

匿名类型具有固有的作用域。您的示例打破了这种作用域,因此类型不同。在当前的c#编译器中,匿名类型不能超越程序集(更确切地说,是模块)。即使来自两个不同程序集的两个匿名类型具有相同的属性,它们也是两种不同的类型(它们是internal,因此要注意安全含义)。当您将匿名类型向下转换为object时,您知道您做错了。

TL;DR:你在滥用匿名类型。别惊讶它咬了你。

如果使用像Reflector这样的工具反汇编程序集,您将看到您的匿名类型由每个程序集中的类表示,看起来像这样(在取消编译器生成的标识符之后):

internal sealed class AnonymousType<TName, TValue>
{
    private readonly TName _name;
    private readonly TValue _value;
    public TName name => this._name;
    public TValue value => this._value;
    public AnonymousType(TName name, TValue value)
    {
        this._name = name;
        this._value = value;
    }
    public override bool Equals(object value)
    {
        var that = value as AnonymousType<TName, TValue>;
        return that != null &&
            EqualityComparer<TName>.Default.Equals(this._name, that._name) &&
            EqualityComparer<TValue>.Default.Equals(this._value, that._value);
    }
    public override int GetHashCode()
    {
        // ...
    }
}

Equals方法的第一行检查value是否是AnonymousType<TName, TValue>的实例,具体指的是当前程序集中定义的类。因此,来自不同程序集的匿名类型在比较时永远不会相等,即使它们具有相同的结构。

您可能希望更改测试以比较对象的序列化JSON,而不是对象本身。

匿名类型在其所在的程序集中被编译为隐藏类型,如果定义匹配,则出于效率目的重用该类型。这意味着不同程序集中的类似at将具有不同的类型,它们的. equals将进行类型检查。

这是我最喜欢的处理匿名类型的方法之一:

void Main()
{
    var json = "{ '"name'": '"Dylan'"}";
    var x = Deserialize(json, new { name = null as string});
    Console.WriteLine(x.name);
}
T Deserialize<T>(string json, T template)
{
    return (T) JsonConvert.DeserializeObject(json, typeof(T));
}

将Deserialize方法放在另一个程序集中会很有趣…