`+ =`のC#演算子のオーバーロード?


114

の演算子のオーバーロードを実行しようとしていますが、実行+=できません。の演算子をオーバーロードにすることしかできません+

どうして?

編集する

これが機能しない理由は、Vectorクラス(XおよびYフィールド付き)があるためです。次の例を考えてみましょう。

vector1 += vector2;

オペレーターのオーバーロードが次のように設定されている場合:

public static Vector operator +(Vector left, Vector right)
{
    return new Vector(right.x + left.x, right.y + left.y);
}

その後、結果はvector1に追加されませんが、代わりにvector1も参照によりまったく新しいベクターになります。


2
:長い議論のように見えますが、すでにこのことについて持ってきたmaurits.wordpress.com/2006/11/27/...
クリス・S

39
なぜこれをやろうとしているのか説明できますか?「+」をオーバーロードすると、オーバーロードされた「+ =」演算子が無料で取得されます。あなたはここで、いくつかの状況が存在しないオーバーロードされるために、「+ =」たいが、ないない、オーバーロードされるように「+」したいの?
Eric Lippert、2011

3
C ++から来ると、これは間違っているように感じますが、C#では実際には完全に理にかなっています。
Jon Purdy、2011


12
@Mathias:更新について:ベクトルは不変の数学オブジェクトのように動作する必要があります。2から3を追加する場合、オブジェクト3をオブジェクト5に変更しません。まったく新しいオブジェクト5を作成します。追加演算子をオーバーロードするポイントは、独自の数学オブジェクトを作成することです。それらを変更可能にすることは、その目的に反します。私はあなたのベクトル型を不変の値型にします。
Eric Lippert、2011

回答:


147

オーバーロード可能な演算子、MSDNから:

代入演算子はオーバーロードできませんが+=、たとえば、は+オーバーロードできるを使用して評価されます。

さらに、どの代入演算子もオーバーロードできません。これは、ガベージコレクションとメモリ管理に影響が出るためだと思います。これは、CLRの強い型指定の世界で潜在的なセキュリティホールになります。

それでも、演算子とは何かを見てみましょう。有名なJeffrey Richterの本によると、各プログラミング言語には独自の演算子リストがあり、特別なメソッド呼び出しでコンパイルされており、CLR自体は演算子について何も知りません。では++=演算子と演算子の後ろに何が残っているかを見てみましょう。

次の簡単なコードをご覧ください。

Decimal d = 10M;
d = d + 10M;
Console.WriteLine(d);

この手順のILコードを見てみましょう。

  IL_0000:  nop
  IL_0001:  ldc.i4.s   10
  IL_0003:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32)
  IL_0008:  stloc.0
  IL_0009:  ldloc.0
  IL_000a:  ldc.i4.s   10
  IL_000c:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32)
  IL_0011:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Addition(valuetype [mscorlib]System.Decimal,
                                                                                                valuetype [mscorlib]System.Decimal)
  IL_0016:  stloc.0

このコードを見てみましょう:

Decimal d1 = 10M;
d1 += 10M;
Console.WriteLine(d1);

そしてこれのためのILコード:

  IL_0000:  nop
  IL_0001:  ldc.i4.s   10
  IL_0003:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32)
  IL_0008:  stloc.0
  IL_0009:  ldloc.0
  IL_000a:  ldc.i4.s   10
  IL_000c:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32)
  IL_0011:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Addition(valuetype [mscorlib]System.Decimal,
                                                                                                valuetype [mscorlib]System.Decimal)
  IL_0016:  stloc.0

彼らは等しいです!したがって、+=演算子はC#でのプログラムの単なる構文上の砂糖であり、+演算子を単純にオーバーロードできます。

例えば:

class Foo
{
    private int c1;

    public Foo(int c11)
    {
        c1 = c11;
    }

    public static Foo operator +(Foo c1, Foo x)
    {
        return new Foo(c1.c1 + x.c1);
    }
}

