为调用堆栈创建一个变量 last

make a variable last for a call stack

我有一个包含一些字段的 class。我需要按值比较此 class 的实例,因此我相应地定义了 GetHashCodeEquals。因为 class 允许循环引用,我需要一种机制来避免无限递归(更详细的解释见 )。我通过修改我的 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: 

    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中使用checkedPairsfoo1.Equals(foo2)foo2.Equals(foo1)之间会无限递归跳跃。)

这在我的单线程、非并发沙箱环境中工作正常。但是,我只为 checkedPairs 使用了 static 字段,因为我不知道任何其他方法可以将已收集的项目从 Equals 的一次调用转移到接下来在调用堆栈中。

但是使用这种方法我不能使用多线程或并发环境,其中多个 Equals 检查可能 运行 并行或以混合顺序(例如由于传递 Equals 作为委托并在稍后而不是立即调用它)。

问题:

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

  2. 有没有办法让checkedPairs"call stack static"?这样每个调用堆栈都有自己的 checkedPairs 副本?然后对于每个新的调用堆栈,将创建一个新的(空的)checkedPairs,在递归期间填充,并在递归结束后收集垃圾。

感谢 jdweng 向我指出适用于问题中所述特定代码的简单解决方案:

Foo class 中删除 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);
}

但是,这种方法一般不会奏效。以这个问题中的 class 为例:,想象一下我为 ClubPerson 类似地定义了 MyEquals。由于无法从 class 外部调用 MyEquals (我希望它是私有的),因此仍然会无限递归。例如。当调用 Person.MyEquals 时,它会在内部调用 FavouriteInstitution.Equals,但它应该以某种方式重定向到 FavouriteInstitution.MyEquals(可能已经填充了 checkedPairs!)。此外,Members.SetEquals(other.Members) 将重定向到 Person.Equals 而不是 Person.MyEquals