【问题标题】:Compare equality of two objects based on dictionaries基于字典比较两个对象的相等性
【发布时间】:2016-09-07 22:15:39
【问题描述】:

我有两个具有这些定义的对象:

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

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 的权益?我的意思是每个成员的平等性和价值,甚至在 Subs 对象的深处;

c#中是否有针对这种情况的任何功能,或者我必须自己编写一个自定义方法来根据对象的结构检查相等性;

第二个问题:如果我可以创建两个不同的 Products 副本,我可以避免相等问题;我的意思是说我们有一个包含所有成员和值的基本 Container 对象,然后创建 Container 的两个单独的副本,即 cont1 和 cont2,它们更改 cont1 中的值不会更改 cont2 中的相同值。

注意1:此克隆方法无效:

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

注意2:其他答案中建议的大多数方法都基于单级字典(使用循环或 LINQ 进行检查),而不是当我们有属性和字典对象(具有它们自己的属性)在对象中。

【问题讨论】:

  • My question is: How can I deep check the equity of cont1 and cont2 - 这是否意味着两个字典具有相同的键。并且每个键的值具有完全相同的 values 或完全相同的 Container?instances
  • 当然,您必须自己编写一个自定义方法来根据对象的结构检查相等性。但它甚至不清楚你理解为平等。另外,不要一次问多个问题。
  • 它们可能有不同的键和值,这就是重点;检查它们是否具有完全相同的键、具有相同值的属性
  • @wiki:您是否需要知道它们是否相等,或者哪些在另一个中?
  • 没有;我只需要知道它们是否完全相等;差异并不重要

标签: c# object dictionary clone equality


【解决方案1】:

字典是一个序列,所以通常您可能正在寻找的是Enumerable&lt;T&gt;.SequenceEquals,它允许传入IEquityComparer&lt;T&gt;

您的序列(字典)是一个IEnumerable&lt;KeyValuePair&lt;string,Container&gt;&gt;,因此您需要一个实现IEquityComparer&lt;IEnumerable&lt;KeyValuePair&lt;string,Container&gt;&gt;&gt; 的比较器(那是很多尖括号!)。

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

请注意,元素字典的顺序无法保证,因此要正确使用该方法,您可能应该在比较序列之前使用OrderBy - 但是这会增加该方法的效率。


对于您的第二个问题,您要做的是 克隆 字典。一般来说你的Container应该实现ICloneable接口,然后你可以用它来创建一个副本

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

【讨论】:

  • 是的;你说的对;这就是我问这个问题的原因,想知道是否有更简单的方法
  • 比基于我的类结构实现长方法来检查是否相等更容易
  • @wiki 啊我明白了——不,这里没有捷径。
  • 创建两个不相关的副本怎么样?对此有何评论?
  • @wiki 更新了答案 - 抱歉,错过了您的第二个问题(这就是为什么第二个问题应该是一个单独的问题!)
【解决方案2】:

是的,您必须自己编写一个自定义方法来根据对象的结构检查相等性。我会提供一个自定义的IEqualityComparer&lt;Container&gt; 和一个IEqualityComparer&lt;Sub&gt; 就像这里一样(GetHashCode 实现基于this):

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
}

【讨论】:

  • 非常感谢您证明了完整的实现!无论如何,我更想知道这种情况是否有捷径
  • @wiki:不,很遗憾不是。我的实现中也使用了Object.ReferenceEquals。但是,如果您不覆盖Equals+GetHashCodeyourself(或提供IEqualityComparer&lt;T&gt;),那么.NET 将使用ReferenceEquals。所以只检查两者是否是相同的引用,而不是属性是否相等。
猜你喜欢
  • 2021-09-14
  • 1970-01-01
  • 2010-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多