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

时间:2012-03-11 15:02:09

标签: 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;
        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;
    }
}

2 个答案:

答案 0 :(得分:26)

  

为什么会这样?

因为语言规则说。

您已向运营商提供此签名:

public static bool operator ==(Foo f1, Foo f2)

然后 - 无论发生在代码中的哪个地方 - 你都有这个表达式:

f1 == null

其中f1的编译时类型为Foo。现在null也可隐式转换为Foo,那么为什么不会使用您的运算符?如果你的操作员的第一行无条件地调用自己,你应该期待堆栈溢出......

为了让发生,您需要对该语言进行两项更改之一:

  • 语言必须特殊 - ====声明中使用时的含义。 ICK。
  • 语言必须决定任何==表达式,其中一个操作数为null 总是意味着参考比较。

IMO也不是特别好。避免它很简单,避免冗余,并添加优化:

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
    }
}

请注意,这使您只能在一个地方进行无效检查。为了避免在f1通过Equals的实例方法调用时对{* 1}}进行冗余比较,您可以==委托给{{} 1}}检查Equals的无效后,但我可能会坚持这一点。

答案 1 :(得分:0)

一个有点老的话题,但是我到达了此页面,因此它可能对某些人有所帮助。 为了保持完全相同的行为,但没有堆栈溢出,我现在将其重写如下:

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;
    }
}