【问题标题】:Equality operator overloading in struct and classes结构和类中的等式运算符重载
【发布时间】:2012-02-08 09:58:14
【问题描述】:

如果我为一个类重载operator ==,我必须在比较字段之前执行一些检查:

  • 如果两个参数都为null,或者两个参数是同一个实例,则返回true

    例如:if (System.Object.ReferenceEquals(arg1, arg2)) return true;

  • 如果一个为null,但不是两者,则返回false

    示例:if (((object)arg1 == null) || ((object)arg2 == null)) return false;

确实,如果我有一个结构并且我想对operator == 进行重载,则这些检查不是必需的,而是无用的,原因如下:结构是一个值类型,所以不能为null,例如DateTime date = null;无效,因为DateTime(即struct)不是引用类型,所以不能比较两个DateTime,其中一个设置为null.

我用operator == 创建了一个简单的结构Point2D,然后我将Point2D 的一个实例与null 进行比较:

Point2D point = new Point2D(0,0);
Console.WriteLine((point == null));
  1. 显然operator == 没有被调用,但比较返回False。调用哪个方法?

  2. documentation 声明不建议在非不可变类型中重载此运算符。为什么?

【问题讨论】:

  • 您应该尝试一次只问一个问题。如果您有两个问题,请分别提出。
  • @svick:对不起。对于接下来的问题,我会避免这个。

标签: c# .net


【解决方案1】:

Chris Shain 的回答是正确,但没有解释为什么这是合法的

当你重写相等运算符,并且两个操作数都是不可为空的值类型,并且返回类型是bool,那么我们免费给你一个解除运算符。也就是说,如果你有

public static bool operator ==(S s1, S s2) { ... }

然后无需额外费用即可获得

public static bool operator ==(S? s1, S? s2) { ... }

被调用的是 那个 操作符。当然,编译器知道结果总是错误的,因为其中一个操作数是 null 而另一个永远不会是。

曾经有一个警告,大意是您的代码总是返回 false,但我们不小心将它禁用了几个版本,并且从未真正重新打开它。明天我将在 Roslyn 编译器中处理这段代码,所以我会看看我能做些什么来让它恢复原状。

【讨论】:

  • 阅读您的答案总是具有教育意义,即使在编写了近十年的 .NET 代码之后也是如此。谢谢:-)
  • Eric,你是说这里有两个Pointnull 类型的操作数;它们之间没有隐式转换,但编译器调用另一种类型的运算符,两个操作数都可以隐式转换为该类型。这似乎与条件运算符的处理不一致,你不能说int? x = someBool ? 7 : null; 我从 C# 版本 2 开始;像1 == null 这样的表达式会在早期版本中编译失败吗?
  • @phoog:这个类比不太合适。 运算符重载解析的正确类比是方法重载解析。如果您有一个静态方法int? Operators.Add(int?, int?) 并调用Operator.Add(1, null),那么您希望选择该方法,即使 int 不能转换为 null 并且 null 不能转换为 int,对吗?将所有内置和提升运算符视为类 Operators 上的静态方法;运算符重载决议只是对这些方法进行重载决议。
  • 感谢您的澄清。
  • 所以为了清楚起见,这是否意味着对于具有重载 operator== 的不可空结构,自动生成的提升运算符在调用重载 operator== 之前执行空检查,这这就是为什么重载的 operator== 永远不需要自己执行此检查的原因?
【解决方案2】:

因为似乎编译器优化了这一点。我试过这段代码:

System.Drawing.Point point = new System.Drawing.Point(0,0);
Console.WriteLine((point == null));

它生成了以下 IL:

IL_0000:  ldloca.s    00 
IL_0002:  ldc.i4.0    
IL_0003:  ldc.i4.0    
IL_0004:  call        System.Drawing.Point..ctor
IL_0009:  ldc.i4.0    
IL_000A:  call        System.Console.WriteLine

这最终归结为“创建一个点,然后将 false 写入命令行”

这也解释了为什么它不呼叫您的接线员。 struct 永远不能为 null,并且在编译器可以保证您始终会因此结果为 false 的情况下,它根本不会费心发布代码来调用运算符。

即使 String 是一个类并且重载 == 运算符,这段代码也会发生同样的事情:

System.Drawing.Point point = new System.Drawing.Point(0,0);
Console.WriteLine("foo" == null);

至于不变性... C# 中的 == 运算符通常被解释为“引用相等”,例如这两个变量指向类的同一个实例。如果你要重载它,那么你通常的意思是说类的两个实例,虽然不是同一个实例,但当它们的数据相同时,它们应该表现得好像它们是同一个实例。经典的例子是字符串。 "A" == GiveMeAnA() 即使GiveMeAnA 返回的实际字符串引用可能与文字"A" 表示的不同。

如果您在不可变的类上重载了== 运算符,那么在评估== 之后类的突变可能会导致许多细微的错误。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2020-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多