1

たとえば、クラスがあります:

class M
{
     public int val; 

また、その+中の演算子:

    public static M operator +(M a, M b)
    {
        M c = new M();
        c.val = a.val + b.val;
        return c;
    }
}

そして、私はListクラスのオブジェクトを持っています:

List<M> ms = new List();
M obj = new M();
obj.val = 5;
ms.Add(obj);

他のオブジェクト:

M addie = new M();
addie.val = 3;

私がすることができます:

ms[0] += addie;

確かに期待どおりに機能します-リストの値が変更されます。でもやりたいなら

M fromList = ms[0];
fromList += addie;

ms明らかな理由により、値は変更されません。

しかし直感的にはms[0]、その後も変化すると予想しています。実際には、リストからオブジェクトを選択してから、他のオブジェクトでその値を増やします。なので、加算前に参考にしたms[0]ので、実行後fromListもそのままにしておきたいと思いfromListます。

それを達成する方法はありますか?

4

6 に答える 6

6

変わることを期待してはいけません。ms[0]初期化された後fromListはまったく接続されずmsfromListたまたまms[0]同じ値になりますが、それだけです。+= は新しい値を返しているので (実際にはそうあるべきです)、 に格納されている値を変更するだけfromListで、リストの値とは完全に独立しています。(ここでの値は参照であり、オブジェクトではないことに注意してください。)

この動作を変更しようとしないでください。これは正しいことです。代わりに、期待を変えてください。

リストの内容に変更を反映させたい場合は、新しいオブジェクトを参照するようにリストの値を変更するか、新しいオブジェクトを作成する代わりに既存のオブジェクトを変更するようにコードを変更する必要があります。後者のアプローチを取る場合は、オペレーター内でこれを行うべきではありません。代わりに、Addメソッドを作成して、次のように呼び出します。

fromList.Add(addie);

これが変更操作であることは明らかなので、期待を裏切ることはありません。

個人的には、代わりに不変型を使用して、リストを変更する必要がないように (またはリストを直接操作するように) デザインを調整します。

于 2010-02-19T13:55:50.463 に答える
4

変更可能な動作と不変の動作を混在させています。それが混乱を招くところだと思います。

オブジェクトは変更可能 (つまり、そのプロパティを変更できます) であり、そのプロパティに値を割り当てたときに期待どおりに動作します。

+ 演算子を使用している場合、代わりに不変値として動作します。整数のリストがあり、整数を変数に読み込むとします。変数の値を変更した場合、リスト内の整数を変更する必要はありません。

List<int> list = new List<int>() { 1, 2, 3};
int x = list[0];

// this will of cousre not change the content in the list:
x += 42;

このコードがある場合:

M fromList = ms[0];
fromList += addie;

コンパイラは + 演算子を次のように使用します。

M fromList = ms[0];
fromlist = fromList + addie;

fromlist + addieは、クラスの新しいインスタンスへの参照を返し、その参照が変数に割り当てられます。当然、変数が以前に参照していたオブジェクトは変更されません。

于 2010-02-19T14:26:25.610 に答える
0

本当の答えは、彼の + 演算子は lhs オブジェクトを操作していないということです。コピーを作成し、コピーをインクリメントします。彼の + 演算子が実際にオブジェクトを変更した場合、リスト内の値が変更されます。

 M fromlist = ms[0];

fromlist と ms[0] はどちらも同じオブジェクトを指しています。今

 fromlist += addie;

実際に

 fromlist = fromlist.op+(addie);

op+ は、val = fromlist.val + addie.val で新しい M を返します

したがって、fromlist と ms[0] は異なる値を持つ異なるものを指します

于 2010-02-22T22:08:24.220 に答える
0

これは、他の回答では見たことのない別の角度です。

C++ では、配列のようなクラスを作成する方法は、オーバーロード[](添え字演算子として知られています) によるものです。

std::string operator[](int nIndex);

しかし、その定義では項目を読み取ることしかできません。書き込みをサポートするには、私たち C++ ファンボーイがすぐに呼び出しを開始する必要があるものを返す必要がありますlvalue reference

std::string& operator[](int nIndex);

つまり、(少なくともこのコンテキストでは) 代入式の左側に表示できることを意味します。

list[3] = "hi";

しかし、それはあらゆる種類の恐ろしい生涯の問題を引き起こします. そのような参照を取得できる場合は、それを名前にバインドできます。

std::string& oops = list[3];

oopsの内部にあるものを参照するようになりましたlist。これは非常に安全な状況ではありません。操作によっては時限爆弾listが発生する可能性があるためです。これを回避するには、どのように機能するかについて詳細な仕様を用意する必要があります。oopslist

C# の動作は異なります。配列のようなクラスの設計者は、インデクサー(添字演算子の実装の C# 名) の[]一部として、演算子の 2 つの個別の定義を定義します。

public string this[int index]
{
    get { /* return a string somehow */ }
    set { /* do something with implicit 'value' param */ }
}

これにより、左辺値参照の必要性がうまくバイパスされます。また、実際に機能する簡単な例には、気の利いたコンパイラのフットワークが含まれることも意味します。

ms[0] += addie;

他の多くの回答が言っているように、これは通常の+演算子を使用して実装されるため、次のように展開されます。

ms[0] = ms[0] + addie;

しかし、これらの 2 つの出現ms[0]は、実際にはまったく異なるメソッドへの呼び出しです。それは少し似ています:

ms.SetAt(0, ms.GetAt(0) + addie);

そのため、その例が機能します。リストに格納されているオブジェクトを置き換えます。これは、機能しない例に欠けているステップです。

于 2010-02-22T22:48:41.313 に答える
0

演算子の実装を避けるようにしてください。ほとんどの場合、問題を起こす価値はなく、コードをより混乱させます。代わりに単純な加算/減算メソッドを実装してください。また、オブジェクト内に他の値を追加することになった場合、拡張が容易になります。演算子を使用すると、オブジェクト全体に関連するセマンティックを使用して、オブジェクトの一部を操作できます。通常、演算子は構造体と一緒に使用するとより意味があります。構造体は、値型のように動作する必要がある型を実装するときに使用することになっているものです。

于 2010-02-19T14:05:46.437 に答える
0

複合代入と単純な演算子の関係は、C# と C++ ではまったく異なります。

C# では、複合代入演算子は、結果を新しいインスタンスに配置する単純な演算子を呼び出し、元の変数を変更して新しいインスタンスを参照します。元の指示対象は影響を受けません。

C++ では、複合代入演算子が指示対象を変更します。ほとんどの場合、単純な算術演算子は LHS をテンポラリーに複製することから始め、次に複合代入演算子を呼び出してテンポラリーを変更します。

したがって、単純な演算子は両方で同じように機能し、新しいインスタンスを生成しますが、複合代入演算子は常に C++ ではなく C# で新しいインスタンスを作成します。

于 2010-02-22T21:59:46.553 に答える