Перегрузка оператора после инкремента - PullRequest
16 голосов
/ 21 марта 2009

У меня проблемы с попыткой перегрузить оператор постинкрементного преобразования в C #. Используя целые числа, мы получаем следующие результаты.

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), но, наконец, объекты обмениваются. Почему это происходит?

Ответы [ 3 ]

14 голосов
/ 21 марта 2009

Моей первой мыслью было указать, что нормальная семантика ++ - это модификация на месте . Если вы хотите повторить то, что написали бы:

public static Account operator ++(Account a)
{
    a.Balance += 1;
    return a;
}

и не создавать новый объект.

Но потом я понял, что вы пытаетесь имитировать приращение поста.

Итак, моя вторая мысль - «не делай этого» - семантика вообще плохо отображается на объекты, поскольку «используемое» значение действительно является изменяемым местом хранения. Но никому не нравится, когда случайный незнакомец говорит «не делай этого», поэтому я позволю Microsoft сказать тебе не делать этого . И я боюсь, что их слово является окончательным по таким вопросам.

P.S. Что касается , почему делает то, что делает, вы действительно переопределяете оператор преинкремента, а затем используете его , как если бы это был оператор постинкремента.

12 голосов
/ 21 марта 2009

Ключ в понимании того, как работает строка Account b = a++;. Учитывая, как написан ваш код, эта строка эквивалентна этому:

Account b = a;
a++;

И это порядок, в котором он будет выполняться. Назначение эффективно (1) происходит до приращения. Итак, первый эффект этой строки состоит в том, что a и b оба ссылаются на исходный объект a .

Теперь часть ++ будет оценена. Внутри операторного метода мы увеличиваем Balance исходного объекта. В этот момент a и b оба указывают на оригинал с 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's 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
1 голос
/ 21 марта 2009

Вы всегда должны возвращать измененное значение. C # использует это как новое значение и возвращает старое или новое значение в зависимости от оператора.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...