static void Main(string[] args)
{
    Foo d1 =  new Foo (10);
    Foo d2 = new Foo(11);
    d2 += d1;
}

このコードはコンパイルされ、次のように正常に実行されます。

  IL_0000:  nop
  IL_0001:  ldc.i4.s   10
  IL_0003:  newobj     instance void ConsoleApplication2.Program/Foo::.ctor(int32)
  IL_0008:  stloc.0
  IL_0009:  ldc.i4.s   11
  IL_000b:  newobj     instance void ConsoleApplication2.Program/Foo::.ctor(int32)
  IL_0010:  stloc.1
  IL_0011:  ldloc.1
  IL_0012:  ldloc.0
  IL_0013:  call       class ConsoleApplication2.Program/Foo ConsoleApplication2.Program/Foo::op_Addition(class ConsoleApplication2.Program/Foo,
                                                                                                          class ConsoleApplication2.Program/Foo)
  IL_0018:  stloc.1

更新:

あなたのアップデートによると-@EricLippertが言うように、あなたは本当に不変のオブジェクトとしてベクトルを持つべきです。2つのベクトルを追加した結果は、サイズの異なる最初のベクトルではなく、新しいベクトルです。

何らかの理由で最初のベクトルを変更する必要がある場合は、このオーバーロードを使用できます(ただし、私にとっては、これは非常に奇妙な動作です)。

public static Vector operator +(Vector left, Vector right)
{
    left.x += right.x;
    left.y += right.y;
    return left;
}

2
それが事実であると述べることは理由に答えていません。
Jouke van der Maas、2011

1
@Jouke van der Maasそして、なぜそれが不可能なのか、私にどのように答えてほしいですか?設計上不可能ですが、他に何が言えるでしょうか。
VMAtm 2011

2
なぜこのように設計したのか。それが本当に問題なのです 他のいくつかの答えを見てください。
Jouke van der Maas、2011

2
「奇妙な振る舞い」は、C#:pで「生まれながら」のプログラミングを行っている場合のみです。しかし、答えは正しく、非常によく説明されているので、私の+1も得られます;)
ThunderGr

5
@ThunderGrいいえ、どの言語を見ていても奇妙です。ステートメントを変更することも、通常とは異なるv3 = v1 + v2;ことになるようにするためv1v3
Assimilater '26

17

このリンクは参考になると思います:オーバーロード可能な演算子

代入演算子はオーバーロードできませんが、たとえば+ =は、オーバーロード可能な+を使用して評価されます。


2
@pickypg-このコメントはこのスレッドに関連していません。あなたの回答の削除を取り消してください。私の質問に回答します。私はあなたの方法を使用せざるを得ないと思います。それを解決するより良い方法があると思いました。
Shimmy Weitzhandler 2012年

16

これは、代入演算子をオーバーロードできないのと同じ理由によるものです。割り当てを正しく実行するコードは記述できません。

class Foo
{
   // Won't compile.
   public static Foo operator= (Foo c1, int x)
   {
       // duh... what do I do here?  I can't change the reference of c1.
   }
}

代入演算子はオーバーロードできませんが、たとえば+ =は、オーバーロード可能な+を使用して評価されます。

MSDNから。


16

+=これは実際には一意の演算子ではないため、オーバーロードすることはできません。単なる構文上の糖です。x += yはの速記x = x + yです。そのため+=の用語で定義されている+と、=あなたはケースで、問題を作成することができ、別途、それを上書きすることができ、事業者x += yx = x + yまったく同じように動作しませんでした。

下位レベルでは、C#コンパイラーが両方の式を同じバイトコードにコンパイルする可能性が非常に高いため、プログラムの実行中にランタイムそれらを異なる方法で処理できない可能性が高くなります。

私はあなたがそれを別の操作のように扱いたいかもしれないことを理解できます:古い参照に割り当てる前に新しいオブジェクトを作成するのではなくx += 10xオブジェクトを所定の位置に変更して時間とメモリを節約できることを知っているようなステートメントでx + 10

