C # operator kelebihan beban untuk `+ =`?


114

Saya mencoba melakukan kelebihan beban operator +=, tetapi saya tidak bisa. Saya hanya bisa membuat operator kelebihan beban +.

Bagaimana bisa?

Sunting

Alasan ini tidak berhasil adalah karena saya memiliki kelas Vektor (dengan bidang X dan Y). Perhatikan contoh berikut.

vector1 += vector2;

Jika kelebihan beban operator saya disetel ke:

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

Maka hasilnya tidak akan ditambahkan ke vektor1, tetapi sebagai gantinya, vektor1 akan menjadi Vektor baru dengan referensi juga.


2
Sepertinya diskusi panjang telah dilakukan tentang ini: maurits.wordpress.com/2006/11/27/…
Chris S

39
Bisakah Anda menjelaskan mengapa Anda mencoba melakukan ini? Anda mendapatkan operator "+ =" yang kelebihan beban secara gratis saat Anda membebani "+" secara berlebihan. Apakah ada beberapa situasi di mana Anda tidak ingin "+ =" akan kelebihan beban tapi jangan tidak ingin "+" untuk kelebihan beban?
Eric Lippert

3
Berasal dari C ++, ini terasa salah, tetapi di C # ini benar-benar masuk akal.
Jon Purdy


12
@Mathias: re update Anda: Vektor harus berperilaku seperti objek matematika yang tidak berubah. Saat Anda menambahkan 2 ke 3, Anda tidak mengubah objek 3 menjadi objek 5. Anda membuat objek yang sama sekali baru, 5. Tujuan operator penjumlahan membebani adalah membuat objek matematika Anda sendiri; membuat mereka bisa berubah bekerja melawan tujuan itu. Saya akan membuat jenis vektor Anda menjadi jenis nilai yang tidak dapat diubah.
Eric Lippert

Jawaban:


147

Operator Overloadable , dari MSDN:

Operator tugas tidak bisa kelebihan beban, tetapi +=, misalnya, dievaluasi menggunakan +, yang bisa dibebani.

Terlebih lagi, tidak ada operator penugasan yang dapat kelebihan beban. Saya pikir ini karena akan ada efek untuk pengumpulan Sampah dan manajemen memori, yang merupakan lubang keamanan potensial di dunia tipe CLR yang kuat.

Namun demikian, mari kita lihat apa sebenarnya operator itu. Menurut buku Jeffrey Richter yang terkenal , setiap bahasa pemrograman memiliki daftar operatornya sendiri, yang disusun dalam metode panggilan khusus, dan CLR sendiri tidak tahu apa-apa tentang operator. Jadi mari kita lihat apa sebenarnya yang tertinggal di belakang operator +dan +=.

Lihat kode sederhana ini:

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

Mari lihat kode IL untuk instruksi ini:

  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

Sekarang mari kita lihat kode ini:

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

Dan kode IL untuk ini:

  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

Mereka sama! Jadi +=operator hanyalah gula sintaksis untuk program Anda di C # , dan Anda cukup membebani +operator.

Sebagai contoh:

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;
}

Kode ini akan dikompilasi dan berhasil dijalankan sebagai:

  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

Memperbarui:

Menurut Pembaruan Anda - seperti yang dikatakan @EricLippert, Anda benar-benar harus memiliki vektor sebagai objek yang tidak dapat diubah. Hasil penjumlahan kedua vektor tersebut adalah vektor baru , bukan vektor pertama dengan ukuran berbeda.

Jika, karena alasan tertentu Anda perlu mengubah vektor pertama, Anda dapat menggunakan kelebihan ini (tetapi bagi saya, ini adalah perilaku yang sangat aneh):

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

2
Menyatakan bahwa itu masalahnya tidak menjawab mengapa.
Jouke van der Maas

1
@ Jouke van der Maas Dan bagaimana Anda ingin saya menjawab mengapa itu tidak mungkin? Tidak mungkin secara desain, apa lagi yang bisa saya katakan?
VMAtm

