如何正确地解释+ =和 – =运算符?

+=-=运算符究竟(底层)是什么?

或者它们是隐含的,它们是按照types定义的?

我已经广泛地使用了它们,这是一个非常简单的语法特征,但是我从来没有想过如何工作的深度。

是什么引起了这个问题

我可以像这样连接一个string值:

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

一切都好。 但为什么这不与collections?

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

你可能会说'你试图附加一个不同的types,你不能附加一个string到一个不是string的types'。 但是下面也不行:

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

好吧,也许它不适用于集合,但是下面不是一个(一种)事件处理程序的集合?

 myButton.Click += myButton_Click; 

我显然对这些运营商的工作方式缺乏深入的了解。

请注意:我并没有试图用一种真正的项目来构build集合myCol 我只是好奇这个运营商的运作,这是假设的。

+=运算符隐式定义如下: a += b变成a = a + b;-=运算符相同。
(注意:正如Jeppe指出的那样,如果a是一个expression式,那么只有在使用a+=b时才评估一次,而使用a=a+b则只评估两次)

您不能单独重载+=-=运算符。 任何支持+运算符的types也支持+= 。 您可以通过重载+-来为自己的types添加对+=-=支持。

然而,有一个例外硬编码到C#,你已经发现:
事件有一个+=-=操作符,它将事件处理程序添加到订阅事件处理程序的列表中,并将其删除。 尽pipe如此,他们不支持+-运营商。
这是不是你可以做自己的类与正常运营商重载。

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

但作为一个实验,您可以定义自己的inheritanceList<string>并定义+运算符。 像这样的东西:

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

简单的答案是,C#中的运算符必须重载给定的types。 这也适用于+=string包含此运算符的重载,但是List<>没有。 因此,对列表使用+=运算符是不可能的。 对于代表, +=操作符也被重载,这就是为什么你可以在事件处理器上使用+=原因。

稍长的答案是,你可以自由地超载操作员。 然而,你必须为自己的types重载它,所以为List<T>创build一个重载是不可能的,而你实际上可以为你自己的类做这个,例如inheritance自List<T>

从技术上讲,你并不是真的重载+=操作符,而是+操作符。 然后通过将+运算符与一个赋值相结合来推断+=运算符。 为此, +运算符应该被重载,使得结果types匹配第一个参数的types,否则当你尝试使用+=时,C#编译器会抛出一个错误信息。

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

首先,如果左边的expression式比简单的variables更复杂,那么它只被评估一次。 所以M().a += bM().a = M().a + b不一样, M().a = M().a + b ,因为那样会把值赋给一个完全不同于它的对象,或者会导致方法的副作用发生两次。

如果M()返回一个引用types,则复合赋值运算符可以认为是var obj = M(); obj.a = obj.a+b; var obj = M(); obj.a = obj.a+b; (但仍然是一个expression)。 但是,如果obj是一个值types的话,那么这个简化也不起作用,如果这个方法返回一个引用(C#7中的new),或者实际上是一个数组元素,或者是从一个索引器等返回的东西,那么运算符确保它不会创build比修改对象所需更多的副本,并将其应用到正确的位置,而不会产生额外的副作用。

事件分配是一个完全不同的野兽,虽然。 在类范围外+=导致调用add访问器, -=导致调用remove事件的访问器。 如果这些访问器不是用户实现的,则事件分配可能导致在类范围内的内部委托对象上调用Delegate.CombineDelegate.Remove 。 这也是为什么你不能简单地在课堂以外获得事件对象,因为它不是公开的。 += / -=在这种情况下也不是一个expression式。

我build议阅读Eric Lippert的“ 化合物指定” 。 它更详细地描述了这一点。