2016-07-16 47 views
5

爲什麼通過序列化比較兩個對象並比較兩個對象並不是一種好的做法,然後比較下面示例中的字符串?使用序列化比較兩個對象C#

public class Obj 
{ 
    public int Prop1 { get; set; } 
    public string Prop2 { get; set; } 
} 

public class Comparator<T> : IEqualityComparer<T> 
{ 
    public bool Equals(T x, T y) 
    { 

     return JsonConvert.SerializeObject(x) == JsonConvert.SerializeObject(y); 
    } 

    public int GetHashCode(T obj) 
    { 
     return JsonConvert.SerializeObject(obj).GetHashCode(); 
    } 
} 

Obj o1 = new Obj { Prop1 = 1, Prop2 = "1" }; 
Obj o2 = new Obj { Prop1 = 1, Prop2 = "2" }; 

bool result = new Comparator<Obj>().Equals(o1, o2); 

我已經測試它和它的作品,它是通用的,所以它可以代表對象的差異很大,但我所問的是這是這種方法比較對象的缺點?

我看到它在this question中被提出,它收到了一些upvotes,但我不明白爲什麼這不被認爲是最好的方式,如果有人想比較兩個對象的屬性的值?

編輯:我嚴格談論Json序列化,而不是XML。

我在問這個,因爲我想爲單元測試項目創建一個簡單通用的Comparator,所以比較的性能並沒有打擾我,因爲我知道這可能是最大的不利因素之一。另外,無類型的問題可以使用Newtonsoft.Json的TypeNameHandling屬性設置爲All

+3

由於您已經在使用Json.NET,因此它提供了一個API方法['JToken.DeepEquals()'](http://www.newtonsoft.com/json/help/html/DeepEquals.htm)序列化的對象。 – dbc

+2

您需要使用新的散列函數替換'return obj.GetHashCode()',默認的散列函數使用與Equals相同的邏輯,因爲您已經改變了散列現在不正確的equals行爲,因爲許多進程在檢查if後是否只調用Equals散列是相等的,你可能會得到非常奇怪的結果,因爲它代表 – MikeT

回答

3

您可能會繼續添加一個賞金的問題,直到有人告訴你,這樣做很好。所以你明白了,不要猶豫,要利用NewtonSoft.Json庫來保持代碼簡單。如果您的代碼已經過審查或者其他人接管了代碼的維護,您只需要一些很好的參數來捍衛您的決定。

有些也可能讓反對的,他們的反駁:

這是非常低效的代碼!

當然,特別是GetHashCode()會讓你的代碼在如果你使用Dictionary或HashSet中的對象時變得很慢。

最好的反駁是要注意效率在單元測試中很少關注。最典型的單元測試的開始時間比實際執行時間要長,並且它是否需要1毫秒或1秒是不相關的。而且你可能很早就會發現一個問題。

你是單元測試一個你沒寫的庫!

這當然是一個有效的問題,您實際上正在測試NewtonSoft.Json生成對象的一致字符串表示形式的能力。這是有理由的,特別是浮點值(float和double)從來都不是問題。也有some evidence,圖書館作者不確定如何正確地做到這一點。

最好的反說法是圖書館是廣泛使用使用和維護良好,筆者發佈了多年來的很多更新。當確保具有完全相同的運行時環境的完全相同的程序同時生成兩個字符串(即不存儲它)並確保單元測試是在禁用優化的情況下構建時,可以推斷浮點一致性問題。

您不是單元測試需要測試的代碼!

是的,如果類本身沒有提供比較對象的方法,那麼只會編寫該代碼。換句話說,它本身並不會覆蓋Equals/GetHashCode,也不會暴露比較器。因此,在您的單元測試中測試相等性會執行一個功能,即待測試的代碼實際上不支持。單元測試永遠不應該做的事情,當測試失敗時你不能寫出錯誤報告。

反指令的理由是你需要來測試相等性來測試類的另一個特性,比如構造函數或屬性設置器。代碼中的簡單評論足以證明這一點。

3

對不起,我不能寫評論,所以我會寫在這裏。

通過將對象序列化爲JSON,基本上將所有對象都更改爲另一種數據類型,因此適用於JSON庫的所有內容都會對結果產生影響。

因此,如果在其中一個對象中有[ScriptIgnore]這樣的標籤,那麼您的代碼將會忽略它,因爲它已從數據中省略。

此外,對於不相同的對象,字符串結果可能相同。像這個例子一樣。

static void Main(string[] args) 
    { 
     Xb x1 = new X1() 
     { 
      y1 = 1, 
      y2 = 2 
     }; 
     Xb x2 = new X2() 
     { 
      y1 = 1, 
      y2= 2 
     }; 
     bool result = new Comparator<Xb>().Equals(x1, x2); 
    } 
} 

class Xb 
{ 
    public int y1 { get; set; } 
} 

class X1 : Xb 
{ 
    public short y2 { get; set; } 
} 
class X2 : Xb 
{ 
    public long y2 { get; set; } 
} 

所以當你看到X1已經從X2不同類型,甚至Y2的數據類型是這兩種不同的,但JSON的結果將是相同的。

