web-dev-qa-db-ja.com

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

+=の演算子オーバーロードをしようとしていますが、できません。 +の演算子オーバーロードのみを作成できます。

どうして?

編集

これが機能しない理由は、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は参照による真新しいVectorになります。

オーバーロード可能な演算子 、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つのベクトルを加算した結果はnewベクトルであり、サイズの異なる最初のベクトルではありません。

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

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

このリンクは参考になると思います: Overloadable Operators

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

17
pickypg

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

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
agent-j

+=をオーバーロードすることはできません。これは実際には一意の演算子ではなく、単なる 構文糖 であるためです。 x += yx = x + yを書くための簡単な方法です。 +=+演算子と=演算子の観点から定義されているため、x += yx = x + y演算子を別々にオーバーライドすると問題が発生する可能性がありますまったく同じように振る舞わないでください。

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

私はあなたがそれを別の操作のように扱いたいかもしれないことを理解することができます:x += 10のようなステートメントで、あなたはxオブジェクトを変更し、おそらく古い参照に割り当てる前の新しいオブジェクトx + 10.

ただし、次のコードを検討してください。

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

最後にa == bする必要がありますか?ほとんどのタイプでは、いいえ、abよりも10多いです。ただし、+=演算子をオーバーロードして、その場で変更できる場合は、そうです。ここで、abがプログラムの離れた部分に渡される可能性があることを考慮してください。コードが予期しない場所でオブジェクトが変化し始めた場合、可能な最適化により混乱を招くバグが作成される可能性があります。

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

16
benzado

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

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

MSDN

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

x += yx = x + yと等しい

9
Andrew Orsich

+の演算子オーバーロードは+=演算子で使用され、A += BA = operator+(A, B)と等しくなります。

6
Alex Sedow

+演算子を次のようにオーバーロードした場合:

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

できるよ

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

または

 foo = foo + 10;

これは等しくコンパイルおよび実行されます。

6
Bala R

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

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#が+=実装を行う理由はありません使用されます。 ++=の違いがないことでエラーを防ぐと言う人もいますが、これは私の問題ではありませんか?

6
msedi

私はまったく同じ質問をしましたが、私はおそらくそれよりもうまく答えることができません この人が持っています

3
user34537

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

0
N_E