为调用堆栈创建一个变量 last
make a variable last for a call stack
我有一个包含一些字段的 class。我需要按值比较此 class 的实例,因此我相应地定义了 GetHashCode
和 Equals
。因为 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
中使用checkedPairs
,foo1.Equals(foo2)
和foo2.Equals(foo1)
之间会无限递归跳跃。)
这在我的单线程、非并发沙箱环境中工作正常。但是,我只为 checkedPairs
使用了 static
字段,因为我不知道任何其他方法可以将已收集的项目从 Equals
的一次调用转移到接下来在调用堆栈中。
但是使用这种方法我不能使用多线程或并发环境,其中多个 Equals
检查可能 运行 并行或以混合顺序(例如由于传递 Equals
作为委托并在稍后而不是立即调用它)。
问题:
使用线程静态变量有用吗?恐怕不是,因为我可以想象来自同一个调用堆栈的不同 Equals
调用仍然可以在不同的线程上执行(但我不知道)。
有没有办法让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 为例:,想象一下我为 Club
和 Person
类似地定义了 MyEquals
。由于无法从 class 外部调用 MyEquals
(我希望它是私有的),因此仍然会无限递归。例如。当调用 Person.MyEquals
时,它会在内部调用 FavouriteInstitution.Equals
,但它应该以某种方式重定向到 FavouriteInstitution.MyEquals
(可能已经填充了 checkedPairs
!)。此外,Members.SetEquals(other.Members)
将重定向到 Person.Equals
而不是 Person.MyEquals
。
我有一个包含一些字段的 class。我需要按值比较此 class 的实例,因此我相应地定义了 GetHashCode
和 Equals
。因为 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
中使用checkedPairs
,foo1.Equals(foo2)
和foo2.Equals(foo1)
之间会无限递归跳跃。)
这在我的单线程、非并发沙箱环境中工作正常。但是,我只为 checkedPairs
使用了 static
字段,因为我不知道任何其他方法可以将已收集的项目从 Equals
的一次调用转移到接下来在调用堆栈中。
但是使用这种方法我不能使用多线程或并发环境,其中多个 Equals
检查可能 运行 并行或以混合顺序(例如由于传递 Equals
作为委托并在稍后而不是立即调用它)。
问题:
使用线程静态变量有用吗?恐怕不是,因为我可以想象来自同一个调用堆栈的不同
Equals
调用仍然可以在不同的线程上执行(但我不知道)。有没有办法让
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 为例:Club
和 Person
类似地定义了 MyEquals
。由于无法从 class 外部调用 MyEquals
(我希望它是私有的),因此仍然会无限递归。例如。当调用 Person.MyEquals
时,它会在内部调用 FavouriteInstitution.Equals
,但它应该以某种方式重定向到 FavouriteInstitution.MyEquals
(可能已经填充了 checkedPairs
!)。此外,Members.SetEquals(other.Members)
将重定向到 Person.Equals
而不是 Person.MyEquals
。