2016-08-31 3 views
9

強く型付けされたビジネスオブジェクトを匿名型にマップするいくつかのコードがあります。コードはJSONにシリアル化され、APIを介して公開されます。Object.Equals()は、異なるアセンブリからインスタンス化されたときに、同一の匿名型に対してfalseを返すのはなぜですか?

私のソリューションを別のプロジェクトに再構成した後、私のテストのいくつかが失敗し始めました。私は少し掘り下げましたが、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によると、「同じ匿名型の2つのインスタンスは、は、それらのプロパティがすべて等しい場合にのみ等しくなります。 (私の強調) - 2つのインスタンスが比較のために同じの匿名タイプであるかどうかを制御するものは何ですか?私の2つのインスタンスは等しいハッシュコードを持っていて、どちらも<>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() { // ... } } 

valueEquals方法チェックの最初の行は、現在のアセンブリ定義されたクラスに特異的に参照すると、AnonymousType<TName, TValue>のインスタンスです。したがって、同じアセンブリを持っていても、異なるアセンブリの匿名型は決して等しいとは決して比較されません。

テストを変更して、オブジェクト自体ではなく、オブジェクトのシリアル化されたJSONを比較することができます。

8

匿名の種類は、本質的にスコープされています。あなたの例はそのスコープを破るので、タイプは異なります。現在のC#コンパイラでは、匿名型はアセンブリ(またはモジュール)を超越することはできません。 2つの異なるアセンブリの2つの匿名型が同じプロパティを持っていても、それらは2つの異なる型です(そして、それらは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)); 
} 

別のアセンブリに逆シリアル化方法を置くために興味深いものになるだろう...

関連する問題