怎样才能解释+ =和 - =运算符?

时间:2016-11-09 09:24:27

标签: c# operators

+=-=运营商究竟在(引擎盖下)做了什么?

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

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

该问题带来了什么

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

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。我只是对这个算子的运作感到好奇,这是假设的。

4 个答案:

答案 0 :(得分:41)

+=运算符隐式定义如下:a += b变为a = a + b;,与-=运算符相同。
(警告:Jeppe指出,如果a是一个表达式,只有使用a+=b才会评估一次,而a=a+b只评估两次)

您不能单独重载+=-=运算符。支持+运算符的任何类型也支持+=。您可以overloading + and -+=-=的支持添加到您自己的类型中。

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

答案 1 :(得分:28)

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

但作为实验,您可以定义自己继承List<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;
}

答案 2 :(得分:15)

简短的回答是C#中的运算符必须为给定类型重载。这也适用于+=string包含此运算符的重载,但List<>没有。因此,无法使用+=运算符列表。对于代理人来说,+=运算符也会被重载,这就是为什么可以在事件处理程序上使用+=的原因。

稍微长一点的答案是您可以自己使操作员过载。但是,您必须为自己的类型重载它,因此无法为List<T>创建重载,而实际上您可以为自己的类执行此操作,例如继承自List<T>

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

答案 3 :(得分:4)

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

首先,如果左边的表达式比简单变量更复杂,那么它只被评估一次。所以M().a += bM().a = M().a + b不同,因为它会在完全不同的对象上分配值,或者会导致方法的副作用发生两次。

如果M()返回引用类型,则可以将复合赋值运算符视为var obj = M(); obj.a = obj.a+b;(但仍然是表达式)。但是,如果obj是值类型,如果方法返回引用(C#7中的新增内容)或者它实际上是数组元素,或者从索引器返回的内容等,则此简化也不起作用然后,操作员确保它不会产生比修改对象所需的更多副本,并且它将被应用到正确的位置,没有额外的副作用。

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

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