Sovraccarico dell'operatore C # per `+ =`?


114

Sto cercando di eseguire i sovraccarichi dell'operatore per +=, ma non posso. Posso solo creare un sovraccarico dell'operatore per +.

Come mai?

modificare

Il motivo per cui questo non funziona è che ho una classe Vector (con un campo X e Y). Considera il seguente esempio.

vector1 += vector2;

Se il mio sovraccarico dell'operatore è impostato su:

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

Quindi il risultato non verrà aggiunto a vector1, ma anche vector1 diventerà un nuovo vettore per riferimento.


2
Sembra che una lunga discussione sia già stata fatta su questo: maurits.wordpress.com/2006/11/27/…
Chris S

39
Puoi spiegare perché stai cercando di farlo? Ottieni gratuitamente un operatore "+ =" sovraccarico quando sovraccarichi "+". C'è qualche situazione in cui si fa vuole "+ =" per essere sovraccaricato, ma fai non vuole "+" per essere sovraccaricato?
Eric Lippert

3
Provenendo da C ++, questo sembra sbagliato, ma in C # ha davvero perfettamente senso.
Jon Purdy


12
@ Mathias: re il tuo aggiornamento: i vettori dovrebbero comportarsi come oggetti matematici immutabili. Quando si aggiunge 2 a 3, non si modifica l'oggetto 3 nell'oggetto 5. Si crea un oggetto completamente nuovo, 5. Lo scopo di sovraccaricare gli operatori di addizione è creare i propri oggetti matematici; renderli mutabili funziona contro tale scopo. Renderei il tuo tipo di vettore un tipo di valore immutabile.
Eric Lippert

Risposte:


147

Operatori sovraccaricabili , da MSDN:

Gli operatori di assegnazione non possono essere sovraccaricati, ma +=, ad esempio, vengono valutati utilizzando +, che può essere sovraccaricato.

Inoltre, nessuno degli operatori di assegnazione può essere sovraccaricato. Penso che ciò sia dovuto al fatto che ci sarà un effetto per la Garbage Collection e la gestione della memoria, che è un potenziale buco di sicurezza nel mondo fortemente tipizzato di CLR.

Tuttavia, vediamo cos'è esattamente un operatore. Secondo il famoso libro di Jeffrey Richter , ogni linguaggio di programmazione ha il proprio elenco di operatori, che sono compilati in speciali chiamate di metodo, e CLR stesso non sa nulla degli operatori. Quindi vediamo cosa rimane esattamente dietro gli operatori +e +=.

Vedi questo semplice codice:

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

Visualizza il codice IL per queste istruzioni:

  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

Ora vediamo questo codice:

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

E il codice IL per questo:

  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

Sono uguali! Quindi l' +=operatore è solo zucchero sintattico per il tuo programma in C # e puoi semplicemente sovraccaricare l' +operatore.

Per esempio:

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

Questo codice verrà compilato ed eseguito correttamente come:

  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

Aggiornare:

Secondo il tuo aggiornamento, come dice @EricLippert, dovresti davvero avere i vettori come oggetto immutabile. Il risultato dell'aggiunta dei due vettori è un nuovo vettore, non il primo con dimensioni diverse.

Se per qualche motivo hai bisogno di cambiare il primo vettore, puoi usare questo sovraccarico (ma per quanto mi riguarda, questo è un comportamento molto strano):

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

2
Affermare che è così non significa rispondere al perché.
Jouke van der Maas

1
@Jouke van der Maas E come vuoi che risponda perché non è possibile? È impossibile in base alla progettazione, cos'altro posso dire?
VMAtm

2
Perché l'hanno progettato in questo modo; ecco qual è la domanda, davvero. Vedi alcune delle altre risposte.
Jouke van der Maas

2
"Strano comportamento" solo se si è "nati" programmando in C #: p. Ma, poiché la risposta è corretta e molto ben spiegata, ottieni anche il mio +1;)
ThunderGr

5
@ThunderGr No, è piuttosto strano indipendentemente dalla lingua che stai guardando. Per rendere la dichiarazione il v3 = v1 + v2;risultato di v1essere cambiato così come v3è insolito
Assimilater

17

Penso che troverai questo collegamento informativo: Operatori sovraccaricabili

Gli operatori di assegnazione non possono essere sovraccaricati, ma + =, ad esempio, viene valutato utilizzando +, che può essere sovraccaricato.


2
@pickypg - questo commento non è correlato a questo thread: per favore annulla la tua risposta , risponde alla mia domanda, penso di non avere altra scelta che usare il tuo metodo, ho pensato che ci fosse un modo migliore per risolverlo.
Shimmy Weitzhandler

16

Ciò è dovuto allo stesso motivo per cui l'operatore di assegnazione non può essere sovraccaricato. Non è possibile scrivere codice che eseguirà correttamente l'assegnazione.

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

Gli operatori di assegnazione non possono essere sovraccaricati, ma + =, ad esempio, viene valutato utilizzando +, che può essere sovraccaricato.

Da MSDN .


16

Non puoi sovraccaricare +=perché non è davvero un operatore unico, è solo zucchero sintattico . x += yè solo un modo abbreviato di scrivere x = x + y. Poiché +=è definito in termini di operatori +e =, consentire di sovrascriverlo separatamente potrebbe creare problemi, nei casi in cui x += ye x = x + ynon si comportassero esattamente allo stesso modo.

