为调用堆栈创建最后一个变量

时间:2017-10-05 16:04:17

标签: c# recursion concurrency callstack

我有一个包含一些字段的类。我需要按值比较此类的实例,因此我相应地定义了GetHashCodeEquals。因为类允许循环引用,所以我需要一种机制来避免无限递归(有关更详细的解释,请参阅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)会在调用(foo1,foo2)之前将checkedPairs存储在foo2.Equals(foo1)中。在foo2.Equals(foo1)内,我们会注意到checkedPairs包含(foo1,foo2),并且会返回true。此结果将转移到equal调用中的foo1.Equals(foo2)变量,然后checkedPairs被清除,true最终返回到main方法。

(在checkedPairs内部不使用Equalsfoo1.Equals(foo2)foo2.Equals(foo1)之间会有无限递归。)

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

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

问题:

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

  2. 有没有办法让checkedPairs&#34;调用堆栈静态&#34;?这样每个调用堆栈都有自己的checkedPairs副本?然后,对于每个新的调用堆栈,将创建一个新的(空)perl -lane 'print join " ", grep {/\b[bcdfghjklmnpqrstvwxyz][[:alpha:]]*[aeiou]\b/i} @F' file ,在递归期间填充,并在递归结束后收集垃圾。

1 个答案:

答案 0 :(得分:2)

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

checkedPairs类中删除Foo字段,并使用以下代码替换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?,并想象我为MyEqualsClub类似地定义了Person。由于MyEquals不能从类外部调用(我希望它是私有的),因此仍然会有无限递归。例如。调用Person.MyEquals时,它会在内部调用FavouriteInstitution.Equals,但它应该以某种方式重定向到FavouriteInstitution.MyEquals(可能已填充checkedPairs!)。此外,Members.SetEquals(other.Members)会重定向到Person.Equals而不是Person.MyEquals