2016-08-31 18 views
9

我有一些代码将强类型的业务对象映射为匿名类型,然后将其序列化为JSON并通过API公开。为什么Object.Equals()在从不同的程序集实例化时为相同的匿名类型返回false?

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

https://github.com/dylanbeattie/AnonymousTypeEquality完整的repro代码,但实际上打破的位低于。这个代码是在测试项目:

[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] - 但我猜测匿名类型的平等必须考虑完全限定的类型名称,因此将代码移入不同的程序集可能会破坏事情。任何人都得到了确切的来源/链接究竟如何实现?

回答

4

就像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而不是对象本身。

8

匿名类型的内在范围。你的例子打破了范围,所以类型是不同的。在当前的C#编译器中,匿名类型不能超越程序集(或模块,更确切地说)。即使来自两个不同程序集的两个匿名类型具有相同的属性,它们也是两种不同的类型(并且它们是internal,因此请小心安全隐患)。第二个你倒下了一个匿名类型到object,你知道你做错了。

TL; DR:你滥用匿名类型。不要惊讶它咬你。

+0

我有一段时间没有任何问题被咬伤......这真的很高兴知道你做什么来挑起咬伤。 :) –

+1

@DylanBeattie您将一个匿名类型转换为'object'。永远不要这样做。要么保持泛型类型(如LINQ那样),要么确保使用正确的类型保持本地类型。其他一切都是禁止的。 – Luaan

2

匿名类型会被编译为一个隐藏的类型,在它们所在的程序集内部,如果定义匹配,它将被重用以提高效率。这意味着不同组件中类似的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)); 
} 

这将是有趣的,把反序列化方法在另一个装配...

相关问题