Surcharge de l'opérateur C # pour `+ =`?

114

J'essaie de faire des surcharges d'opérateurs pour +=, mais je ne peux pas. Je ne peux faire une surcharge d'opérateur que pour +.

Comment venir?

Éditer

La raison pour laquelle cela ne fonctionne pas est que j'ai une classe Vector (avec un champ X et Y). Prenons l'exemple suivant.

vector1 += vector2;

Si ma surcharge d'opérateur est définie sur:

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

Ensuite, le résultat ne sera pas ajouté à vector1, mais à la place, vector1 deviendra également un tout nouveau vecteur par référence.

Mathias Lykkegaard Lorenzen
la source
2
On dirait qu'une longue discussion a déjà eu lieu à ce sujet: maurits.wordpress.com/2006/11/27/…
Chris S
39
Pouvez-vous expliquer pourquoi vous essayez de faire cela? Vous obtenez un opérateur "+ =" surchargé gratuitement lorsque vous surchargez "+". Y at - il une situation dans laquelle vous ne voulez « + = » être surchargé , mais ne pas envie « + » pour être surchargé?
Eric Lippert
3
Venant de C ++, cela semble juste faux, mais en C #, cela a en fait un sens parfait.
Jon Purdy
12
@Mathias: votre mise à jour: les vecteurs doivent se comporter comme des objets mathématiques immuables. Lorsque vous ajoutez 2 à 3, vous ne transformez pas l'objet 3 en objet 5. Vous créez un objet entièrement nouveau, 5. Le but de surcharger les opérateurs d'addition est de créer vos propres objets mathématiques; les rendre mutables va à l'encontre de cet objectif. Je ferais de votre type vectoriel un type valeur immuable.
Eric Lippert

Réponses:

147

Opérateurs surchargeables , de MSDN:

Les opérateurs d'affectation ne peuvent pas être surchargés, mais +=, par exemple, sont évalués à l'aide de +, qui peut être surchargé.

De plus, aucun opérateur d'affectation ne peut être surchargé. Je pense que c'est parce qu'il y aura un effet pour le ramassage des ordures et la gestion de la mémoire, qui est une faille de sécurité potentielle dans le monde à fort typage CLR.

Néanmoins, voyons ce qu'est exactement un opérateur. Selon le célèbre livre de Jeffrey Richter , chaque langage de programmation a sa propre liste d'opérateurs, qui sont compilées dans un appel de méthode spécial, et CLR lui-même ne sait rien des opérateurs. Voyons donc ce qui reste exactement derrière les opérateurs +et +=.

Voir ce code simple:

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

Voyons le code IL pour ces instructions:

  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

Voyons maintenant ce code:

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

Et le code IL pour cela:

  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

Ils sont égaux! Ainsi, l' +=opérateur n'est que du sucre syntaxique pour votre programme en C # , et vous pouvez simplement surcharger l' +opérateur.

Par exemple:

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

Ce code sera compilé et exécuté avec succès en tant que:

  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

Mettre à jour:

Selon votre mise à jour - comme le dit @EricLippert, vous devriez vraiment avoir les vecteurs comme un objet immuable. Le résultat de l'ajout des deux vecteurs est un nouveau vecteur, pas le premier avec des tailles différentes.

