17

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) を持ちますが、最終的にオブジェクトが交換されます。なぜこうなった?

4

3 に答える 3

17

私が最初に考えたのは、++ の通常のセマンティクスはインプレース変更であることを指摘することでした。それを模倣したい場合は、次のように記述します。

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

新しいオブジェクトを作成しません。

しかし、その後、投稿の増分を模倣しようとしていることに気付きました。

したがって、私の 2 番目の考えは、「そうしないでください」です。「使用される」値は実際には変更可能なストレージの場所であるため、セマンティクスはオブジェクトにまったくマップされません。しかし、ランダムな見知らぬ人から「それをしないでください」と言われるのが好きな人はいないので、マイクロソフトにそうしないように言わせます. そして、私は彼らの言葉がそのような問題について最終的であることを恐れています.

PSなぜそれが何をしているのかについては、あなたは実際にプリインクリメント演算子をオーバーライドしてから、ポストインクリメント演算子であるかのように使用しています。

于 2009-03-21T05:10:58.537 に答える
12

重要なのは、ラインがどのように機能するかを理解することAccount b = a++;です。コードがどのように記述されているかを考えると、この行は次のようになります。

Account b = a;
a++;

そして、それが実行される順序です。割り当ては実質的に(1)インクリメントの前に発生します。したがって、この行の最初の効果は、abの両方が元のオブジェクトaを参照することです。

これで ++ 部分が評価されます。operator メソッド内でBalance、元のオブジェクトの をインクリメントします。この時点で、 abは両方ともオリジナルを指しており、 aBalanceは 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
于 2009-03-21T05:30:29.473 に答える
1

常に変更された値を返す必要があります。C# はこれを新しい値として使用し、演算子に応じて古い値または新しい値を返します。

于 2009-03-21T05:18:08.240 に答える