Подтвердить что ты не робот

Как ТОЧНО может быть интерпретирован оператор + = и - =?

Что именно (под капотом) выполняют операторы += и -=?

Или они неявны в том, что они определены для каждого типа?

Я использовал их широко, это очень простая особенность синтаксиса, но я никогда не думал о глубинах того, как это работает.

Что вызвало вопрос

Я могу связать строковое значение следующим образом:

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 таким образом, в реальном проекте. Мне просто интересно, как работает этот оператор, он гипотетический.

4b9b3361

Ответ 1

Оператор += неявно определяется следующим образом: a += b превращается в a = a + b;, то же самое с оператором -=.
(caveat: as Jeppe указал, что если a - это выражение, оно оценивается только один раз, если вы используете a+=b, но дважды с a=a+b)суб >

Вы не можете перегружать оператора += и -= отдельно. Любой тип, поддерживающий оператор +, также поддерживает +=. Вы можете добавить поддержку += и -= к своим собственным типам перегрузка + и -.

Однако есть одно исключение, жестко закодированное в С#, которое вы обнаружили:
События имеют оператор += и -=, который добавляет и удаляет обработчик событий в список обработчиков подписанных событий. Несмотря на это, они не поддерживают операторы + и -.
Это не то, что вы можете сделать для своих собственных классов с регулярной перегрузкой оператора.

Ответ 2

Как говорит другой ответ, оператор + не определен для 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;

Edit

В комментарии @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;
}

Ответ 3

Короткий ответ заключается в том, что операторы в С# должны быть перегружены для заданного типа. Это также справедливо для +=. string содержит перегрузку этого оператора, но List<> не имеет. Поэтому использование оператора += для списков невозможно. Для делегатов оператор += также перегружен, поэтому вы можете использовать += для обработчиков событий.

Несколько более длинный ответ заключается в том, что вы можете перегружать оператора самостоятельно. Однако вам придется перегружать его для ваших собственных типов, поэтому создание перегрузки для List<T> невозможно, в то время как вы действительно можете сделать это для своего собственного класса, который, например, наследуется от List<T>.

Технически, вы действительно не перегружаете += -оператор, а оператор +. Оператор += затем выводится путем объединения оператора + с назначением. Чтобы это сработало, оператор + должен быть перегружен таким образом, чтобы тип результата соответствовал типу первого аргумента, иначе С# -компилятор собирается выдать сообщение об ошибке при попытке использовать +=.

Ответ 4

Правильная реализация на самом деле довольно сложна, чем можно было бы подумать. Прежде всего, недостаточно сказать, что a += b точно совпадает с a = a+b. Он имеет ту же семантику в простейшем случае, но это не простая замена текста.

Во-первых, если выражение слева сложнее простой переменной, оно оценивается только один раз. Таким образом, M().a += b не совпадает с M().a = M().a + b, поскольку это будет присвоить значение на совершенно другом объекте, чем оно было принято, или вызовет побочные эффекты метода дважды.

Если M() возвращает ссылочный тип, оператор составного присваивания можно представить как var obj = M(); obj.a = obj.a+b; (но все еще являющийся выражением). Однако, если obj имел тип значения, это упрощение также не сработало бы, в случае, если метод вернул ссылку (новый в С# 7) или, если он фактически был элементом массива, или что-то, возвращенное из индексатора и т. то оператор гарантирует, что он не делает больше копий, чем необходимо для изменения объекта, и он будет применен к правильному месту без дополнительных побочных эффектов.

Назначение событий - совсем другое зверь. Вне класса, += приводит к вызову add accessor, а -= приводит к вызову remove accessor события. Если эти аксессоры не реализованы пользователем, назначение событий может привести к вызову Delegate.Combine и Delegate.Remove для внутреннего объекта делегата внутри области класса. Вот почему вы не можете просто получить объект события вне класса, потому что он не является общедоступным. +=/-= также не является выражением в этом случае.

Я рекомендую прочитать Compound Assignment от Eric Lippert. Он описывает это гораздо подробнее.