我有一个模型类Class1
,我想比较Class1
的两个实例是否相同(结构相等)。
public class Class1 : IEquatable<Class1>
{
public string Id { get; set; }
public string Name { get; set; }
public IList<Class2> Class2s { get; set; }
public bool Equals(Class1 other)
{
return QuestName.Equals(other.QuestName)
&& Class2s.OrderBy(c => c.Id).SequenceEqual(other.Class2s.OrderBy(c => c.Id));
//Below method is very fast but not so accurate
//because 2 objects with the same hash code may or may not be equal
//return GetHashCode() == other.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is Class1
&& this.Equals(obj as Class1);
}
public override int GetHashCode()
{
unchecked
{
int hash = 13;
hash = (hash * 7) + Name.GetHashCode();
foreach (var c2 in Class2s.OrderBy(c => c.Id))
{
hash = (hash * 7) + c2.GetHashCode();
}
return hash;
}
}
}
public class Class2 : IEquatable<Class2>
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Class3> Class3s { get; set; }
public bool Equals(Class2 other)
{
return Id == other.Id
&& Name.Equals(other.Name)
&& Class3s.OrderBy(c => c.Id).SequenceEqual(other.Class3s.OrderBy(c => c.Id));
}
public override bool Equals(object obj)
{
return obj is Class2
&& this.Equals(obj as Class2 );
}
public override int GetHashCode()
{
unchecked
{
int hash = 13;
hash = (hash * 7) + Id.GetHashCode();
hash = (hash * 7) + Name.GetHashCode();
foreach (var c3 in Class3s.OrderBy(c => c.Id))
{
hash = (hash * 7) + c3.GetHashCode();
}
return hash;
}
}
}
public class Class3 : IEquatable<Class3>
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Class4> Class4s { get; set; }
public bool Equals(Class3 other)
{
return Id == other.Id
&& Name.Equals(other.Name)
&& Class4s.OrderBy(c => c.Id).SequenceEqual(other.Class4s.OrderBy(c => c.Id));
}
public override bool Equals(object obj)
{
return obj is Class3
&& this.Equals(obj as Class3);
}
public override int GetHashCode()
{
unchecked
{
int hash = 13;
hash = (hash * 7) + Id.GetHashCode();
hash = (hash * 7) + Name.GetHashCode();
foreach (var c in Class4s.OrderBy(c => c.Id))
{
hash = (hash * 7) + c.GetHashCode();
}
return hash;
}
}
}
public class Class4 : IEquatable<Class4>
{
public int Id { get; set; }
public string Name { get; set; }
public bool Equals(Class4 other)
{
return Id.Equals(other.Id)
&& Name.Equals(other.Name);
}
public override bool Equals(object obj)
{
return obj is Class4
&& this.Equals(obj as Class4);
}
public override int GetHashCode()
{
unchecked
{
int hash = 13;
hash = (hash * 7) + Id.GetHashCode();
hash = (hash * 7) + Name.GetHashCode();
return hash;
}
}
}
我说,在以下情况下,两个Class1
对象相等:
1.它们具有相同的Name
2.它们具有相同的Class2
对象(它们的顺序无关紧要)
两个Class2
对象相等:
1.它们具有相同的ID
2.它们具有相同的名称
3.它们具有相同的Class3
对象(它们的顺序无关紧要)
两个Class3
对象相等:
1.它们具有相同的ID
2.它们具有相同的名称
3.它们具有相同的Class4
对象(它们的顺序无关紧要)
两个Class4
对象相等:
1.它们具有相同的ID
2.它们具有相同的名称
我使用Equals
方法比较它们,并像这样测量运行时间:
Class1 obj1 = GetFirstClass1Object();
Class1 obj2 = GetSecondClass1Object();
var startTime = DateTime.Now;
bool equals = obj1.Equals(obj2);
var elaspedTime = DateTime.Now.Substract(startTime)
上述解决方案效果很好,但是速度很慢。
我知道,如果我们将obj1
和obj2
展平,它们每个包含3500个Class4
对象,并且比较obj1
和obj2
大约需要12秒钟。>
有没有更快的方法可以做到这一点?我可以以某种方式利用散列来使其更快吗?
此外,Class2
和Class3
内的Class4
,obj1
和obj2
对象的数量将始终相同
答案 0 :(得分:4)
我已经针对您的代码做了一些BenchmarkDotNet基准测试,并提出了一些优化代码的想法。
对于每个测试,我创建了一个Class1
实例,其中有150个类型为Class2
的子代,每个子实体都有150个类型为Class3
的子代,每个子代都有150个Class4
类型的子代。
我已经测量过将对象与自身进行比较的原因,因为比较不同的对象会更快,因为任何返回假快捷方式的比较都会使整个过程变得更短。另外,没有ReferenceEquals()
快捷方式,因此我不必费心克隆对象。
| Method | Mean | Error | Ratio |
|----------------------------------------------------------------------- |------------:|------:|------:|
| 'Original code' | 535.46 ms | NA | 1.00 |
| 'Custom dictionary-based SequenceEquals' | 6,606.23 ms | NA | 12.34 |
| 'Custom dictionary-based SequenceEquals, classes cache their HashCode' | 1,136.91 ms | NA | 2.12 |
| 'Custom Except()-based SequenceEquals' | 2,281.12 ms | NA | 4.26 |
| 'Custom Except()-based SequenceEquals, classes cache their HashCode' | 257.46 ms | NA | 0.48 |
| 'No OrderBy()' | 76.31 ms | NA | 0.14 |
Original code
:这是您的代码。我将它用作比较的基准。Custom dictionary-based SequenceEquals
:然后,我尝试优化列表相等性比较。首先,我尝试了受this answer启发的Dictionary
解决方案。原来,它慢了12倍,因为Dictionary
必须频繁地计算哈希码,而在我们的案例中,哈希码意味着遍历子代和嵌套子代。Custom dictionary-based SequenceEquals, classes cache their HashCode
:我认为如果开始缓存哈希码,则可以做得更好。现在,基于Dictionary
的解决方案的速度仅为原始解决方案的两倍。Custom Except()-based SequenceEquals
:然后是Except()
方法。在幕后,它创建了类似HashSet的东西。据我了解,它只需要为两个可枚举的每个元素计算一次哈希码。该解决方案所需的时间是原始解决方案的4.26倍。Custom Except()-based SequenceEquals, classes cache their HashCode
:与以前一样,我开始缓存哈希码,因此实际上只为每个对象计算一次。生成的解决方案花费原始时间的0.48倍。还不错。No OrderBy()
:然后,我不再使用OrderBy()
,仅使用SequenceEquals()
,并且鉴于我正在将对象与其自身进行比较,您可以说数据已经被排序了,因此比较安全::)。最终的解决方案是极大的加速,其速度是原始速度的0.14倍。您最好的选择是查看模型和需求,您真的需要比较像这样的巨大对象图吗? 如果确实需要:
Except()
的比较。请注意,由于基于集合的解决方案假定您不关心重复项,因此必须在Count
之前比较列表Except()
。OrderBy()
和简单的SequenceEquals()
比较。这是一个折衷,因为刀片将变得更加昂贵。请参阅,这是否适用于您的方案。将我的代码和度量值上传到this repo。
答案 1 :(得分:1)
以提供的类为例,考虑以下结构。没有基于您的示例的示例数据可以对其进行测试,因此您将必须使用自己的示例进行测试。
public class Class1 : IEquatable<Class1> {
public int Id { get; set; }
public string Name { get; set; }
public IList<Class2> Class2s { get; set; }
public static bool operator ==(Class1 left, Class1 right) {
return Equals(left, right);
}
public static bool operator !=(Class1 left, Class1 right) {
return !(left == right);
}
public bool Equals(Class1 other) {
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(this.ToString(), other.ToString());
}
public override bool Equals(object obj) {
return obj is Class1 other && this.Equals(other);
}
public override int GetHashCode() {
return ToString().GetHashCode();
}
public override string ToString() {
var cs = Class2s == null ? "" : string.Join("", Class2s.OrderBy(_ => _.Id).Select(_ => _.ToString()));
return string.Join("", Id, Name, cs);
}
}
public class Class2 : IEquatable<Class2> {
public int Id { get; set; }
public string Name { get; set; }
public IList<Class3> Class3s { get; set; }
public static bool operator ==(Class2 left, Class2 right) {
return Equals(left, right);
}
public static bool operator !=(Class2 left, Class2 right) {
return !(left == right);
}
public bool Equals(Class2 other) {
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(this.ToString(), other.ToString());
}
public override bool Equals(object obj) {
return obj is Class2 other && this.Equals(other);
}
public override int GetHashCode() {
return ToString().GetHashCode();
}
public override string ToString() {
var cs = Class3s == null ? "" : string.Join("", Class3s.OrderBy(_ => _.Id).Select(_ => _.ToString()));
return string.Join("", Id, Name, cs);
}
}
public class Class3 : IEquatable<Class3> {
public int Id { get; set; }
public string Name { get; set; }
public IList<Class4> Class4s { get; set; }
public static bool operator ==(Class3 left, Class3 right) {
return Equals(left, right);
}
public static bool operator !=(Class3 left, Class3 right) {
return !(left == right);
}
public bool Equals(Class3 other) {
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(this.ToString(), other.ToString());
}
public override bool Equals(object obj) {
return obj is Class3 other && this.Equals(other);
}
public override int GetHashCode() {
return ToString().GetHashCode();
}
public override string ToString() {
var cs = Class4s == null ? "" : string.Join("", Class4s.OrderBy(_ => _.Id).Select(_ => _.ToString()));
return string.Join("", Id, Name, cs);
}
}
public class Class4 : IEquatable<Class4> {
public int Id { get; set; }
public string Name { get; set; }
public static bool operator ==(Class4 left, Class4 right) {
return Equals(left, right);
}
public static bool operator !=(Class4 left, Class4 right) {
return !(left == right);
}
public bool Equals(Class4 other) {
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(this.ToString(), other.ToString());
}
public override bool Equals(object obj) {
return obj is Class4 other && Equals(other);
}
public override int GetHashCode() {
return ToString().GetHashCode();
}
public override string ToString() {
return string.Format("{0}{1}", Id, Name);
}
}
除Class4
之外,所有对象的结构都相似,因为它没有内部列表。
尽管只是一个示例,但是很多重复的代码都可以重构为一个通用的基类。
答案 2 :(得分:0)
对列表进行排序以比较它们对我而言似乎效率很低。您可以尝试使用其他方法比较列表
代替
Class2s.OrderBy(c => c.Id).SequenceEqual(other.Class2s.OrderBy(c => c.Id)
您可以尝试类似
!Class2s.Except(other.Class2s).Any()
如果大多数对象不相等,则还可以添加一个额外的测试,以确保列表的大小不相同时不会循环播放这些列表:
Class2s.Count == other.Class2s.Count && !Class2s.Except(other.Class2s).Any()
当然,您也可以对Class2.Equals()和Class3.Equals方法执行相同的操作。