我有一个包含一些字段的类。我需要通过值来比较这个类的实例,所以我相应地定义了GetHashCode
和Equals
。因为类允许循环引用,所以我需要一种避免无限递归的机制(有关更详细的解释,请参阅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
最终返回到主方法。
(不使用checkedPairs
内Equals
,会有无限递归foo1.Equals(foo2)
和foo2.Equals(foo1)
之间跳跃。)
这工作还好吧,我单线程,非并发的沙箱环境。但是,我只为使用static
字段,因为我不知道任何其他方式将已收集的项目从Equals
的一个调用转移到调用堆栈中的下一个。
但这种做法我不能使用多线程或并发环境中,几个Equals
检查可能会并行或混合到上的顺序(例如,由于经过Equals
作为代表,后来调用它,而不是运行立即)。
问题:
将使用一个线程,静态变量的工作?恐怕没有,因为我可以想象,来自同一个调用堆栈的不同
Equals
调用仍然可以在不同的线程上执行(但我不知道)。有没有办法使
checkedPairs
“调用堆栈静态”?这样每个调用堆栈都可以获得自己的checkedPairs
副本?然后对于每个新的调用堆栈,将创建一个新的(空)checkedPairs
,在递归期间填充,并在递归结束后收集垃圾。
我通常只是给Equal()方法添加一个参数来传递项目。没有理由Equals必须有一个参数。如果你有一个ICompare的类,用两个参数调用Equal(object)调用MyEqual()来完成递归。 – jdweng