しかし、このコードを考えてみましょう:

a = ...
b = a;
a += 10;

a == b最後にすべきですか?ほとんどのタイプでは、いいえ、a10以上ですb。しかし、+=演算子をオーバーロードして所定の場所で変異させることができれば、そうです。それaを考えてみてください。そうbすれば、プログラムの遠い部分に回ることができます。コードが予期しない場所でオブジェクトが変化し始めると、可能な最適化によって混乱するバグが発生する可能性があります。

つまり、パフォーマンスがそれほど重要である場合、のx += 10ようなメソッド呼び出しに置き換えるのはそれほど難しくなくx.increaseBy(10)、関係者全員にとって非常に明確です。


2
個人的には、次のように変更it's just syntactic sugarit's just syntactic sugar in C#ます。それ以外の場合、一般的すぎるように聞こえますが、一部のプログラミング言語では、これは単なる構文上の砂糖ではなく、実際にパフォーマンス上の利点をもたらす場合があります。
セバスチャンマッハ

単純な(int、floatなど)型の場合、+=算術演算が単純であるため、おそらくスマートコンパイラーによって最適化できます。しかし、オブジェクトを扱うと、すべての賭けは無効になります。どの言語もほとんど同じ問題に直面しています。これが、オペレーターの過負荷が悪である理由です。
ベンザド2011

@SebastianMach質問には、c#およびdotnetタグが具体的に付けられています。明らかに、例えばc ++では、 '+'と '+ ='(さらには '=')を別々にオーバーロードできます。
Bojidar Stanchev

1
@BojidarStanchev:そうだね。私は自分の9歳の若い自分に謝罪します:-D
セバスチャンマッハ

9

これは、この演算子をオーバーロードできないためです。

代入演算子はオーバーロードできませんが、たとえば+ =は、オーバーロード可能な+を使用して評価されます。

MSDN

+演算子をオーバーロードするだけです。

x += y に等しい x = x + y



6

+このように演算子をオーバーロードすると:

class Foo
{
    public static Foo operator + (Foo c1, int x)
    {
        // implementation
    }
}

できるよ

 Foo foo = new Foo();
 foo += 10;

または

 foo = foo + 10;

これにより、コンパイルと実行が同じになります。


6

この問題には常に同じ答えがあり+=ます+。をオーバーロードした場合に無料で入手できるのに、なぜが必要なのですか。しかし、このようなクラスがあるとどうなりますか。

using System;
using System.IO;

public class Class1
{
    public class MappableObject
    {
        FileStream stream;

        public  int Blocks;
        public int BlockSize;

        public MappableObject(string FileName, int Blocks_in, int BlockSize_in)
        {
            Blocks = Blocks_in;
            BlockSize = BlockSize_in;

            // Just create the file here and set the size
            stream = new FileStream(FileName); // Here we need more params of course to create a file.
            stream.SetLength(sizeof(float) * Blocks * BlockSize);
        }

        public float[] GetBlock(int BlockNo)
        {
            long BlockPos = BlockNo * BlockSize;

            stream.Position = BlockPos;

            using (BinaryReader reader = new BinaryReader(stream))
            {
                float[] resData = new float[BlockSize];
                for (int i = 0; i < BlockSize; i++)
                {
                    // This line is stupid enough for accessing files a lot and the data is large
                    // Maybe someone has an idea to make this faster? I tried a lot and this is the simplest solution
                    // for illustration.
                    resData[i] = reader.ReadSingle();
                }
            }

            retuen resData;
        }

