`+ = 'İçin C # operatör aşırı yüklemesi?


114

Operatör aşırı yüklemeleri yapmaya çalışıyorum +=ama yapamıyorum. Sadece operatör için aşırı yükleme yapabilirim +.

Nasıl olur?

Düzenle

Bunun işe yaramamasının nedeni, bir Vector sınıfımın (bir X ve Y alanlı) olmasıdır. Aşağıdaki örneği düşünün.

vector1 += vector2;

Operatör aşırı yüküm şu şekilde ayarlanmışsa:

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

O zaman sonuç vektör1'e eklenmeyecek, bunun yerine vektör1 de referans olarak yepyeni bir Vector olacaktır.



39
Bunu neden yapmaya çalıştığını açıklayabilir misin? "+" 'Yı aşırı yüklediğinizde ücretsiz olarak aşırı yüklenmiş "+ =" operatörü elde edersiniz. Hangi bazı durum var mı yok istiyorum "+ =" aşırı yüklenmiş başka yapacak değil istiyoruz "+" aşırı edilecek?
Eric Lippert

3
C ++ 'dan gelince, bu sadece yanlış geliyor, ancak C #' da aslında mükemmel bir anlam ifade ediyor.
Jon Purdy


12
@Mathias: güncellemeniz yeniden: Vektörler değişmez matematiksel nesneler gibi davranmalıdır. 2'yi 3'e eklediğinizde, nesne 3'ü nesne 5'e dönüştürmezsiniz. Tamamen yeni bir nesne oluşturursunuz, 5. Toplama operatörlerini aşırı yüklemenin amacı, kendi matematiksel nesnelerinizi yapmaktır; onları değişken hale getirmek bu amaca aykırıdır. Vektör tipinizi değişmez bir değer türü yapardım.
Eric Lippert

Yanıtlar:


147

MSDN'den Aşırı Yüklenebilir Operatörler :

Atama operatörleri aşırı yüklenemez, ancak +=örneğin, +aşırı yüklenebilen kullanılarak değerlendirilir .

Dahası, atama operatörlerinin hiçbiri aşırı yüklenemez. Bunun CLR güçlü tip dünyasında potansiyel bir güvenlik açığı olan Çöp toplama ve bellek yönetimi için bir etki olacağını düşünüyorum.

Yine de, bir operatörün tam olarak ne olduğunu görelim. Ünlü Jeffrey Richter'in kitabına göre , her programlama dilinin özel bir yöntem çağrılarında derlenen kendi operatör listesi vardır ve CLR'nin kendisi operatörler hakkında hiçbir şey bilmiyor. Öyleyse +ve +=operatörlerinin arkasında tam olarak neyin kaldığını görelim .

Bu basit koda bakın:

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

Bu talimatlar için IL kodunu inceleyelim:

  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

Şimdi bu kodu görelim:

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

Ve bunun için IL kodu:

  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

Onlar eşit! Yani +=operatör, C # programınız için sadece sözdizimsel şekerdir ve +operatörü basitçe aşırı yükleyebilirsiniz .

Örneğin:

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

Bu kod şu şekilde derlenecek ve başarıyla çalıştırılacaktır:

  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

Güncelleme:

Güncellemenize göre - @EricLippert'in dediği gibi, vektörlere gerçekten değişmez bir nesne olarak sahip olmalısınız. İki vektörün eklenmesinin sonucu, farklı boyutlara sahip ilki değil, yeni bir vektördür.

Herhangi bir nedenle ilk vektörü değiştirmeniz gerekirse, bu aşırı yüklemeyi kullanabilirsiniz (ama bana gelince, bu çok garip bir davranış):

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

2
Durumun böyle olduğunu belirtmek nedenini cevaplamıyor.
Jouke van der Maas

1
@Jouke van der Maas Peki bunun neden mümkün olmadığını nasıl cevaplamamı istiyorsunuz? Tasarım gereği imkansız, başka ne söyleyebilirim?
VMAtm

2
Neden bu şekilde tasarladılar; asıl soru bu, gerçekten. Diğer cevaplardan bazılarına bakın.
Jouke van der Maas

2
"Garip davranış" yalnızca C # programlamada "doğmuş" iseniz: p. Ancak, cevap doğru olduğu ve çok iyi açıklandığı için +
1'imi

5
@ThunderGr Hayır, hangi dile bakarsanız bakın oldukça garip. Deyimi yapmak için v3 = v1 + v2;sonucu v1sıra sıra değiştirilmesini v3sıradışı
Assimilater


16

Bunun nedeni, atama operatörünün aşırı yüklenememesiyle aynı nedendir. Atamayı doğru bir şekilde gerçekleştirecek bir kod yazamazsınız.

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

Atama operatörleri aşırı yüklenemez, ancak örneğin + =, aşırı yüklenebilen + kullanılarak değerlendirilir.

Gönderen MSDN .


16

Aşırı yüklenemezsiniz +=çünkü bu gerçekten benzersiz bir operatör değil, sadece sözdizimsel şekerdir . x += ysadece kısa bir yazma şeklidir x = x + y. Çünkü +=açısından tanımlanır +ve =siz durumlarda, sorunlara yol açabileceği ayrı olarak geçersiz kılmak için izin operatörler x += yve x = x + ytam aynı şekilde hareket etmedi.

Daha düşük bir düzeyde, C # derleyicisinin her iki ifadeyi de aynı bayt kodunda derlemesi çok olasıdır, bu da çalışma zamanının program yürütme sırasında bunları farklı şekilde ele alamayacağı anlamına gelir .

Bunu ayrı bir işlem olarak ele almak isteyebileceğinizi anlayabiliyorum: x += 10bildiğiniz gibi bir ifadede , eski referansın üzerine atamadan önce xyeni bir nesne oluşturmak yerine , nesneyi yerinde değiştirebilir ve belki biraz zamandan / bellekten tasarruf edebilirsiniz. x + 10.

Ancak şu kodu göz önünde bulundurun:

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

Olmalı a == bsonunda? Çoğu tür için hayır, a10'dan fazladır b. Ama +=operatörü mutasyona uğratması için aşırı yükleyebilirseniz, o zaman evet. Şimdi bunu düşünün ave bprogramın uzak kısımlarına geçilebilir. Olası optimizasyonunuz, nesneniz kodun beklemediği bir yerde değişmeye başlarsa kafa karıştırıcı hatalar yaratabilir.

Başka bir deyişle, performans bu kadar önemliyse, x += 10bir yöntem çağrısı ile değiştirmek çok zor değildir x.increaseBy(10)ve dahil olan herkes için çok daha nettir.


2
Şahsen ben değiştirecek it's just syntactic sugarkadar it's just syntactic sugar in C#; aksi takdirde, kulağa çok genel geliyor, ancak bazı programlama dillerinde bu sadece sözdizimsel şeker değil, aslında performans faydaları sağlayabilir.
Sebastian Mach

Basit (int, float, vb.) Türler için, +=muhtemelen akıllı bir derleyici tarafından optimize edilebilir, çünkü aritmetik basittir. Ancak nesnelerle uğraştığınızda, tüm bahisler kapanır. Herhangi bir dil hemen hemen aynı sorunlarla karşı karşıyadır. Operatör aşırı yüklemesinin kötü olmasının nedeni budur.
benzado

@SebastianMach Soru özellikle c # ve dotnet etiketleriyle etiketlenmiştir. Açıktır ki, örneğin c ++ 'da' + 've' + = '(ve hatta' = ') ayrı ayrı aşırı yüklenebilir.
Bojidar Stanchev

1
@BojidarStanchev: Doğru. 9 yaş genç halim için özür dilerim :-D
Sebastian Mach

9

Bunun nedeni, bu operatörün aşırı yüklenememesidir:

Atama operatörleri aşırı yüklenemez, ancak örneğin + =, aşırı yüklenebilen + kullanılarak değerlendirilir.

MSDN

Sadece +operatörü aşırı yükle, çünkü

x += y eşittir x = x + y


6

Operatörde +kullanılan +=operatör aşırı yükü , A += Beşittir A = operator+(A, B).


6

+Operatörü bu şekilde aşırı yüklerseniz :

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

yapabilirsin

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

veya

 foo = foo + 10;

Bu eşit şekilde derlenecek ve çalışacaktır.


6

Bu sorunun cevabı her zaman aynıdır: Neden +=ücretsiz olarak alırsanız +,. Ama böyle bir dersim olursa ne olur?

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

Hala +="otomatik uygulanmış" olmasının iyi olduğunu söylüyor musunuz? C # ile yüksek performanslı bilgi işlem yapmaya çalışırsanız, işlem süresini ve bellek tüketimini azaltmak için bu tür özelliklere sahip olmanız gerekir, eğer birisi iyi bir çözüme sahipse çok takdir edilir, ancak bana bunu statik yöntemlerle yapmam gerektiğini söylemeyin , bu sadece bir geçici çözümdür ve C # +=tanımlı değilse uygulamayı neden yapar ve tanımlanmışsa kullanılacaktır. Bazı insanlar arasında bir fark olmadığını +ve +=hataları önlediğini söylüyorlar , ama bu benim sorunum değil mi?


2
Performansı gerçekten önemsiyorsanız, operatörün aşırı yüklenmesiyle uğraşmayacaksınız, bu da yalnızca hangi kodun çalıştırıldığını anlamanızı zorlaştırır. Anlambilimini bozmanın +=kendi probleminiz olup olmadığına gelince ... bu sadece kimsenin kodunuzu okuması, sürdürmesi veya çalıştırması gerekmediğinde doğrudur.
benzado

2
Merhaba benzado. Bir şekilde haklısınız, ancak sahip olduğumuz şey, prototip uygulamaları oluşturmak için yüksek performanslı bilgi işlem için bir platform. Bir yandan performansa ihtiyacımız var, diğer yandan basit bir semantik ihtiyacımız var. Aslında, C #'nin şu anda sunduğundan daha fazla operatöre sahip olmayı da seviyoruz. Burada, C # dilinden daha fazlasını elde etmek için bir hizmet tekniği olarak C # 5 ve derleyiciyi umuyorum. Bununla birlikte, C ++ ile büyüdüğüm ve C # 'da C ++' dan biraz daha fazla özellik varsa memnun olacağım, ancak C # yaptığım için C ++ 'ya tekrar dokunmak istemezdim.
msedi

2
Mühendislik tamamen ödün vermekle ilgilidir; istediğiniz her şeyin bir bedeli vardır.
benzado

3
Aritmetik işleçler yeni örnekleri kural olarak döndürür - bu nedenle genellikle değişmez türlerde geçersiz kılınırlar. Örneğin, List<T>bir operatörü kullanarak yeni bir öğe ekleyemezsiniz list += "new item". Onun Addyerine yöntemini çağırırsınız .
Şafak Gür


0

Daha iyi bir tasarım yöntemi Explicit Casting'dir. Casting'i kesinlikle aşırı yükleyebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.