如何准确地解释+=和-=操作符

本文关键字:操作符 何准确 解释 | 更新日期: 2023-09-27 18:16:59

究竟(在引擎盖下)+=-=操作符做什么?

或者它们是隐式的,因为它们是每个类型定义的?

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

什么带来了这个问题

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

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在这种方式,在一个真正的项目。我只是好奇这个算子的工作原理,这是假设的。

如何准确地解释+=和-=操作符

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

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

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

正如另一个答案所说,+运算符没有为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;
}

简短的回答是c#中的操作符必须为给定的类型重载。这也适用于+=string包含该操作符的重载,但List<>没有。因此,对列表使用+=运算符是不可能的。对于委托,+=操作符也是重载的,这就是为什么您可以在事件处理程序上使用+=

稍微长一点的答案是,您可以自己自由地重载操作符。但是,您必须为自己的类型重载它,因此为List<T>创建重载是不可能的,而实际上您可以为自己的类(例如继承自List<T>的类)这样做。

从技术上讲,您实际上并没有重载+= -操作符,而是重载+操作符。然后通过将+操作符与赋值结合来推断+=操作符。为了使其工作,+操作符应该以这样一种方式重载,即结果类型与第一个参数的类型匹配,否则当您尝试使用+=时,c#编译器将抛出错误消息。

正确的实现实际上比人们想象的要复杂得多。首先,说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。组合Delegate。移除类范围内内部委托对象上的。这也是为什么不能简单地从类外部获取事件对象的原因,因为它不是公共的。在本例中,+=/-=也不是表达式。

我推荐阅读Eric Lippert的复合式作业。