C# 为什么在此运算符定义中抛出stackoverflowexception?

C# 为什么在此运算符定义中抛出stackoverflowexception?,c#,.net,operator-overloading,stack-overflow,C#,.net,Operator Overloading,Stack Overflow,请在下面的代码中查看我的评论。如何检查参数是否为null?看起来null被强制转换为Foo,这实际上是对=操作符进行递归调用。为什么会发生这种情况 public class Foo { public static bool operator ==(Foo f1, Foo f2) { if (f1 == null) //This throw a StackOverflowException return f2 == null;

请在下面的代码中查看我的评论。如何检查参数是否为
null
?看起来
null
被强制转换为
Foo
,这实际上是对
=
操作符进行递归调用。为什么会发生这种情况

public class Foo
{
    public static bool operator ==(Foo f1, Foo f2)
    {
        if (f1 == null) //This throw a StackOverflowException
            return f2 == null;
        if (f2 == null)
            return f1 == null;
        else
            return f1.Equals((object)f2);
    }

    public static bool operator !=(Foo f1, Foo f2)
    {
        return !(f1 == f2);
    }

    public override bool Equals(object obj)
    {
        Foo f = obj as Foo;
        if (f == (Foo)null)
            return false;

        return false;
    }

    public override int GetHashCode()
    {
        return 0;
    }
}
为什么会发生这种情况

public class Foo
{
    public static bool operator ==(Foo f1, Foo f2)
    {
        if (f1 == null) //This throw a StackOverflowException
            return f2 == null;
        if (f2 == null)
            return f1 == null;
        else
            return f1.Equals((object)f2);
    }

    public static bool operator !=(Foo f1, Foo f2)
    {
        return !(f1 == f2);
    }

    public override bool Equals(object obj)
    {
        Foo f = obj as Foo;
        if (f == (Foo)null)
            return false;

        return false;
    }

    public override int GetHashCode()
    {
        return 0;
    }
}
因为语言规则是这样说的

您已向操作员提供此签名:

public static bool operator ==(Foo f1, Foo f2)
然后,无论代码中出现什么情况,都会得到以下表达式:

f1 == null
其中
f1
的编译时类型为
Foo
。现在
null
也可以隐式转换为
Foo
,那么它为什么不使用操作符呢?如果操作符的第一行无条件地调用自身,那么应该会出现堆栈溢出

为了避免这种情况发生,您需要对语言进行以下两种更改之一:

  • 当在
    ==
    的声明中使用该语言时,该语言必须具有特殊情况
    ===
    的含义。哎呀
  • 该语言必须确定任何一个操作数为空的
    =
    表达式始终意味着引用比较
在我看来,这两种方法都不是特别好。不过,避免这种方法很简单,避免了冗余,并添加了一个优化:

public static bool operator ==(Foo f1, Foo f2)
{
    if (object.ReferenceEquals(f1, f2))
    {
        return true;
    }
    if (object.ReferenceEquals(f1, null) ||
        object.ReferenceEquals(f2, null))
    {
        return false;
    }
    return f1.Equals(f2);
}
但是,您需要修复
Equals
方法,因为这会导致调用
=
,从而导致另一个堆栈溢出。实际上,你从来没有说过你希望平等是如何决定的

我通常会有这样的东西:

// Where possible, define equality on sealed types.
// It gets messier otherwise...
public sealed class Foo : IEquatable<Foo>
{
    public static bool operator ==(Foo f1, Foo f2)
    {
        if (object.ReferenceEquals(f1, f2))
        {
            return true;
        }
        if (object.ReferenceEquals(f1, null) ||
            object.ReferenceEquals(f2, null))
        {
            return false;
        }

        // Perform actual equality check here
    }

    public override bool Equals(object other)
    {
        return this == (other as Foo);
    }

    public bool Equals(Foo other)
    {
        return this == other;
    }

    public static bool operator !=(Foo f1, Foo f2)
    {
        return !(f1 == f2);
    }

    public override int GetHashCode()
    {
        // Compute hash code here
    }
}
//如果可能,请在密封类型上定义相等。
//否则会变得更混乱。。。
公共密封类Foo:IEquatable
{
公共静态布尔运算符==(Foo f1,Foo f2)
{
if(object.ReferenceEquals(f1,f2))
{
返回true;
}
if(object.ReferenceEquals)(f1,null)||
object.ReferenceEquals(f2,null))
{
返回false;
}
//在此处执行实际相等性检查
}
公共覆盖布尔等于(对象其他)
{
返回this==(其他为Foo);
}
公共布尔等于(Foo其他)
{
返回this==other;
}
公共静态布尔运算符!=(Foo f1,Foo f2)
{
返回!(f1==f2);
}
公共覆盖int GetHashCode()
{
//在这里计算哈希代码
}
}

请注意,这只允许您在一个位置进行空性检查。为了避免在通过
Equals
实例方法调用
f1
时对null进行冗余比较,在检查
f1
的null值后,您可以将
=
委托给
Equals
,但我可能会坚持这一点,所以它可能会帮助一些人。 为了保持完全相同的行为,但没有堆栈溢出,我现在将其重写如下:

public class Foo
{
    public static bool operator ==(Foo f1, Foo f2)
    {
        if (f1 is null) 
            return f2 is null;
        if (f2 is null)
            return false;
        else
            return f1.Equals((object)f2);
    }

    public static bool operator !=(Foo f1, Foo f2)
    {
        return !(f1 == f2);
    }

    public override bool Equals(object obj)
    {
        Foo f = obj as Foo;
        if(f is null) return false;
        return f == this;
    }

    public override int GetHashCode()
    {
        return 0;
    }
}

在==运算符的实现中使用==运算符,并且它位于同一类型上。这里需要的是Object.ReferenceEquals来测试null。如果不是
Foo
,您希望
null
在该
=
操作中是什么类型?@harold:我在
null
上使用它,而不是在同一类型上使用它。@Ropstah左边的操作数使该类型与thinkrelated有关:(workkaround上的线程)谢谢你的详细回复@Jon Skeet,感谢您不仅仅是写了一个简短的回复,并展示了一个完整的示例,非常感谢。解释得很好!不幸的是,要想被这个错误抓住,一个很好的方法就是遵循同样的错误行为…:(如果
f1
f2
都为空怎么办?您的代码将返回
false
。您不应该检查
if(object.ReferenceEquals(f1,null)和&!object.ReferenceEquals(f2,null))
反之亦然?@DrewChapin:不会-它将返回true,因为
if(object.ReferenceEquals(f1,f2)){返回true;}