【问题标题】:How to perform a null check in an equality operator overload如何在相等运算符重载中执行空检查
【发布时间】:2019-08-30 21:31:58
【问题描述】:

我编写了一个简单的类来包装 XElement。我想将相等操作传递给包装的实例。所以我写了这个:

public class XmlWrapper
{
    protected readonly XElement _element;

    public XmlWrapper(XElement element)
    {
        _element = element;
    }

    static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
    {
        return lhs._element.Equals(rhs._element);
    }

    static public bool operator != (XmlWrapper lhs, XmlWrapper rhs)
    {
        return !(lhs == rhs);
    }
}

这看起来很简单,但实际上它会为非常简单的空检查抛出异常:

    XmlWrapper wrapper = new XmlWrapper(someElement);
    XmlWrapper nullWrapper = null;

    if (wrapper != nullWrapper) Console.WriteLine("Wrapper is not null"); //This line throws

这是因为相等运算符为其参数之一接收 null,因此无法检索包装的 XElement。所以显而易见的想法是添加一个空检查,如下所示:

static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
{
    if (lhs == null && rhs == null) return true;
    if (lhs == null || rhs == null) return false;
    return lhs._element.Equals(rhs._element);
}

但是,这会导致无限递归,因为== 运算符再次调用== 运算符。

如果这是任何其他类型的方法,我只会调用基础,但这不适用于运算符,例如你不会写

if (lhs base.== rhs)

那么我该如何解决这个问题?有没有办法从重载的操作符体内调用基本的== 操作符?或者在不使用==的情况下执行空检查的其他方式?

这里是code on DotNetFiddle。

【问题讨论】:

  • 检查ReferenceEquals(null, lhs)
  • 理想情况下你应该覆盖Equals方法
  • 提供了完整答案here
  • 请注意,完整的答案在 FM "Guidelines for Overriding Equals() and Operator ==" 中提供,特别是:“如果两者都为 null,或者两者都是相同的实例,则返回 true。如果一个为 null,但不是两者,则返回 false。 " if (System.Object.ReferenceEquals(a, b)) ...

标签: c# .net operator-overloading


【解决方案1】:
public static bool operator == (XmlWrapper lhs, XmlWrapper rhs)
{
    if (Object.ReferenceEquals(lhs, null) && Object.ReferenceEquals(rhs, null))
    {
       return true;
    }

    if (Object.ReferenceEquals(lhs, null) || Object.ReferenceEquals(rhs, null))
    {
       return false;
    }

    return lhs._element.Equals(rhs._element);
}

【讨论】:

  • 也看看@nkosi 的回答。一旦你开始实现相等比较的概念,你真的需要全力以赴(==、Equals、GetHashCode 等)
  • 我认为 Rufus L 的答案更简洁,而且如果 lhs._element 为 null(在构造函数中没有防范 null),它也不会抛出。
【解决方案2】:

这应该提供所需的行为。

另请注意,如果覆盖相等,您需要覆盖 GetHashCode

public class XmlWrapper : IEquatable<XmlWrapper> {
    protected readonly XElement _element;

    public XmlWrapper(XElement element) {
        _element = element ?? throw new ArgumentNullException(nameof(element));
    }

    static public bool operator ==(XmlWrapper lhs, XmlWrapper rhs) {
        return Equals(lhs, rhs);
    }

    static public bool operator !=(XmlWrapper lhs, XmlWrapper rhs) {
        return !Equals(lhs, rhs);
    }

    public override string ToString() {
        return _element != null ? _element.ToString() : this.GetType().FullName;
    }

    public override int GetHashCode() {
        return _element.GetHashCode();
    }

    public override bool Equals(object obj) {
        return obj is XmlWrapper other && Equals(other);
    }

    public bool Equals(XmlWrapper other) {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return _element.Equals(other._element);
    }
}

值得注意的是,此实现特定于引用类型。因为XmlWrapper 是一个引用类型。

【讨论】:

  • 我认为operator == 有点过头了(与 Rufus L 的版本相比),但它并没有遗漏任何东西,而且整个东西现在看起来很健壮。
  • 利用静态object.Equals(object,object) 方法...聪明。但我认为现在值得注意的是,这个实现是特定于引用类型的。值类型就像你之前使用operator == 一样,调用lhs.Equals(rhs)。由于XmlWrapper 是一个引用类型,这是一个很好的方法,但你不希望有人盲目地将它复制为值类型。 lhs 和 rhs 将被装箱,并且永远不会比较彼此相等的引用或 null,然后无论如何它都会以 Equals(object) 结束。
  • 总而言之,我认为这很好,但值得注意(可能在评论中)它特定于引用类型,因此它不会用于值类型。
【解决方案3】:

您可以使用三元运算符将代码缩短为一行:

static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
{
    return lhs is null ? rhs is null : !(rhs is null) && lhs._element == rhs._element;
}

其中说,“如果 lhs 为 null,如果 rhs 为 null,则返回 true;如果 rhs 不为 null,则返回 false。否则(如果 lhs 不为 null),如果 rhs 不为 null 并且它们的元素相等,则返回 true,否则返回假的。”

【讨论】:

  • 戴上我的代码审查员的帽子...该代码不容易破译。从长远来看,更清楚地表达您的意图和行为的更冗长的代码更易于阅读和维护。
  • 此operator == 是为该类型定义的,因此它会回调该类型。堆栈溢出异常。这就是为什么 Mads Torgersen 说使用 is null 而不是 == null 的原因:它始终是参考检查,并且始终避免重载运算符。没有方法调用是ReferenceEquals。
  • @flydog57 我想我已经阅读代码太久了,对我来说非常清楚!
  • lhs == null 将不断调用此运算符。它是 return op_Equality(lhs, null) ?...;,这是您在此处定义的非常静态的方法。
  • 只是因为我在单元测试中被它咬过!你不会轻易忘记这一课。
【解决方案4】:

您也可以应用 Elvis 运算符和 null-coalescing 运算符使其工作。

    static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
    {
        if (lhs?._element == null && rhs?._element == null) return true;
        return lhs?._element?.Equals(rhs?._element) ?? false;
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 2016-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多