Si, pour une raison quelconque, vous devez changer le premier vecteur, vous pouvez utiliser cette surcharge (mais comme pour moi, c'est un comportement très étrange):

public static Vector operator +(Vector left, Vector right)
{
    left.x += right.x;
    left.y += right.y;
    return left;
}
VMAtm
la source
2
Dire que c'est le cas, ce n'est pas savoir pourquoi.
Jouke van der Maas
1
@Jouke van der Maas Et comment voulez-vous que je réponde pourquoi ce n'est pas possible? C'est impossible par conception, que puis-je dire d'autre?
VMAtm
2
Pourquoi ils l'ont conçu de cette façon; c'est vraiment ça la question. Voir quelques-unes des autres réponses.
Jouke van der Maas le
2
"Comportement étrange" uniquement si vous êtes "né" de la programmation en C #: p. Mais, comme la réponse est correcte et très bien expliquée, vous obtenez également mon +1;)
ThunderGr
5
@ThunderGr Non, c'est assez étrange quelle que soit la langue que vous regardez. Faire en sorte que la déclaration v3 = v1 + v2;entraîne v1un changement et v3est inhabituel
Assimilateur
17

Je pense que vous trouverez ce lien informatif: les opérateurs surchargeables

Les opérateurs d'affectation ne peuvent pas être surchargés, mais + =, par exemple, est évalué à l'aide de +, qui peut être surchargé.

pickypg
la source
2
@pickypg - ce commentaire n'est pas lié à ce fil: veuillez annuler la suppression de votre réponse , il répond à ma question, je pense que je n'ai pas d'autre choix que d'utiliser votre méthode, j'ai pensé qu'il y avait un meilleur moyen de le résoudre.
Shimmy Weitzhandler
16

C'est pour la même raison que l'opérateur d'affectation ne peut pas être surchargé. Vous ne pouvez pas écrire de code qui effectuerait correctement l'affectation.

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

Les opérateurs d'affectation ne peuvent pas être surchargés, mais + =, par exemple, est évalué à l'aide de +, qui peut être surchargé.

De MSDN .

agent-j
la source
16

Vous ne pouvez pas surcharger +=car ce n'est pas vraiment un opérateur unique, c'est juste du sucre syntaxique . x += yest juste une manière abrégée d'écrire x = x + y. Comme il +=est défini en termes d' opérateurs +et =, vous permettre de le remplacer séparément pourrait créer des problèmes, dans les cas où x += yet x = x + yne se comporterait pas exactement de la même manière.

À un niveau inférieur, il est très probable que le compilateur C # compile les deux expressions dans le même bytecode, ce qui signifie qu'il est très probable que le runtime ne puisse pas les traiter différemment pendant l'exécution du programme.

Je peux comprendre que vous souhaitiez peut-être le traiter comme une opération séparée: dans une instruction comme x += 10vous savez que vous pouvez muter l' xobjet sur place et peut-être économiser du temps / de la mémoire, plutôt que de créer un nouvel objet x + 10avant de l'assigner sur l'ancienne référence .

Mais considérez ce code:

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

Devrait a == bà la fin? Pour la plupart des types, non, ac'est 10 de plus que b. Mais si vous pouviez surcharger l' +=opérateur pour qu'il mute sur place, alors oui. Maintenant, considérez cela aet bpourrait être transmis à des parties éloignées du programme. Votre éventuelle optimisation pourrait créer des bogues déroutants si votre objet commence à changer là où le code ne l'attend pas.

En d'autres termes, si les performances sont si importantes, ce n'est pas trop difficile à remplacer x += 10par un appel de méthode comme x.increaseBy(10), et c'est beaucoup plus clair pour toutes les personnes impliquées.

benzado
la source
2
Personnellement, je changerais it's just syntactic sugaren it's just syntactic sugar in C#; sinon, cela semble trop général, mais dans certains langages de programmation, ce n'est pas seulement du sucre syntaxique, mais peut en fait offrir des avantages en termes de performances.
Sebastian Mach
Pour les types simples (int, float, etc.), +=pourrait probablement être optimisé par un compilateur intelligent, car l'arithmétique est simple. Mais une fois que vous avez affaire à des objets, tous les paris sont ouverts. Toute langue est confrontée à peu près aux mêmes problèmes. C'est pourquoi la surcharge des opérateurs est un mal.
benzado
@SebastianMach La question est spécifiquement balisée avec des balises c # et dotnet. Évidemment, en C ++ par exemple, «+» et «+ =» (et même «=») peuvent être surchargés séparément.
Bojidar Stanchev le
1
@BojidarStanchev: C'est vrai. Je m'excuse pour mon jeune moi de 9 ans :-D
Sebastian Mach
9

C'est parce que cet opérateur ne peut pas être surchargé:

Les opérateurs d'affectation ne peuvent pas être surchargés, mais + =, par exemple, est évalué à l'aide de +, qui peut être surchargé.

MSDN

Surcharger simplement l' +opérateur, à cause de

x += y égal à x = x + y

Andrew Orsich
la source
6

La surcharge de l'opérateur pour +est utilisée dans l' +=opérateur, A += Bégale à A = operator+(A, B).

Alex Sedow
la source
6

Si vous surchargez l' +opérateur comme ceci:

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

tu peux faire

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

ou

 foo = foo + 10;

Cela compilera et fonctionnera de la même manière.

Bala R
la source
6

Il y a toujours la même réponse à ce problème: pourquoi avez-vous besoin du +=, si vous l'obtenez gratuitement si vous surchargez le fichier +. Mais que se passe-t-il si j'ai une classe comme celle-ci.

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

Dites-vous toujours qu'il est bon que le +=soit "implémenté automatiquement". Si vous essayez de faire du calcul haute performance en C #, vous devez avoir de telles fonctionnalités pour réduire le temps de traitement et la consommation de mémoire, si quelqu'un a une bonne solution, c'est très apprécié, mais ne me dites pas que je dois le faire avec des méthodes statiques , ce n'est qu'une solution de contournement et je ne vois aucune raison pour laquelle C # fait l' +=implémentation s'il n'est pas défini, et s'il est défini, il sera utilisé. Certaines personnes disent que ne pas avoir de différence entre +et +=empêche les erreurs, mais n'est-ce pas mon propre problème?

msedi
la source
2
Si vous vous souciez vraiment des performances, vous n'allez pas vous soucier de la surcharge des opérateurs, ce qui rend seulement plus difficile de savoir quel code est appelé. Quant à savoir si gâcher la sémantique de +=est votre propre problème ... ce n'est vrai que si personne d'autre n'a jamais à lire, maintenir ou exécuter votre code.
benzado le
2
Bonjour, benzado. D'une certaine manière, vous avez raison, mais ce que nous avons, c'est une plate-forme de calcul haute performance pour créer des applications prototypes. Dans un sens, nous devons avoir la performance, de l'autre, nous avons besoin d'une simple sémantique. En fait, nous aimons également avoir plus d'opérateurs que C # n'en fournit actuellement. Ici, j'espère que C # 5 et le compilateur en tant que technique de service tireront le meilleur parti du langage C #. Néanmoins, comme j'ai grandi avec C ++ et j'apprécierais qu'il y ait un peu plus de fonctionnalités de C ++ en C #, bien que je ne voudrais pas toucher à nouveau C ++ puisque je fais # C #.
msedi
2
L'ingénierie est une question de compromis; tout ce que vous voulez a un prix.
benzado le
3
Les opérateurs arithmétiques renvoient de nouvelles instances par convention - ils sont donc généralement remplacés sur les types immuables. Vous ne pouvez pas ajouter un nouvel élément à un en List<T>utilisant un opérateur comme list += "new item", par exemple. Vous appelez sa Addméthode à la place.
Şafak Gür
0

Une meilleure méthode de conception est le casting explicite. Vous pouvez certainement surcharger Casting.

N_E
la source