Перегрузка оператора с последующим приращением
У меня возникают проблемы с попыткой перегрузить оператор post increment в С#. Используя целые числа, получаем следующие результаты.
int n;
n = 10;
Console.WriteLine(n); // 10
Console.WriteLine(n++); // 10
Console.WriteLine(n); // 11
n = 10;
Console.WriteLine(n); // 10
Console.WriteLine(++n); // 11
Console.WriteLine(n); // 11
Но, когда я пытаюсь использовать классы, это похоже на обмен объектами.
class Account
{
public int Balance { get; set; }
public string Name { get; set; }
public Account(string name, int balance)
{
Balance = balance;
Name = name;
}
public override string ToString()
{
return Name + " " + Balance.ToString();
}
public static Account operator ++(Account a)
{
Account b = new Account("operator ++", a.Balance);
a.Balance += 1;
return b;
}
public static void Main()
{
Account a = new Account("original", 10);
Console.WriteLine(a); // "original 10"
Account b = a++;
Console.WriteLine(b); // "original 11", expected "operator ++ 10"
Console.WriteLine(a); // "operator ++ 10", expected "original 11"
}
}
Отладка приложения, перегруженного оператора, возвращает новый объект со старым значением (10) и объектом, который был передан по ссылке, имеет новое значение (11), но, наконец, объекты обмениваются. Почему это происходит?
Ответы
Ответ 1
Ключ в понимании того, как работает линия Account b = a++;
. Учитывая, как написан ваш код, эта строка эквивалентна этому:
Account b = a;
a++;
И это будет порядок, в котором он будет выполняться. Эффективное присваивание (1) происходит до приращения. Итак, первый эффект этой строки состоит в том, что a и b оба относятся к исходному объекту a.
Теперь часть ++ будет оценена. Внутри метода оператора мы увеличиваем Balance
исходного объекта. В этот момент a и b оба указывают на оригинал, a Balance
из 11, и b будет продолжать делать это.
Однако вы создали новый объект внутри метода оператора и вернули его в качестве вывода оператора. a теперь будет обновляться, чтобы указать на вновь созданный объект.
Итак, a теперь указывает на новый объект, а b продолжает указывать на оригинал. Таким образом, вывод WriteLine будет заменен.
Как указывал @MarkusQ, оператор ++ предназначен для модификации на месте. Создавая новый объект, вы нарушаете это предположение. Перегрузка операторов по объектам - сложный вопрос, и это отличный пример того, почему в большинстве случаев его лучше избегать.
1. Просто ради точности назначение фактически не происходит до приращения при работе с операторами на объектах, но в этом случае конечный результат будет таким же. Фактически исходная ссылка на объект копируется, операция выполняется на оригинале, а затем скопированная ссылка назначается левой переменной. Это просто проще объяснить, если вы притворитесь, что назначение происходит первым.
Что действительно происходит, так это:
Account b = a++;
приводит к этому, из-за того, как оператор ++ работает с объектами:
Account copy = a;
Account x = new Account("operator ++", a.Balance);
a.Balance += 1; // original object Balance is incremented
a = x; // a now points to the new object, copy still points to the original
Account b = copy; // b and copy now point at the same, original, object
Ответ 2
Моя первая мысль заключалась в том, чтобы указать, что нормальная семантика ++ является модификацией на месте. Если вы хотите подражать тому, что вы пишете:
public static Account operator ++(Account a)
{
a.Balance += 1;
return a;
}
и не создавать новый объект.
Но потом я понял, что вы пытаетесь имитировать приращение постов.
Итак, моя вторая мысль - "не делай этого" - семантика вообще не отображает объекты на объекты, так как значение "используется" действительно является изменчивым местом хранения. Но никто не любит рассказывать "не делай этого" случайным незнакомцем, поэтому я позволю Microsoft скажет вам не делать этого. И я боюсь, что их слово окончательно по этим вопросам.
P.S. Что касается того, почему он делает то, что он делает, вы действительно переопределяете оператор preincrement, а затем используете его, как если бы это был оператор postincrement.
Ответ 3
Вы всегда должны возвращать измененное значение. С# использует это как новое значение и возвращает старое или новое значение, соответствующее оператору.