【问题标题】:How EXACTLY can += and -= operators be interpreted?+= 和 -= 运算符如何被解释?
【发布时间】:2017-03-23 01:25:04
【问题描述】:

+=-= 操作符到底是做什么的?

或者它们是隐含的,它们是按类型定义的?

我已经广泛使用它们,这是语法的一个非常简单的功能,但我从未想过它的工作原理。

问题带来了什么

我可以像这样连接一个字符串值:

var myString = "hello ";
myString += "world";

一切都好。但是为什么这不适用于集合呢?

var myCol = new List<string>();
myCol += "hi";

你可能会说'好吧,你正试图附加一个不同的类型,你不能将一个字符串附加到一个不是字符串的类型'。但以下也不起作用:

var myCol = new List<string>();
myCol += new List<string>() { "hi" };

好的,也许它不适用于集合,但以下不是事件处理程序的(某种)集合吗?

myButton.Click += myButton_Click;

我显然对这些运算符的工作原理缺乏深入的了解。

请注意:我并不想在实际项目中以这种方式构建集合myCol。我只是对这个运算符的工作原理感到好奇,这是假设性的。

【问题讨论】:

  • 值得注意的是,“什么是加法”实际上是数学中一个相当棘手的小概念。尽管这个概念对许多人来说是直观的,但它需要大量精心构建的工作来定义它。类似的推论出现在计算机语言设计中。虽然定义 += 的功能很容易,但深入了解 为什么 我们将其编写为以这种方式工作需要更多时间。
  • 我的意思是,真的,"10" + "20" - "1020" 会发生什么?还是"30"?谁来告诉别人回答这个问题的方法是对还是错?
  • 这里的根本问题是+被用来表示正常的数字加法、字符串连接和多播委托的排序,所有这三个操作都只是彼此相切的。这有点滥用我们对加法的直觉,这会导致混乱。最好的办法是将++= 视为由于历史原因具有相同语法的几个不同事物。
  • @corsiKa "10" + "20""1020",因为它们是字符串。 10 + 2030,因为它们是数字。这不是很明显(在静态类型语言中)吗?

标签: c# operators


【解决方案1】:

+= 运算符隐式定义如下:a += b 变为 a = a + b;,与 -= 运算符相同。
(警告:正如Jeppe 指出的那样,如果a 是一个表达式,则在使用a+=b 时只计算一次,但使用a=a+b 时计算两次)

您不能分别重载+=-= 运算符。任何支持+ 运算符的类型也支持+=。您可以通过overloading + and - 为您自己的类型添加对+=-= 的支持。

但是,您发现了一个硬编码到 c# 中的异常:
Events 有一个 +=-= 运算符,可以在列表中添加和删除事件处理程序订阅的事件处理程序。尽管如此,它们不支持+- 运算符。
这不是您可以通过常规运算符重载为自己的类做的事情。

【讨论】:

  • 通常需要注意的是,+= 的左侧仅被评估一次。出于这个原因,CallMethod().Member += 10;CallMethod().Member = CallMethod().Member + 10; 并不完全相同,因为前者的 CallMethod 仅被调用一次,而后者则被调用两次。
  • @SamC 因为添加/组合 (+) 首先发生,在分配 (=) 之前,因此 += 的顺序
  • @SamC 我认为在 =+ 运算符和使用赋值运算符后跟一元 + 运算符之间也会有歧义。编译器如何知道“a =+ b”是指“a = a + b”还是“a = (+b)”?
  • @GeorgeT @GeorgeT 可以通过maximal munch 解决这种歧义,这几乎可以肯定是 C# 中的一般规则( 进行最大咀嚼标记化的语言非常不寻常,而且我想不出一个我的头顶)。这纯粹是历史性的 - 请参阅下面对 SamC 的回复。
  • @SamC 历史原因。在现在称为 C 的语言的最初迭代中,简写 a =+ b,对于所有其他操作和赋值运算符也是如此。出于可读性原因,在Unix V6Unix V7 之间将它们更改为+=:鉴于a=-b,程序员是指a =- b 还是a = -b? V6 编译器会采用前一种解释,但 Kernighan 和 Ritchie 显然认为可能是第二种解释。 C# 逐字从 C 中借用了这一切。
【解决方案2】:

正确的实现实际上比人们想象的要复杂得多。首先,仅仅说a += ba = a+b 完全相同是不够的。它在最简单的情况下具有相同的语义,但它不是简单的文本替换。

首先,如果左边的表达式比一个简单的变量更复杂,它只计算一次。所以M().a += bM().a = M().a + b 不一样,因为这会将值分配给完全不同的对象,而不是从中获取,或者会导致方法的副作用发生两次。

