2017-10-05 74 views
4

我有一个包含一些字段的类。我需要通过值来比较这个类的实例,所以我相应地定义了GetHashCodeEquals。因为类允许循环引用,所以我需要一种避免无限递归的机制(有关更详细的解释,请参阅Value-equals and circular references: how to resolve infinite recursion?)。为调用堆栈创建最后一个变量

class Foo 
{ 
    public string Name { get; set; } 
    public Foo Reference { get; set; } 

    public override int GetHashCode() { return Name.GetHashCode(); } 

    static HashSet<(Foo,Foo)> checkedPairs 
     = new HashSet<(Foo,Foo)>(ValuePairRefEqualityComparer<Foo>.Instance); 
     // using an equality comparer that compares corresponding items for reference; 
     // implementation here: https://stackoverflow.com/a/46589154/5333340 

    public override bool Equals(object obj) 
    { 
     Foo other = obj as Foo; 
     if (other == null) 
      return false; 

     if !(Name.Equals(other.Name)) 
      return false; 

     if (checkedPairs.Contains((this,other)) || checkedPairs.Contains((other,this))) 
      return true; 

     checkedPairs.Add((this,other)); 

     bool refsEqual = Reference.Equals(other.Reference); 
     checkedPairs.Clear(); 
     return refsEqual; 
    } 
} 

想象中的主要方法如下代码:

Foo foo1 = new Foo { Name = "foo" }; 
Foo foo2 = new Foo { Name = "foo" }; 
foo1.Reference = foo2; 
foo2.Reference = foo1; 

bool foo_equals_bar = foo1.Equals(foo2); 
Console.WriteLine("foo_equals_bar = " + foo_equals_bar); 

foo1.Equals(foo2)将在checkedPairs存储(foo1,foo2)之前,我通过修改我的Equals方法,以便它跟踪的比较完成之前解决这个问题它调用foo2.Equals(foo1)。在foo2.Equals(foo1)内部,将会注意到checkedPairs包含(foo1,foo2),并且将返回true。这个结果被转移到foo1.Equals(foo2)调用中的equal变量内,然后清除checkedPairs,并且true最终返回到主方法。

(不使用checkedPairsEquals,会有无限递归foo1.Equals(foo2)foo2.Equals(foo1)之间跳跃。)

这工作还好吧,我单线程,非并发的沙箱环境。但是,我只为使用static字段,因为我不知道任何其他方式将已收集的项目从Equals的一个调用转移到调用堆栈中的下一个。

但这种做法我不能使用多线程或并发环境中,几个Equals检查可能会并行或混合到上的顺序(例如,由于经过Equals作为代表,后来调用它,而不是运行立即)。

问题:

  1. 将使用一个线程,静态变量的工作?恐怕没有,因为我可以想象,来自同一个调用堆栈的不同Equals调用仍然可以在不同的线程上执行(但我不知道)。

  2. 有没有办法使checkedPairs“调用堆栈静态”?这样每个调用堆栈都可以获得自己的checkedPairs副本?然后对于每个新的调用堆栈,将创建一个新的(空)checkedPairs,在递归期间填充,并在递归结束后收集垃圾。

+3

我通常只是给Equal()方法添加一个参数来传递项目。没有理由Equals必须有一个参数。如果你有一个ICompare的类,用两个参数调用Equal(object)调用MyEqual()来完成递归。 – jdweng

回答

2

感谢jdweng指向我一个简单的解决方案,在问题陈述的特定代码的工作:

Foo类中删除checkedPairs域,并通过此代码替换Equals方法:

public override bool Equals(object obj) 
{ 
    return MyEquals(obj, new HashSet<(Foo,Foo)>(ValuePairRefEqualityComparer<Foo>.Instance)); 
} 

private bool MyEquals(object obj, HashSet<(Foo,Foo)> checkedPairs) 
{ 
    Foo other = obj as Foo; 
    if (other == null) 
     return false; 

    if (!Name.Equals(other.Name)) 
     return false; 

    if (checkedPairs.Contains((this,other)) || checkedPairs.Contains((other,this))) 
     return true; 

    checkedPairs.Add((this,other)); 

    return Reference.MyEquals(other.Reference, checkedItems); 
} 

但是,这种方法通常不起作用,一般。举例来说,来自这个问题的类别:Value-equals and circular references: how to resolve infinite recursion?,想象我在中类似地定义了ClubPerson。由于MyEquals不能从课堂外调用(我希望它是私人的),所以仍然会有无限递归。例如。当调用Person.MyEquals时,它将在内部调用FavouriteInstitution.Equals,但它应该以某种方式重定向到FavouriteInstitution.MyEquals(可能已经填充checkedPairs!)。此外,Members.SetEquals(other.Members)将重定向到Person.Equals而不是Person.MyEquals

+2

如果你做'foo1.Reference = null; foo2.Reference = null;'then'foo1.Equals(foo2);','foo1.Equals(foo1);'或'foo2.Equals(foo2);'all将抛出空引用异常。 –