C # -Operatorüberladung für `+ =`?

114

Ich versuche, Operatorüberladungen durchzuführen +=, kann dies aber nicht. Ich kann nur einen Bediener überladen +.

Woher?

Bearbeiten

Der Grund, warum dies nicht funktioniert, ist, dass ich eine Vektorklasse habe (mit einem X- und Y-Feld). Betrachten Sie das folgende Beispiel.

vector1 += vector2;

Wenn meine Bedienerüberlastung auf Folgendes eingestellt ist:

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

Dann wird das Ergebnis nicht zu Vektor1 hinzugefügt, sondern Vektor1 wird auch als Referenz zu einem brandneuen Vektor.

Mathias Lykkegaard Lorenzen
quelle
2
Es sieht so aus, als ob bereits eine lange Diskussion darüber geführt wurde: maurits.wordpress.com/2006/11/27/…
Chris S
39
Können Sie erklären, warum Sie dies versuchen? Sie erhalten einen überladenen Operator "+ =" kostenlos, wenn Sie "+" überladen. Gibt es eine Situation , in der Sie haben „+ =“ wollen lastet , aber tun nicht wollen „+“ zu überlastet werden?
Eric Lippert
3
Aus C ++ kommend fühlt sich das einfach falsch an, aber in C # macht es tatsächlich Sinn.
Jon Purdy
12
@ Mathias: Re your update: Vektoren sollten sich wie unveränderliche mathematische Objekte verhalten. Wenn Sie 2 zu 3 hinzufügen, mutieren Sie das Objekt 3 nicht zum Objekt 5. Sie erstellen ein völlig neues Objekt. 5. Der Punkt beim Überladen von Additionsoperatoren besteht darin, Ihre eigenen mathematischen Objekte zu erstellen. sie gegen dieses Ziel veränderbar zu machen. Ich würde Ihren Vektortyp zu einem unveränderlichen Werttyp machen.
Eric Lippert

Antworten:

147

Überladbare Operatoren von MSDN:

Zuweisungsoperatoren können nicht überladen werden, sondern +=werden beispielsweise mit bewertet +, was überladen werden kann.

Darüber hinaus kann keiner der Zuweisungsoperatoren überladen werden. Ich denke, das liegt daran, dass sich dies auf die Garbage Collection und die Speicherverwaltung auswirken wird, was eine potenzielle Sicherheitslücke in der stark typisierten CLR-Welt darstellt.

Mal sehen, was genau ein Operator ist. Laut dem berühmten Buch von Jeffrey Richter hat jede Programmiersprache eine eigene Operatorliste, die in speziellen Methodenaufrufen zusammengestellt wird, und CLR selbst weiß nichts über Operatoren. Mal sehen, was genau hinter den Operatoren +und bleibt +=.

Siehe diesen einfachen Code:

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

Sehen Sie sich den IL-Code für diese Anleitung an:

  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

Nun sehen wir diesen Code:

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

Und IL-Code dafür:

  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

Sie sind gleich! Der +=Operator ist also nur syntaktischer Zucker für Ihr Programm in C # , und Sie können den +Operator einfach überladen .

Beispielsweise:

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

Dieser Code wird kompiliert und erfolgreich ausgeführt als:

  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

Aktualisieren:

Laut Ihrem Update - wie der @EricLippert sagt, sollten Sie die Vektoren wirklich als unveränderliches Objekt haben. Das Ergebnis der Addition der beiden Vektoren ist ein neuer Vektor, nicht der erste mit unterschiedlichen Größen.

Wenn Sie aus irgendeinem Grund den ersten Vektor ändern müssen, können Sie diese Überladung verwenden (aber für mich ist dies ein sehr seltsames Verhalten):