如果M() 返回一个引用类型,那么复合赋值运算符可以被认为是var obj = M(); obj.a = obj.a+b;(但仍然是一个表达式)。然而,如果obj 是一个值类型,那么这种简化也不起作用,以防方法返回一个引用(C# 7 中的新功能)或者它实际上是一个数组元素,或者从索引器返回的东西等,然后操作员确保它不会复制超过修改对象所需的数量,并且它将被应用到正确的位置,没有额外的副作用。

不过,事件分配是完全不同的野兽。 在类范围之外+= 导致调用 add 访问器,-= 导致调用事件的 remove 访问器。如果这些访问器不是用户实现的,则事件分配可能会导致在类范围内的内部委托对象上调用 Delegate.CombineDelegate.Remove。这也是为什么您不能简单地在类之外获取事件对象的原因,因为它不是公共的。 +=/-= 在这种情况下也不是表达式。

我建议阅读 Eric Lippert 的 Compound Assignment。它更详细地描述了这一点。

【讨论】:

  • 表达式 M().a += b; 将是非法的(编译时错误)是声明的返回类型 M 是一个值类型 (struct) 与成员 a。编译器的当前实现使用消息 error CS1612: Cannot modify the return value of 'YourType.M()' because it is not a variable 我想你可以更清楚一点。跨度>
  • @JeppeStigNielsen 是的,我提到它应该只适用于ref-returning 方法。
【解决方案3】:

正如另一个答案所说,+ 运算符没有为List&lt;&gt; 定义。你可以检查它试图重载它,编译器会抛出这个错误One of the parameters of a binary operator must be the containing type

但是作为一个实验,你可以定义你自己的继承List&lt;string&gt;的类并定义+操作符。像这样的:

class StringList : List<string>
{
    public static StringList operator +(StringList lhs, StringList rhs)
    {
        lhs.AddRange(rhs.ToArray<string>());
        return lhs;
    }
}

那么你可以毫无问题地做到这一点:

StringList listString = new StringList() { "a", "b", "c" };
StringList listString2 = new StringList() { "d", "e", "f" };
listString += listString2;

编辑

根据@EugeneRyabtsev 的评论,我对+ 运算符的实现会导致意外行为。所以它应该更像这样:

public static StringList operator +(StringList lhs, StringList rhs)
{
      StringList newList=new StringList();
      newList.AddRange(lhs.ToArray<string>());
      newList.AddRange(rhs.ToArray<string>());
      return newList;
}

【讨论】:

  • 从某种意义上说,这是一个危险的实验,如果你在生产环境中养成了这样做的习惯,那么其他人接下来会写listString3 = listString1 + listString2; 并猜测会发生什么。
  • 好吧,首先正如我所说,这只是一个实验,以了解 + 运算符在 List 中的作用。但是,如果有人这样做listString3 = listString1 + listString2; 会发生什么?我认为它会按预期工作......
  • 可能出乎作者的意料,listString1 会被更改,并随着对listString3 的任何后续修改再次更改,因为它们将是同一个列表(List 是一个类) .无法确定下一个人是否会期望这一点,即使您期望它也是如此。
  • 我明白了。你是对的,这可能会发生,但这不是实验的问题,而是我的实施问题。我会编辑解决它
  • 欢迎了解+=+ 更好的原语的原因:用+= 编写+ 不需要任何成本,用+ 编写+= 需要复制lhs 不必要的。糟透了 C#。
【解决方案4】:

简短的回答是 C# 中的运算符必须为给定类型重载。这也适用于+=string 包含此运算符的重载,但 List&lt;&gt; 没有。因此,无法对列表使用+= 运算符。对于委托,+= 运算符也被重载,这就是您可以在事件处理程序上使用+= 的原因。

略长一点的答案是您可以自己重载运算符。但是,您必须为自己的类型重载它,因此无法为 List&lt;T&gt; 创建重载,而实际上您可以为自己的类(例如从 List&lt;T&gt; 继承的类)执行此操作。

从技术上讲,您并没有真正重载+=-运算符,而是+ 运算符。然后通过将+ 运算符与赋值相结合来推断+= 运算符。为此,+ 运算符应以这样的方式重载,以使结果类型与第一个参数的类型匹配,否则当您尝试使用 += 时,C# 编译器将抛出错误消息。

【讨论】:

  • 谢谢,所以这是非常确定的。框架/语言不知道没有特定类型重载的“标准”添加/追加方式吗?很有趣。
  • @JᴀʏMᴇᴇ 一般没有添加/附加的通用概念
  • 只是说如果你在参考代码中打开String类,运算符+不会重载。
  • @mybirthname 对于字符串,您不会发现 +-operator 的显式重载,true 并且您也不会发现 intdouble 等的显式重载。这基本上是因为 C# 编译器直接支持这些运算符重载,就像代表的 +-运算符一样。有趣的是,decimal 是唯一一个对 + 运算符有显式重载的标准数字 BCL 类。
猜你喜欢
  • 1970-01-01
  • 2012-11-18
  • 1970-01-01
  • 2017-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-17
  • 1970-01-01
相关资源
最近更新 更多