+=
和-=
运营商究竟在(引擎盖下)做了什么?
或者它们是隐含的,因为它们是按类型定义的?
我已广泛使用它们,这是语法的一个非常简单的特性,但我从未想过它的工作原理的深度。
该问题带来了什么
我可以像这样连接一个字符串值:
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
。我只是对这个算子的运作感到好奇,这是假设的。
答案 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 += b
与a = a+b
完全相同是不够的。它在最简单的情况下具有相同的语义,但它不是简单的文本替换。
首先,如果左边的表达式比简单变量更复杂,那么它只被评估一次。所以M().a += b
与M().a = M().a + b
不同,因为它会在完全不同的对象上分配值,或者会导致方法的副作用发生两次。
如果M()
返回引用类型,则可以将复合赋值运算符视为var obj = M(); obj.a = obj.a+b;
(但仍然是表达式)。但是,如果obj
是值类型,如果方法返回引用(C#7中的新增内容)或者它实际上是数组元素,或者从索引器返回的内容等,则此简化也不起作用然后,操作员确保它不会产生比修改对象所需的更多副本,并且它将被应用到正确的位置,没有额外的副作用。
事件分配是一个完全不同的野兽。 课外范围,+=
会调用添加访问者,而-=
会调用删除访问者这件事。如果这些访问器不是用户实现的,则事件分配可能会导致在类范围内的内部委托对象上调用 Delegate.Combine 和 Delegate.Remove 。这也是为什么你不能简单地在课堂外获取事件对象的原因,因为它不是公共的。在这种情况下,+=
/ -=
也不是表达式。
我建议阅读Eric Lippert的Compound Assignment。它更详细地描述了这一点。