A un livello inferiore, è molto probabile che il compilatore C # compili entrambe le espressioni fino allo stesso bytecode, il che significa che è molto probabile che il runtime non possa trattarle in modo diverso durante l'esecuzione del programma.

Posso capire che potresti voler trattarla come un'operazione separata: in un'affermazione come x += 10sai che puoi mutare l' xoggetto in posizione e forse risparmiare un po 'di tempo / memoria, piuttosto che creare un nuovo oggetto x + 10prima di assegnarlo al vecchio riferimento .

Ma considera questo codice:

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

Dovrebbe a == balla fine? Per la maggior parte dei tipi, no, aè 10 in più di b. Ma se potessi sovraccaricare l' +=operatore per mutare sul posto, allora sì. Ora consideralo ae bpotresti passare a parti distanti del programma. La tua possibile ottimizzazione potrebbe creare bug confusi se il tuo oggetto inizia a cambiare dove il codice non se lo aspetta.

In altre parole, se le prestazioni sono così importanti, non è troppo difficile sostituirle x += 10con una chiamata di metodo come x.increaseBy(10), ed è molto più chiaro per tutte le persone coinvolte.


2
Personalmente, cambierei it's just syntactic sugarin it's just syntactic sugar in C#; altrimenti, sembra troppo generico, ma in alcuni linguaggi di programmazione, non è solo zucchero sintattico ma potrebbe effettivamente dare vantaggi in termini di prestazioni.
Sebastian Mach

Per i tipi semplici (int, float, ecc.), +=Potrebbe probabilmente essere ottimizzato da un compilatore intelligente, perché l'aritmetica è semplice. Ma una volta che hai a che fare con gli oggetti, tutte le scommesse sono annullate. Qualsiasi lingua deve affrontare più o meno gli stessi problemi. Questo è il motivo per cui il sovraccarico dell'operatore è dannoso.
benzado

@SebastianMach La domanda è specificamente contrassegnata con i tag c # e dotnet. Ovviamente, in c ++, ad esempio, "+" e "+ =" (e anche "=") possono essere sovraccaricati separatamente.
Bojidar Stanchev

1
@ BojidarStanchev: è vero. Chiedo scusa per me stesso di 9 anni più giovane :-D
Sebastian Mach

9

Questo perché questo operatore non può essere sovraccaricato:

Gli operatori di assegnazione non possono essere sovraccaricati, ma + =, ad esempio, viene valutato utilizzando +, che può essere sovraccaricato.

MSDN

Basta sovraccaricare l' +operatore, a causa di

x += y uguale a x = x + y


6

L'overload dell'operatore per +viene utilizzato in +=operator, A += Buguale a A = operator+(A, B).


6

Se sovraccarichi l' +operatore in questo modo:

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

tu puoi fare

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

o

 foo = foo + 10;

Questo verrà compilato ed eseguito allo stesso modo.


6

C'è sempre la stessa risposta a questo problema: perché hai bisogno di +=, se lo ottieni gratuitamente se sovraccarichi il file +. Ma cosa succede se ho una classe come questa.

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

Dici ancora che è bene che +=sia "implementato automaticamente". Se provi a fare calcoli ad alte prestazioni in C # devi avere tali caratteristiche per ridurre i tempi di elaborazione e il consumo di memoria, se qualcuno ha una buona soluzione è molto apprezzata, ma non dirmi che devo farlo con metodi statici , questa è solo una soluzione alternativa e non vedo alcun motivo per cui C # esegua l' +=implementazione se non è definito e se è definito verrà utilizzato. Alcune persone dicono che non avere una differenza tra +e +=previene gli errori, ma questo non è un mio problema?


2
Se ti interessano davvero le prestazioni, non dovrai scherzare con il sovraccarico dell'operatore, il che rende solo più difficile dire quale codice viene invocato. Per quanto riguarda se incasinare la semantica di +=è un tuo problema ... questo è vero solo se nessun altro deve leggere, mantenere o eseguire il tuo codice.
benzado

2
Ciao, benzado. In qualche modo hai ragione, ma quello che abbiamo è una piattaforma per l'elaborazione ad alte prestazioni per creare applicazioni prototipo. In un modo abbiamo bisogno della performance, dall'altra abbiamo bisogno di una semantica semplice. In effetti ci piace anche avere più operatori di quelli che C # offre attualmente. Qui spero che C # 5 e la tecnica del compilatore come servizio ottengano di più dal linguaggio C #. Tuttavia, poiché sono cresciuto con C ++ e apprezzerei se ci fossero un po 'più di funzionalità da C ++ in C #, anche se non vorrei toccare di nuovo C ++ dato che # sto facendo C #.
msedi

2
L'ingegneria è tutta una questione di compromessi; tutto quello che vuoi ha un prezzo.
benzado

3
Gli operatori aritmetici restituiscono nuove istanze per convenzione, quindi di solito vengono sovrascritti sui tipi immutabili. Non puoi aggiungere un nuovo elemento a List<T>un operatore come list += "new item", ad esempio. Chiami Addinvece il suo metodo.
Şafak Gür


0

Un metodo di progettazione migliore è il cast esplicito. Puoi sicuramente sovraccaricare Casting.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.