除此之外,由於x1和x2都來自Xb類型,因此我可以在沒有任何問題的情況下調用比較器。

+0

,如其他答案的評論:*我知道這隻適用於Newtonsoft,但肯定必須爲其他序列化庫:[TypeNameHandling](http:// www。 newtonsoft.com/json/help/html/serializetypenamehandling.htm)消除無類型的問題,所以它可以在同一個項目中的類上正常工作。* – meJustAndrew

13

的主要問題是,它是低效

舉個例子想象這等於功能

public bool Equals(T x, T y) 
{ 
    return x.Prop1 == y.Prop1 
     && x.Prop2 == y.Prop2 
     && x.Prop3 == y.Prop3 
     && x.Prop4 == y.Prop4 
     && x.Prop5 == y.Prop5 
     && x.Prop6 == y.Prop6; 
} 

如果PROP1是不一樣的,然後其他5對從未需要進行檢查,如果你這樣做這與JSON,你將不得不將整個對象轉換成一個JSON字符串,然後每次比較字符串,這是序列化之上是一個昂貴的任務,它本身。

然後下一個問題是序列化被設計用於通信,例如,從內存到文件,通過網絡等等。如果你使用序列化進行比較,你可能會降低你使用它的能力,因爲它是正常使用的,也就是說你不能忽略不需要傳輸的字段,因爲忽略它們可能會破壞你的比較器。

下一個JSON在特定的是無類型的,意思是比沒有任何形狀或形式的值可能被認爲是相等的,並且在相等的反面值可能由於格式化而不相等,如果它們相等序列化到相同的值,這又是不安全和不穩定

唯一上攻至這一技術是需要程序員很少的努力來實現

+0

我知道這隻適用於Newtonsoft,但肯定必須適用於其他序列化庫:[ TypeNameHandling](http://www.newtonsoft.com/json/help/html/serializetypenamehandling.htm)消除無類型的問題,所以它對同一個項目中的類很有用。 – meJustAndrew

+1

@meJustAndrew如果你不使用JSON,並說與XML一起去,那麼類型問題就會消失,但字符串的大小會上升,更不用說XML有多種方式來定義同一個對象,這些屬性按不同的順序其他問題,如果你不關心使用序列化傳輸和效率並不重要,那麼真的沒有任何理由不使用簡單但次優的方法 – MikeT

+1

ps用英語寫作與美國人相反不算作一個錯字;) – MikeT

1

首先,我注意到你說的「序列化它們,然後比較絃樂「。一般來說,普通字符串比較將而不是用於比較XML或JSON字符串,你必須比這更復雜一點。作爲一個反例字符串比較,考慮下面的XML字符串:

<abc></abc> 
<abc/> 

它們顯然不平等的,但他們絕對「的意思是」同樣的事情。雖然這個例子可能看起來有點麻煩,但事實證明,在有些情況下字符串比較不起作用。例如,空格和縮進在字符串比較中很重要,但在XML中可能並不重要。

對於JSON來說情況並不是那麼好。你可以爲此做類似的反例。

{ abc : "def" } 
{ 
    abc : "def" 
} 

再說一遍,顯然這些意味着同樣的事情,但它們不是字符串相等的。基本上,如果你在進行字符串比較,你會相信序列化程序總是以完全相同的方式序列化一個特定的對象(沒有任何添加的空白等),這最終會非常脆弱,尤其是考慮到大多數情況下就我所知,圖書館沒有提供任何這樣的保證。如果你在某些時候更新了序列化庫,並且他們如何進行序列化存在細微的差異,那麼這是特別有問題的;在這種情況下,如果您嘗試將保存的已與之前版本的庫序列化的對象與使用當前版本序列化的對象進行比較,那麼它將無法工作。另外,就像你的代碼本身一樣,「==」運算符是而不是比較對象的正確方法。一般來說,「==」測試參考等於,不是對象相等。

哈希算法的又一個快速離題:它們作爲平等測試手段的可靠程度取決於它們是如何抵抗碰撞的。換句話說,給定兩個不同的,不相等的對象,他們會哈希到相同的值的概率是多少?相反,如果兩個對象哈希到相同的值,它們實際上相等的機率是多少?許多人認爲它們的散列算法具有100%的抗碰撞能力(即,如果且僅當它們相等時,兩個對象纔會哈希到相同的值),但這不一定是正確的。 (一個特別衆所周知的例子是MD5密碼散列函數,其相對較差的抗碰撞性使其不適於進一步使用)。對於一個正確實現的散列函數,在大多數情況下,散列到相同值的兩個對象實際上相等的概率是足夠高的,以適合作爲相等性測試的手段,但不能保證。

+0

考慮將Newtonsoft.Json作爲庫,所以XML問題不適用於這種情況。另外,因爲我在我的問題中編寫了比較器,所以它將僅用於單個庫來比較兩個對象,所以當庫過期時不是問題。在這種情況下,你所有的答案都不適用於這個問題。 – meJustAndrew

+0

我不同意。正如我在我的帖子中提到的,問題適用於* both * XML *和* JSON。你不能只是天真的字符串平等,並期望它按照你的期望工作。要正確地比較XML或JSON字符串,您必須考慮它們的*含義*,**而不僅僅是字符串相等。在一般情況下,您的算法*可能*的唯一方法是如果您能*保證*序列化程序將總是* *就*格式一致*並且文檔沒有提供這樣的保證。 – EJoshuaS

1

這些都是一些缺點的:

一)業績會越來越差更深的對象樹。