public static Vector operator +(Vector left, Vector right)
{
    left.x += right.x;
    left.y += right.y;
    return left;
}
VMAtm
quelle
2
Die Aussage, dass dies der Fall ist, beantwortet nicht die Gründe.
Jouke van der Maas
1
@Jouke van der Maas Und wie soll ich antworten, warum das nicht möglich ist? Es ist von Natur aus unmöglich, was kann ich noch sagen?
VMAtm
2
Warum sie es so entworfen haben; Das ist wirklich die Frage. Sehen Sie einige der anderen Antworten.
Jouke van der Maas
2
"Seltsames Verhalten" nur, wenn Sie in C # "programmiert" wurden: p. Aber da die Antwort richtig und sehr gut erklärt ist, bekommst du auch meine +1;)
ThunderGr
5
@ThunderGr Nein, es ist ziemlich seltsam, egal welche Sprache du betrachtest. Um die Aussage zu machen v3 = v1 + v2;in Folge v1sowie geändert wird v3ist ungewöhnlich
Assimilater
17

Ich denke, Sie finden diesen Link informativ: Überladbare Operatoren

Zuweisungsoperatoren können nicht überladen werden, aber + = wird beispielsweise mit + ausgewertet, das überladen werden kann.

pickypg
quelle
2
@pickypg - dieser Kommentar hat nichts mit diesem Thread zu tun: Bitte stellen Sie Ihre Antwort wieder her , er beantwortet meine Frage. Ich glaube, ich habe keine andere Wahl, als Ihre Methode zu verwenden. Ich dachte, es gibt einen besseren Weg, sie zu lösen.
Shimmy Weitzhandler
16

Dies liegt daran, dass der Zuweisungsoperator nicht überladen werden kann. Sie können keinen Code schreiben, der die Zuweisung korrekt ausführen würde.

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

Zuweisungsoperatoren können nicht überladen werden, aber + = wird beispielsweise mit + ausgewertet, das überladen werden kann.

Von MSDN .

Agent-j
quelle
16

Sie können nicht überladen, +=da es sich nicht wirklich um einen eindeutigen Operator handelt, sondern nur um syntaktischen Zucker . x += yist nur eine Kurzschrift x = x + y. Da +=im Hinblick auf die definiert ist +und =Betreiber, so dass Sie es separat außer Kraft setzen könnte zu Problemen führen, in Fällen , in denen x += yundx = x + y nicht in der exakt gleichen Art und Weise verhalten.

Auf einer niedrigeren Ebene ist es sehr wahrscheinlich, dass der C # -Compiler beide Ausdrücke bis auf denselben Bytecode kompiliert, was bedeutet, dass es sehr wahrscheinlich ist, dass die Laufzeit sie während der Programmausführung nicht unterschiedlich behandeln kann.

Ich kann verstehen, dass Sie es möglicherweise wie eine separate Operation behandeln möchten: In einer Anweisung wie x += 10Sie wissen Sie, dass Sie das xObjekt an Ort und Stelle mutieren und möglicherweise Zeit / Speicher sparen können, anstatt ein neues Objekt zu erstellen, x + 10bevor Sie es der alten Referenz zuweisen .

Beachten Sie jedoch diesen Code:

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

Sollte a == bam Ende? Für die meisten Typen ist nein a10 mehr als b. Aber wenn Sie den +=Operator überlasten könnten , um an Ort und Stelle zu mutieren, dann ja. Betrachten Sie nun das aundb um in weit entfernte Teile des Programms weitergegeben werden könnten. Ihre mögliche Optimierung kann zu verwirrenden Fehlern führen, wenn sich Ihr Objekt dort ändert, wo der Code dies nicht erwartet.

Mit anderen Worten, wenn die Leistung so wichtig ist, ist es nicht allzu schwer, sie durch x += 10einen Methodenaufruf wie zu ersetzen x.increaseBy(10), und es ist für alle Beteiligten viel klarer.

Benzado
quelle
2
Persönlich würde ich ändern it's just syntactic sugarzu it's just syntactic sugar in C#; Andernfalls klingt es zu allgemein, aber in einigen Programmiersprachen ist es nicht nur syntaktischer Zucker, sondern kann tatsächlich Leistungsvorteile bringen.
Sebastian Mach
Für einfache (int, float usw.) Typen +=könnte wahrscheinlich ein intelligenter Compiler optimiert werden, da die Arithmetik einfach ist. Sobald Sie sich jedoch mit Objekten befasst haben, sind alle Wetten ungültig. Jede Sprache hat so ziemlich die gleichen Probleme. Aus diesem Grund ist eine Überlastung des Bedieners böse.
Benzado
@SebastianMach Die Frage ist speziell mit c # - und dotnet-Tags versehen. In C ++ können beispielsweise '+' und '+ =' (und sogar '=') separat überladen werden.
Bojidar Stanchev
1
@ BojidarStanchev: Stimmt das. Ich entschuldige mich für mein 9 Jahre jüngeres Ich :-D
Sebastian Mach
9