        public void SetBlock(int BlockNo, float[] data)
        {
            long BlockPos = BlockNo * BlockSize;

            stream.Position = BlockPos;

            using (BinaryWriter reader = new BinaryWriter(stream))
            {
                for (int i = 0; i < BlockSize; i++)
                {
                    // Also this line is stupid enough for accessing files a lot and the data is large
                    reader.Write(data[i];
                }
            }

            retuen resData;
        }

        // For adding two MappableObjects
        public static MappableObject operator +(MappableObject A, Mappableobject B)
        {
            // Of course we have to make sure that all dimensions are correct.

            MappableObject result = new MappableObject(Path.GetTempFileName(), A.Blocks, A.BlockSize);

            for (int i = 0; i < Blocks; i++)
            {
                float[] dataA = A.GetBlock(i);
                float[] dataB = B.GetBlock(i);

                float[] C = new float[dataA.Length];

                for (int j = 0; j < BlockSize; j++)
                {
                    C[j] = A[j] + B[j];
                }

                result.SetBlock(i, C);
            }
        }

        // For adding a single float to the whole data.
        public static MappableObject operator +(MappableObject A, float B)
        {
            // Of course we have to make sure that all dimensions are correct.

            MappableObject result = new MappableObject(Path.GetTempFileName(), A.Blocks, A.BlockSize);

            for (int i = 0; i < Blocks; i++)
            {
                float[] dataA = A.GetBlock(i);

                float[] C = new float[dataA.Length];

                for (int j = 0; j < BlockSize; j++)
                {
                    C[j] = A[j] + B;
                }

                result.SetBlock(i, C);
            }
        }

        // Of course this doesn't work, but maybe you can see the effect here.
        // when the += is automimplemented from the definition above I have to create another large
        // object which causes a loss of memory and also takes more time because of the operation -> altgough its
        // simple in the example, but in reality it's much more complex.
        public static MappableObject operator +=(MappableObject A, float B)
        {
            // Of course we have to make sure that all dimensions are correct.

            MappableObject result = new MappableObject(Path.GetTempFileName(), A.Blocks, A.BlockSize);

            for (int i = 0; i < Blocks; i++)
            {
                float[] dataA = A.GetBlock(i);

                for (int j = 0; j < BlockSize; j++)
                {
                    A[j]+= + B;
                }

                result.SetBlock(i, A);
            }
        }
    }
}

それでも+=「自動実装」するのが良いと言いますか?C#でハイパフォーマンスコンピューティングを行おうとする場合、処理時間とメモリ消費を削減するための機能が必要です。良い解決策があれば高く評価されますが、静的メソッドでこれを行う必要があると言わないでください。 、これは回避策にすぎず、 C#が+=定義されていない場合に実装が行われる理由はわかりません。定義されている場合は、使用されます。一部の人々は違いがないとエラー++=防ぐと言いますが、これは私自身の問題ではありませんか?


2
本当にパフォーマンスに関心がある場合は、演算子のオーバーロードをいじくり回すことはありません。これにより、呼び出されているコードを特定することが難しくなります。のセマンティクスを台無しにすること+=があなた自身の問題であるかどうかについては...他の誰もあなたのコードを読んだり、維持したり、実行したりする必要がない場合にのみ当てはまります。
ベンザド2011

2
こんにちは、ベンザド。ある意味ではあなたは正しいですが、私たちが持っているのは、プロトタイプアプリケーションを作成するための高性能コンピューティングのためのプラットフォームです。ある意味ではパフォーマンスが必要であり、もう一方では単純なセマンティクスが必要です。実際、C#が現在提供しているよりも多くの演算子を用意することも好みます。ここでは、C#5とコンパイラーをC#言語からさらに活用するためのサービス手法として期待しています。それにもかかわらず、C ++で成長し、C#のC ++の機能がもう少しあれば感謝しますが、C#をやっているので、もう一度C ++に触れたくありません。
msedi

2
エンジニアリングはすべてトレードオフです。必要なものにはすべて価格が付いています。
ベンザド2011

3
算術演算子は慣例により新しいインスタンスを返します-したがって、それらは通常、不変の型でオーバーライドされます。たとえばのList<T>ような演算子を使用して新しい要素をに追加することはできませんlist += "new item"Add代わりにそのメソッドを呼び出します。
ŞafakGUR


0

より良い設計方法は、明示的キャストです。あなたは間違いなくキャスティングをオーバーロードすることができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.