web-dev-qa-db-ja.com

+ =および-=演算子はどのように解釈できますか?

(フードの下で)+=および-=演算子は?

または、タイプごとに定義されているという点で暗黙的ですか?

私はそれらを広範囲に使用しました。これは構文の非常に単純な機能ですが、どのように機能するかについては考えたこともありません。

質問について持ってきたもの

次のように文字列値を連結できます。

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を実際のプロジェクトでこのように構築しようとはしていません。私は単にこの演算子の動作に興味があります、それは仮説です。

42
user1017882

+=演算子は、暗黙的に次のように定義されます。a += bは、a = a + b;演算子と同じ-=に変わります。
(注意:as Jeppe 指摘、aが式の場合、a+=bを使用する場合は1回だけ評価されますが、 a=a+b

+=演算子と-=演算子を別々にオーバーロードすることはできません。 +演算子をサポートするタイプは、+=もサポートします。 オーバーロード+=および-= により、独自のタイプに+および-のサポートを追加できます。

ただし、c#にハードコードされた例外が1つあり、これを発見しました。
Eventsには+=および-=演算子があり、サブスクライブされたイベントハンドラーのリストにイベントハンドラーを追加および削除します。それにもかかわらず、+および-演算子はサポートされていません。
これは、通常の演算子のオーバーロードを使用して独自のクラスに対して実行できることではありません。

41
HugoRune

他の答えが言うように、+演算子は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;

編集

@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;
}
28
Pikoh

簡単な答えは、C#の演算子は特定の型に対してオーバーロードする必要があるということです。これは+=にも当てはまります。 stringにはこの演算子のオーバーロードが含まれていますが、List<>には含まれていません。したがって、リストに+=演算子を使用することはできません。デリゲートの場合、+=演算子もオーバーロードされるため、イベントハンドラーで+=を使用できます。

少し長めの答えは、自分で演算子を自由にオーバーロードできるということです。ただし、独自の型ではオーバーロードする必要があるため、List<T>のオーバーロードを作成することはできませんが、実際には、たとえばList<T>を継承する独自のクラスでこれを行うことができます。

技術的には、実際には+=- operatorをオーバーロードするのではなく、+演算子をオーバーロードします。 +=演算子は、+演算子を割り当てと組み合わせることにより推論されます。これが機能するには、結果の型が最初の引数の型と一致するように+演算子をオーバーロードする必要があります。そうしないと、C#コンパイラーは+=を使用しようとしたときにエラーメッセージをスローします。

15
Georg

正しい実装は、実際には思っているよりもかなり複雑です。まず、_a += b_が_a = a+b_とまったく同じであると言うだけでは不十分です。最も単純な場合でも同じセマンティクスを持ちますが、単純なテキスト置換ではありません。

まず、左側の式が単純な変数よりも複雑な場合は、一度だけ評価されます。したがって、M().a += bM().a = M().a + bと同じではありません。これは、取得元とはまったく異なるオブジェクトに値を割り当てるか、メソッドの副作用が2回発生するためです。

M()が参照型を返す場合、複合代入演算子はvar obj = M(); obj.a = obj.a+b;(ただし式のまま)と見なすことができます。ただし、objが値型である場合、メソッドが参照(C#7の新機能)を返した場合、または実際に配列要素、またはインデクサーから返されたものの場合、この単純化も機能しません。など、その後、オペレーターは、オブジェクトを変更するために必要以上にコピーを作成しないことを保証し、追加の副作用なしで正しい場所に適用されます。

ただし、イベントの割り当てはまったく異なります。 クラススコープの外側、_+=_は、addアクセサーを呼び出し、_-=_は、イベントのremoveアクセサーを呼び出します。これらのアクセサーがユーザー実装されていない場合、イベントの割り当てにより、内部でDelegate.CombineおよびDelegate.Removeが呼び出される可能性がありますクラススコープ内のオブジェクトを委任します。また、イベントオブジェクトはパブリックではないため、クラスの外でイベントオブジェクトを取得することはできません。 _+=_/_-=_もこの場合の式ではありません。

Eric Lippertによる Compound Assignment を読むことをお勧めします。これについて詳しく説明します。

4
IllidanS4