B)new Obj { Prop1 = 1 } Equals new Obj { Prop1 = "1" } Equals new Obj { Prop1 = 1.0 }

C)new Obj { Prop1 = 1.0, Prop2 = 2.0 } Not Equals new Obj { Prop2 = 2.0, Prop1 = 1.0 }

+0

在其他答案的評論:*我知道這隻適用於牛頓軟件,但肯定必須爲其他序列化庫:[TypeNameHandling](http://www.newtonsoft.com/json/help/html/serializetypenamehandling。 HTM)擦除無類型的問題,所以它可以在同一個項目中的類上正常工作*。在這種情況下,只有表演纔有效,但謝謝! – meJustAndrew

3

我想糾正GetHashCode開頭。

public class Comparator<T> : IEqualityComparer<T> 
{ 
    public bool Equals(T x, T y) 
    { 
     return JsonConvert.SerializeObject(x) == JsonConvert.SerializeObject(y); 
    } 
    public int GetHashCode(T obj) 
    { 
     return JsonConvert.SerializeObject(obj).GetHashCode(); 
    } 
} 

好的,接下來我們討論這個方法的問題。


首先,它不適用於有環形連接的類型。

如果你有一個像A - > B - > A那樣簡單的屬性鏈接,它會失敗。

不幸的是,這在連接在一起的列表或地圖中非常常見。

最糟糕的是,幾乎沒有高效的通用迴路檢測機制。


其次,與序列化的比較效率不高。

在成功編譯結果之前,JSON需要反射和多種類型的判斷。

因此,你的比較器將成爲任何算法的嚴重瓶頸。

通常,即使在成千上萬的記錄情況下,JSON被認爲足夠慢。


三,JSON必須檢查每個屬性。

如果您的對象鏈接到任何大對象,它將成爲一個災難。

如果您的對象鏈接到一個大文件會怎麼樣?


因此,C#只是將實現留給用戶。

在創建比較器之前,必須徹底瞭解他的課程。

比較需要良好的迴路檢測,提前終止和效率考慮。

通用解決方案根本不存在。

+1

你已經得到了你的觀點,對於循環鏈接,存在* ReferenceLoopHandling *,因此它可以與選項一起使用*忽略*只是不再序列化一個對象,因此在這種情況下它將起作用,而你其次要提的是序列化速度很慢,這是事實。爲什麼我問這個問題是因爲我想在單元測試項目中做一個自定義的比較器,所以它不會讓我感到它會在第二次延遲的時候運行。它還可以節省我編寫自定義比較器的日子。我也應該在這個問題上加上這一點,只是爲了澄清。 – meJustAndrew

0

您可以使用System.Reflections命名空間獲取實例的所有屬性,如this answer。用反射您不僅可以比較public屬性或字段(如使用Json序列化),還可以比較private,protected等以提高計算速度。當然,很顯然,如果兩個對象不同(不包括只有最後一個屬性或對象字段不同時的示例),則不必比較實例的所有屬性或字段。

1

對象比較使用序列化,然後比較在沒有有效弦表示在下列情況下:

DateTime類型的屬性在需要的類型存在要比較

public class Obj 
{ 
    public DateTime Date { get; set; } 
} 

Obj o1 = new Obj { Date = DateTime.Now }; 
Obj o2 = new Obj { Date = DateTime.Now }; 

bool result = new Comparator<Obj>().Equals(o1, o2); 

它將結果false即使對於時間非常近的對象,除非它們不共享完全相同的屬性。


對於有這需要有小量相比較雙精度或十進制值,以驗證它們是否最終會非常接近對方

public class Obj 
{ 
    public double Double { get; set; } 
} 

Obj o1 = new Obj { Double = 22222222222222.22222222222 }; 
Obj o2 = new Obj { Double = 22222222222222.22222222221 }; 

bool result = new Comparator<Obj>().Equals(o1, o2); 

這也將返回false即使是雙值的對象是非常接近的,而且在涉及計算的程序中,由於多次除法和乘法運算後精度的損失,它將成爲一個真正的問題,並且序列化不提供處理這些情況的靈活性。


還考慮上述情況下,如果一個人想不比較的屬性,就會面臨引入序列化屬性爲實際的類的問題,即使是沒有必要的,它會導致代碼污染或者遇到問題,它將不得不實際使用該類型的序列化。

注意:這些是這種方法的一些實際問題,但我期待找到其他人。

1

序列化物,用於存儲一個對象或發送它在一個管(網絡)製成,它是當前執行上下文之外。不是爲了在執行環境中做某件事。

一些序列化值可能不被認爲是相等的,實際上它們是:小數「1.0」和整數「1」。

當然,你可以像你可以用鏟子吃東西,但你不會因爲你可能會摔斷你的牙齒!