【问题标题】:Why is a stackoverflowexception thrown in this operator definition?为什么此运算符定义中会引发 stackoverflowexception?
【发布时间】:2012-03-11 15:02:08
【问题描述】:

请在下面的代码中查看我的评论。我应该如何检查参数是否为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;
    }
}

【问题讨论】:

  • 您在 == 运算符的实现中使用 == 运算符 - 它属于同一类型。您在这里需要的是 Object.ReferenceEquals 来测试 null。
  • 如果不是Foo,您希望null== 操作中属于什么类型?
  • @harold:我在 null 上使用它,而不是在同一类型上。
  • @Ropstah 左操作数使它成为那种类型虽然
  • 相关:stackoverflow.com/questions/73713/…(解决方法中的一个线程)

标签: c# .net operator-overloading stack-overflow


【解决方案1】:

为什么会这样?

因为语言规则说要。

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

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

然后——无论发生在代码中的什么地方——你都会得到这个表达式:

f1 == null

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

为了使这种发生,您需要对语言进行以下两项更改之一:

  • 当在 == 的声明中使用时,该语言必须对 == 的含义进行特殊处理。艾克。
  • 语言必须确定任何一个操作数为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
    }
}

请注意,这允许您只在一个地方进行无效检查。为了避免在通过Equals 的实例方法调用时将f1 与空值进行冗余比较,您可以在检查空值后从== 委托给Equals f1,但我可能会坚持这样做。

【讨论】:

  • 感谢您的详尽回复!
  • @Jon Skeet,感谢您不仅写了简短的回复,还展示了一个很好的完整示例,非常感谢。
  • 解释清楚!发现这个错误的一个好方法是关注this MSDN example,不幸的是,它包含相同的不当行为...... :(
  • 如果f1f2 都为空怎么办?您的代码将返回 false。你不应该检查if( object.ReferenceEquals(f1,null) &amp;&amp; !object.ReferenceEquals(f2,null) ),反之亦然吗?
  • @DrewChapin:不,它不会——由于if (object.ReferenceEquals(f1, f2)) { return true; },它会返回true
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-05
    • 2013-11-24
    • 2013-12-04
    相关资源
    最近更新 更多