2
Mengapa mereka merancangnya seperti ini; itulah pertanyaannya, sungguh. Lihat beberapa jawaban lainnya.
Jouke van der Maas

2
"Perilaku aneh" hanya jika Anda telah "lahir" pemrograman di C #: p. Tapi, karena jawabannya benar dan dijelaskan dengan sangat baik, Anda juga mendapatkan +1 saya;)
ThunderGr

5
@ThunderGr Tidak, ini cukup aneh tidak peduli bahasa apa yang Anda lihat. Untuk membuat v3 = v1 + v2;hasil pernyataan v1berubah dan v3tidak biasa
Assimilater

17

Saya pikir Anda akan menemukan tautan ini informatif: Operator Overloadable

Operator tugas tidak bisa dibebani, tetapi + =, misalnya, dievaluasi menggunakan +, yang bisa dibebani.


2
@pickypg - komentar ini tidak terkait dengan utas ini: harap batalkan penghapusan jawaban Anda , ini menjawab pertanyaan saya, saya rasa saya tidak punya pilihan selain menggunakan metode Anda, saya pikir ada cara yang lebih baik untuk menyelesaikannya.
Shimmy Weitzhandler

16

Ini karena alasan yang sama bahwa operator penugasan tidak dapat kelebihan beban. Anda tidak dapat menulis kode yang akan melakukan tugas dengan benar.

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.
   }
}

Operator tugas tidak bisa dibebani, tetapi + =, misalnya, dievaluasi menggunakan +, yang bisa dibebani.

Dari MSDN .


16

Anda tidak dapat membebani berlebihan +=karena ini bukan operator unik, itu hanya gula sintaksis . x += yhanyalah cara penulisan singkat x = x + y. Karena +=didefinisikan dalam istilah operator +dan =, memungkinkan Anda untuk menimpanya secara terpisah dapat menimbulkan masalah, dalam kasus di mana x += ydan x = x + ytidak berperilaku dengan cara yang persis sama.

Pada level yang lebih rendah, sangat mungkin bahwa compiler C # mengkompilasi kedua ekspresi tersebut ke bytecode yang sama, yang berarti bahwa sangat mungkin runtime tidak dapat memperlakukannya secara berbeda selama eksekusi program.

Saya dapat memahami bahwa Anda mungkin ingin memperlakukannya seperti operasi terpisah: dalam pernyataan seperti x += 10Anda tahu bahwa Anda dapat mengubah xobjek di tempat dan mungkin menghemat waktu / memori, daripada membuat objek baru x + 10sebelum menetapkannya di atas referensi lama .

Tetapi pertimbangkan kode ini:

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

Haruskah a == bdi akhir? Untuk kebanyakan tipe, tidak, alebih dari 10 b. Tetapi jika Anda bisa membebani +=operator untuk bermutasi di tempat, maka ya. Sekarang pertimbangkan itu adan bdapat disebarkan ke bagian program yang jauh. Pengoptimalan yang mungkin Anda lakukan dapat membuat bug yang membingungkan jika objek Anda mulai berubah di tempat yang tidak diharapkan oleh kode.

Dengan kata lain, jika kinerja itu penting, tidak terlalu sulit untuk mengganti x += 10dengan pemanggilan metode seperti x.increaseBy(10), dan itu jauh lebih jelas bagi semua orang yang terlibat.


2
Secara pribadi, saya akan berubah it's just syntactic sugarmenjadi it's just syntactic sugar in C#; jika tidak, kedengarannya terlalu umum, tetapi dalam beberapa bahasa pemrograman, ini bukan hanya gula sintaksis tetapi mungkin benar-benar memberikan manfaat kinerja.
Sebastian Mach

Untuk tipe sederhana (int, float, dll.), +=Mungkin dapat dioptimalkan oleh kompiler cerdas, karena aritmatika itu sederhana. Tapi begitu Anda berurusan dengan objek, semua taruhan dibatalkan. Bahasa apa pun menghadapi masalah yang hampir sama. Inilah sebabnya mengapa kelebihan beban operator itu jahat.
benzado

