基于字典比较两个对象的相等性
Compare equality of two objects based on dictionaries
我有两个具有这些定义的对象:
public static Dictionary<string, Container> cont1 = new Dictionary<string, Container>();
public static Dictionary<string, Container> cont2 = new Dictionary<string, Container>();
容器class的架构如下:
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 中的值不会更改相同的值续2.
注1:此克隆方法无效:
cont2 = new Dictionary<string, Container>(cont1);
Note2:其他答案中提出的大多数方法都是基于一级字典(使用循环或 LINQ 进行检查),而不是当我们有属性和对象中的字典对象(具有自己的属性)。
字典是一个序列,所以通常您要找的可能是 Enumerable<T>.SequenceEquals
which allows passing in an 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());
是的,您必须自己编写一个自定义方法来根据对象的结构检查是否相等。我会像这里提供自定义 IEqualityComparer<Container>
和 IEqualityComparer<Sub>
(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
}
我有两个具有这些定义的对象:
public static Dictionary<string, Container> cont1 = new Dictionary<string, Container>();
public static Dictionary<string, Container> cont2 = new Dictionary<string, Container>();
容器class的架构如下:
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 中的值不会更改相同的值续2.
注1:此克隆方法无效:
cont2 = new Dictionary<string, Container>(cont1);
Note2:其他答案中提出的大多数方法都是基于一级字典(使用循环或 LINQ 进行检查),而不是当我们有属性和对象中的字典对象(具有自己的属性)。
字典是一个序列,所以通常您要找的可能是 Enumerable<T>.SequenceEquals
which allows passing in an 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());
是的,您必须自己编写一个自定义方法来根据对象的结构检查是否相等。我会像这里提供自定义 IEqualityComparer<Container>
和 IEqualityComparer<Sub>
(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
}