需要重载运算符<并进行空值检查。

15

我正在C#中重载小于号运算符,想知道是否需要检查 null。下面是一个示例:

public static bool operator <(MyClass x, MyClass y)
{
  if (x == null && y == null)
  {
    return false;
  }
  if (x == null)
  {
    return true; //false?
  }
  if (y == null)
  {
    return false; //true?
  }
  return x.Value < y.Value;
}

还是这样正确吗:

public static bool operator <(MyClass x, MyClass y)
{
  return x.Value < y.Value;
}

我在这方面没有找到任何指导。但也许我错过了什么。

5个回答

13

答案取决于你的使用模式。如果你计划在其中包含null值,并且希望将null值视为小于非null值,则你的实现是正确的;如果你希望将null值视为大于非null对象,则应该使用已注释掉的返回值(falsetrue)。如果你不打算允许null值出现,则抛出ArgumentNullException或允许NullReferenceException将是正确的选择。


2
抛出 ArgumentNullException 吧,孩子们,永远不要故意抛出 NullReferenceException - Dagrooms
1
这是“我本意要这样做”的区别和“哎呀”的区别。 - Dagrooms

5

两种方法都是正确的(针对不同的正确价值观)。

如果 xy 可能为 null 并且在您的情况下具有有效含义,则使用第一种方法。

如果 xy 高度不可能为 null,则使用第二种方法,并让任何异常传播到调用代码以进行处理。


3

个人而言,如果xynull,我会抛出一个ArgumentNullException异常,这应该是一种特殊情况。


6
用户代码不应抛出NullReferenceException,这意味着有编程错误。请使用ArgumentNullException代替。 - asawyer
@mdm 这种区分是人为和武断的。从逻辑上讲,将一个空对象传递给不期望它的方法相当于取消引用一个空指针。唯一有意义的区别发生在方法确实不取消引用时(但我很难想到这样的例子,除非存储该值)。我认为 .NET 框架设计者不应该为此引入两个单独的异常。 - Konrad Rudolph
@KonradRudolph 如果你让框架在方法体中抛出空引用异常,那么调试起来就不像提前抛出“嘿,你使用了错误的参数调用我”异常那么容易。我记得Eric Lippert写过一篇关于这个问题的博客文章,但我现在找不到它了。 - asawyer
@KonradRudolph:我不太同意 :) 如果你调用一个方法并且它抛出了NRE,那么调试可能会很困难(例如,如果您还传递可以为null的参数)-特别是如果您在没有调试符号编译的程序集中调用该方法。我认为应该区分“你调用了我,我抛出了NRE,请好好找出原因”和“你调用了我,但这个名为X的参数不应该为null”。ANE继承自ArgumentException,以及其他8或9种处理此类无效参数的异常。 - mdm
我同意Konrad的逻辑观点。但我仍然认为保留两种异常类型是有意义的,因为它们的语义不同。其中一种异常类型涉及到一个“参数”,并且实际上标识了具体的参数,这比从代码中间抛出的NullReferenceException更有助于调试代码。请记住,如果许多方法缺少空值检查,您可能会在调用树的深处遇到NullReferenceException,而不是实际问题所在的位置。 - Timwi
显示剩余9条评论

1
一个自定义运算符只是一个静态方法而已。此外,通常情况下,运算符不应该抛出异常。这意味着如果 MyClass 是引用类型,你需要进行空值检查。
顺便说一下,惯例上,nulls 应该小于非 nulls,这使得您提出的实现成为惯用语。

第一个参数怎么可能为空? - vulkanino
或者你可以让系统抛出异常,这样也没有什么坏处(除了异常可能不太合适)。 - Konrad Rudolph
@vulkanino:第一个参数可以为空,因为该运算符是作为静态方法实现的。 - Ani
@vulkanino MyClass a = null; MyClass b = null; return a < b;@vulkanino MyClass a = null; MyClass b = null; 返回 a < b; - Servy

-1
  1. 在类上重载操作符是个坏主意,但对于结构体来说可以做。

  2. 如果你决定在类上重载操作符,你需要:
    a. 在你的逻辑中包含空值检查。
    b. 当传入null时抛出异常。
    c. 不进行空值检查并允许出现NullReferenceExceptions(不好)。

基本上,在类上重载运算符是个坏主意。我要么将您的类转换为结构体,要么只需实现一个接口,例如IComparable<T> / IEquatable<T>,这些接口在比较时处理空值有一些指导方针。


我不同意这个观点 - 通常有价值的类是有意义的,特别是因为.NET中相关(有用的)指导方针说“如果不确定,请编写一个类”。 - Konrad Rudolph
重载类的运算符是一个更糟糕的想法。结构体是一个值,可以与其他值进行比较。类是一个引用,不应该像结构体一样对待。我还没有看到过为类(而不是结构体)重载运算符的任何必要性。 - myermian
1
复杂类。你不会重载运算符吗? - vulkanino
我也不同意,可以看看 System.Version。它是一个类,我认为 <, > 等符号很有用并且很有意义。 - Lukas
4
@m-y,你是否重载运算符有逻辑原因:当它对业务实体有意义时,你才会重载这些运算符。另一方面,使用类或结构体是基于技术权衡的技术决策,而不是业务决策。这两个决策(在某种程度上)是独立的。例如,为什么不为String类重载比较运算符呢? - Konrad Rudolph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接