2016-05-12 92 views
2

我有兩個對象具有這些定義兩個對象的平等:比較基於字典

public static Dictionary<string, Container> cont1 = new Dictionary<string, Container>(); 
public static Dictionary<string, Container> cont2 = new Dictionary<string, Container>(); 

容器類的模式是如下:

public class Container 
{ 
    public string IDx { get; set; } 
    public string IDy { get; set; } 
    public string Name { get; set; } 
    public Dictionary<string, Sub> Subs = new Dictionary<string, Sub>(); 
} 

public class Sub 
{ 
    public string Namex { get; set; } 
    public string Namey { get; set; } 
    public string Value { get; set; } 
    public Dictionary<string, string> Paths { get; set; } 
} 

我的問題是:我如何深入檢查 cont1和cont2的權益?我的意思是,每個成員和價值都相等,甚至在子對象內部也是如此。

在這種情況下,在c#中是否有任何功能,或者我必須編寫自定義方法來根據對象的結構自己檢查相等性;

第二個問題:我能避免的平等問題,如果我可以創建產品的兩個不同的副本;我的意思是說我們有一個基礎Container對象的所有成員和值,然後創建兩個單獨的副本容器,即CONT1和CONT2這CONT1改變的值不會在CONT2改變同樣價值的

注1:該方法用於克隆不工作:

cont2 = new Dictionary<string, Container>(cont1); 

注2:大多數在其他的答案所提出的方法都是基於一個水平字典(使用用於檢查環或LINQ)而當我們在對象中擁有屬性和字典對象(具有它們自己的屬性)時不會出現這種情況。

+0

'我的問題是:我如何能深入檢查CONT1和cont2'的股權 - 這是否意味着兩個字典具有相同的密鑰。並且每個鍵的值都具有相同的「容器」的確切*值*或相同的確切*實例*? – Jamiec

+0

當然,你必須編寫一個自定義方法來根據對象的結構來檢查相等性。但是你甚至不明白你的理解是否平等。另外,不要一次提出多個問題。 –

+0

他們可能有不同的鍵和值,這就是重點;檢查它們是否具有完全相同的密鑰,具有相同值的屬性 – wiki

回答

1

是的,你必須寫的基於對象自己的結構檢查平等的自定義方法。我將提供(基於thisGetHashCode執行)的自定義IEqualityComparer<Container>和喜歡這裏:

public class ContainerCheck : IEqualityComparer<Container> 
{ 
    private SubCheck subChecker = new SubCheck(); 
    public bool Equals(Container x, Container y) 
    { 
     if (ReferenceEquals(x, y)) 
      return true; 
     if (x == null || y == null) 
      return false; 
     if (x.IDx != y.IDx || x.IDy != y.IDy || x.Name != y.Name) 
      return false; 
     // check dictionary 
     if (ReferenceEquals(x.Subs, y.Subs)) 
      return true; 
     if (x.Subs == null || y.Subs == null || x.Subs.Count != y.Subs.Count) 
      return false; 
     foreach (var kv in x.Subs) 
      if (!y.Subs.ContainsKey(kv.Key) || subChecker.Equals(y.Subs[kv.Key], kv.Value)) 
       return false; 
     return true; 

    } 

    public int GetHashCode(Container obj) 
    { 
     unchecked // Overflow is fine, just wrap 
     { 
      int hash = 17; 
      // Suitable nullity checks etc, of course :) 
      hash = hash * 23 + obj.IDx.GetHashCode(); 
      hash = hash * 23 + obj.IDy.GetHashCode(); 
      hash = hash * 23 + obj.Name.GetHashCode(); 
      foreach (var kv in obj.Subs) 
      { 
       hash = hash * 23 + kv.Key.GetHashCode(); 
       hash = hash * 23 + subChecker.GetHashCode(kv.Value); 
      } 

      return hash; 
     } 
    } 
} 

public class SubCheck : IEqualityComparer<Sub> 
{ 
    public bool Equals(Sub x, Sub y) 
    { 
     if (ReferenceEquals(x, y)) 
      return true; 
     if (x == null || y == null) 
      return false; 
     if (x.Namex != y.Namex || x.Namey != y.Namey || x.Value != y.Value) 
      return false; 
     // check dictionary 
     if (ReferenceEquals(x.Paths, y.Paths)) 
      return true; 
     if (x.Paths == null || y.Paths == null || x.Paths.Count != y.Paths.Count) 
      return false; 
     foreach(var kv in x.Paths) 
      if (!y.Paths.ContainsKey(kv.Key) || y.Paths[kv.Key] != kv.Value) 
       return false; 
     return true; 
    } 

    public int GetHashCode(Sub obj) 
    { 
     unchecked // Overflow is fine, just wrap 
     { 
      int hash = 17; 
      // Suitable nullity checks etc, of course :) 
      hash = hash * 23 + obj.Namex.GetHashCode(); 
      hash = hash * 23 + obj.Namey.GetHashCode(); 
      hash = hash * 23 + obj.Value.GetHashCode(); 
      foreach (var kv in obj.Paths) 
      { 
       hash = hash * 23 + kv.Key.GetHashCode(); 
       hash = hash*23 + kv.Value.GetHashCode(); 
      } 

      return hash; 
     } 
    } 
} 

這應該深檢查所有屬性和字典。然後,你可以使用下面的循環來兩個字典相互比較:

bool equal = true; 
var allKeys = cont1.Keys.Concat(cont2.Keys).ToList(); 
var containerChecker = new ContainerCheck(); 

foreach (string key in allKeys) 
{ 
    Container c1; 
    Container c2; 
    if (!cont1.TryGetValue(key, out c1) || !cont2.TryGetValue(key, out c2)) 
    { 
     equal = false; 
    } 
    else 
    { 
     // deep check both containers 
     if (!containerChecker.Equals(c1, c2)) 
      equal = false; 
    } 
    if(!equal) 
     break; // or collect differences 
} 
+0

非常感謝您驗證完整的實施!無論如何,我更想知道是否有這種情況下的快捷方式 – wiki

+0

@wiki:不,不幸的是不是。你總是有'Object.ReferenceEquals'也在我的實現中使用。但是,如果你不重寫'Equals' +'GetHashCode'(或者提供一個'IEqualityComparer'),那麼.NET將使用的是'ReferenceEquals'。所以只檢查兩者是否是相同的引用,而不是如果屬性相同。 –

2

詞典是一個序列,所以一般來說你可能要找的是Enumerable<T>.SequenceEquals,它允許傳入一個IEquityComparer<T>

你的序列(字典)是一個IEnumerable<KeyValuePair<string,Container>>所以你需要一個比較器,它實現了IEquityComparer<IEnumerable<KeyValuePair<string,Container>>>(這就是很多角度大括號!)。

var equal = cont1.SequenceEquals(cont2, new StringContainerPairEquityComparer()); 

注意要素詞典的順序沒有保證,所以使用的方法正確,你可能應該比較序列之前使用OrderBy - 然而,這增加了該方法的效率。


關於第二個問題,你想要做的是克隆字典。一般來說你Container應該實現ICloneable接口,然後你就可以用它來創建一個副本

var cont2 = cont1.ToDictionary(k => k.Key, v => v.Value.Clone()); 
+0

是的;你是對的;這就是爲什麼我問這個問題,要知道是否有一個更簡單的方法 – wiki

+0

@wiki - 比.....容易比什麼?實施1班? – Jamiec

+0

比實現一個基於我的類的結構的長方法來檢查相等更容易 – wiki