【问题标题】:make a variable last for a call stack为调用堆栈创建最后一个变量
【发布时间】:2018-03-17 08:10:54
【问题描述】:

我有一个包含一些字段的类。我需要按值比较此类的实例,因此我相应地定义了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) 将在调用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 作为委托传递并稍后调用它开启而不是立即开启)。

问题:

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

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

【问题讨论】:

  • 我通常只是在 Equal() 方法中添加一个参数来传递项目。没有理由 Equals 必须有一个参数。如果你有一个带 ICompare 的类,请使用两个参数调用 Equal(object) 调用 MyEqual() 来进行递归。

标签: c# recursion concurrency callstack


【解决方案1】:

感谢 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。由于MyEquals 不能从类外调用(我希望它是私有的),仍然会有无限递归。例如。当Person.MyEquals 被调用时,它会在内部调用FavouriteInstitution.Equals,但它应该以某种方式重定向到FavouriteInstitution.MyEquals(可能已经填充了checkedPairs!)。此外,Members.SetEquals(other.Members) 将重定向到 Person.Equals 而不是 Person.MyEquals

【讨论】:

  • 如果你做foo1.Reference = null; foo2.Reference = null;然后foo1.Equals(foo2);foo1.Equals(foo1);foo2.Equals(foo2);都会抛出空引用异常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-11
  • 2011-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多