【发布时间】:2018-03-17 08:10:54
【问题描述】:
我有一个包含一些字段的类。我需要按值比较此类的实例,因此我相应地定义了GetHashCode 和Equals。因为该类允许循环引用,所以我需要一种机制来避免无限递归(更详细的解释参见Value-equals and circular references: how to resolve infinite recursion?)。我通过修改我的Equals 方法解决了这个问题,以便它跟踪之前完成的比较:
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;
}
}
想象一下 main 方法中的以下代码:
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) 将在调用foo2.Equals(foo1) 之前将(foo1,foo2) 存储在checkedPairs 中。在foo2.Equals(foo1) 中会注意到checkedPairs 包含(foo1,foo2),并且将返回true。这个结果在foo1.Equals(foo2)的调用里面传递给equal变量,然后checkedPairs被清空,最后true返回给main方法。
(如果在Equals 中不使用checkedPairs,则foo1.Equals(foo2) 和foo2.Equals(foo1) 之间会出现无限递归跳转。)
这在我的单线程、非并发沙箱环境中运行良好。但是,我只对checkedPairs 使用static 字段,因为我不知道有任何其他方法可以将已经收集的项目从Equals 的一次调用转移到调用堆栈中的下一次调用。强>
但是通过这种方法,我不能使用多线程或并发环境,其中多个 Equals 检查可能并行运行或以混合顺序运行(例如,由于将 Equals 作为委托传递并稍后调用它开启而不是立即开启)。
问题:
使用线程静态变量会起作用吗?恐怕不会,因为我可以想象来自同一个调用堆栈的不同
Equals调用仍然可以在不同的线程上执行(但我不知道)。有没有办法让
checkedPairs“调用堆栈静态”?这样每个调用堆栈都有自己的checkedPairs副本吗?然后对于每个新的调用堆栈,将创建一个新的(空)checkedPairs,在递归期间填充,并在递归结束后收集垃圾。
【问题讨论】:
-
我通常只是在 Equal() 方法中添加一个参数来传递项目。没有理由 Equals 必须有一个参数。如果你有一个带 ICompare 的类,请使用两个参数调用 Equal(object) 调用 MyEqual() 来进行递归。
标签: c# recursion concurrency callstack