Dies liegt daran, dass dieser Operator nicht überladen werden kann:

Zuweisungsoperatoren können nicht überladen werden, aber + = wird beispielsweise mit + ausgewertet, das überladen werden kann.

MSDN

Nur Überlastungsoperator +, wegen

x += y gleich x = x + y

Andrew Orsich
quelle
6

Operatorüberlastung für +wird im +=Operator verwendet, A += Bgleich A = operator+(A, B).

Alex Sedow
quelle
6

Wenn Sie den +Operator wie folgt überladen :

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

du kannst tun

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

oder

 foo = foo + 10;

Dies wird gleichermaßen kompiliert und ausgeführt.

Bala R.
quelle
6

Auf dieses Problem gibt es immer die gleiche Antwort: Warum brauchen Sie das +=, wenn Sie es kostenlos bekommen, wenn Sie das überladen +. Aber was passiert, wenn ich so eine Klasse habe?

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

Sagen Sie immer noch, dass es gut ist, dass das +="automatisch implementiert" ist. Wenn Sie versuchen, Hochleistungsrechnen in C # durchzuführen, benötigen Sie solche Funktionen, um die Verarbeitungszeit und den Speicherverbrauch zu reduzieren. Wenn jemand eine gute Lösung hat, wird dies sehr geschätzt, aber sagen Sie mir nicht, dass ich dies mit statischen Methoden tun muss Dies ist nur eine Problemumgehung, und ich sehe keinen Grund, warum C # die +=Implementierung ausführt, wenn sie nicht definiert ist und wenn sie definiert ist, wird sie verwendet. Einige Leute sagen, dass es keinen Unterschied gibt +und +=Fehler verhindert, aber ist das nicht mein eigenes Problem?

msedi
quelle
2
Wenn Sie sich wirklich für die Leistung interessieren, werden Sie nicht mit der Überladung von Operatoren herumspielen, was es nur schwieriger macht zu sagen, welcher Code aufgerufen wird. Ob +=es Ihr eigenes Problem ist , die Semantik von durcheinander zu bringen ... das gilt nur, wenn niemand sonst jemals Ihren Code lesen, warten oder ausführen muss.
Benzado
2
Hallo Benzado. In gewisser Hinsicht haben Sie Recht, aber wir haben eine Plattform für Hochleistungsrechner zur Erstellung von Prototypanwendungen. Auf die eine Art brauchen wir die Leistung, auf der anderen Seite brauchen wir eine einfache Semantik. Tatsächlich möchten wir auch mehr Operatoren haben, als C # derzeit liefert. Hier hoffe ich auf C # 5 und den Compiler als Servicetechnik, um mehr aus der C # -Sprache herauszuholen. Da ich mit C ++ aufgewachsen bin und mich freuen würde, wenn es in C # etwas mehr Funktionen von C ++ gibt, möchte ich C ++ jedoch nicht noch einmal berühren, da ich C # mache.
Msedi
2
Beim Engineering dreht sich alles um Kompromisse. Alles, was Sie wollen, ist mit einem Preis verbunden.
Benzado
3
Arithmetische Operatoren geben neue Instanzen gemäß Konvention zurück - daher werden sie normalerweise bei unveränderlichen Typen überschrieben. Sie können List<T>einem Operator wie list += "new item"z. B. kein neues Element hinzufügen . Sie rufen Addstattdessen die Methode auf.
Şafak Gür
3

Ich hatte genau die gleiche Frage und kann sie unmöglich besser beantworten als diese Person

Gemeinschaft
quelle
0

Eine bessere Entwurfsmethode ist Explicit Casting. Sie können Casting definitiv überladen.

N_E
quelle