@SebastianMach Pertanyaan ini secara khusus ditandai dengan c # dan tag dotnet. Jelas, di c ++ misalnya, '+' dan '+ =' (dan bahkan '=') dapat di-overload secara terpisah.
Bojidar Stanchev

1
@BojidarStanchev: Benar itu. Saya minta maaf untuk diri saya yang 9 tahun lebih muda :-D
Sebastian Mach

9

Ini karena operator ini tidak bisa kelebihan beban:

Operator tugas tidak bisa dibebani, tetapi + =, misalnya, dievaluasi menggunakan +, yang bisa dibebani.

MSDN

Hanya membebani +operator, karena

x += y sama dengan x = x + y


6

Operator overload untuk +digunakan di +=operator, A += Bsama dengan A = operator+(A, B).


6

Jika Anda membebani +operator seperti ini:

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

Anda dapat melakukan

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

atau

 foo = foo + 10;

Ini akan mengkompilasi dan berjalan sama.


6

Selalu ada jawaban yang sama untuk masalah ini: Mengapa Anda membutuhkan +=, jika Anda mendapatkannya secara gratis jika Anda membebani file +. Tapi apa jadinya kalau saya punya kelas seperti ini.

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);
            }
        }
    }
}

Apakah Anda masih mengatakan bahwa itu bagus karena +="diterapkan secara otomatis". Jika Anda mencoba melakukan komputasi berkinerja tinggi di C #, Anda perlu memiliki fitur seperti itu untuk mengurangi waktu pemrosesan dan konsumsi memori, jika seseorang memiliki solusi yang baik, itu sangat dihargai, tetapi jangan beri tahu saya bahwa saya harus melakukan ini dengan metode statis , ini hanya solusi dan saya tidak melihat alasan mengapa C # melakukan +=implementasi jika tidak ditentukan, dan jika sudah ditentukan maka akan digunakan. Beberapa orang mengatakan bahwa tidak memiliki perbedaan antara +dan +=mencegah kesalahan, tetapi bukankah ini masalah saya sendiri?


2
Jika Anda benar-benar peduli dengan kinerja, Anda tidak akan dipusingkan dengan kelebihan beban operator, yang hanya mempersulit untuk mengetahui kode apa yang sedang dipanggil. Adapun apakah mengacaukan semantik +=adalah masalah Anda sendiri ... itu hanya benar jika tidak ada orang lain yang harus membaca, memelihara, atau mengeksekusi kode Anda.
benzado

2
Halo, benzado. Dalam beberapa hal Anda benar, tetapi yang kami miliki adalah platform untuk komputasi kinerja tinggi untuk membuat aplikasi prototipe. Di satu sisi kita butuh performansi, di sisi lain kita butuh semantik sederhana. Faktanya, kami juga ingin memiliki lebih banyak operator daripada yang diberikan C # saat ini. Di sini saya berharap untuk C # 5 dan kompilator sebagai teknik layanan untuk mendapatkan lebih banyak dari bahasa C #. Namun demikian ketika saya tumbuh dengan C ++ dan akan sangat menghargai jika ada lebih banyak fitur dari C ++ di C #, meskipun saya tidak ingin menyentuh C ++ lagi karena saya # m melakukan C #.
msedi

2
Teknik adalah tentang pengorbanan; segala sesuatu yang Anda inginkan ada harganya.
benzado

3
Operator aritmatika mengembalikan instance baru berdasarkan konvensi - karena itu biasanya diganti pada tipe yang tidak dapat diubah. Anda tidak dapat menambahkan elemen baru ke List<T>menggunakan operator seperti list += "new item", misalnya. Anda menyebut Addmetodenya sebagai gantinya.
Şafak Gür

3

Saya memiliki pertanyaan yang persis sama dan saya tidak mungkin menjawabnya dengan lebih baik daripada orang ini


0

Metode desain yang lebih baik adalah Pengecoran Eksplisit. Anda pasti dapat membebani Casting.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.