web-dev-qa-db-fra.com

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

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

Comment venir?

Éditer

La raison pour laquelle cela ne fonctionne pas, c'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 vector1 deviendra également un nouveau vecteur par référence.

opérateurs surchargeables , à partir de MSDN:

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

Encore plus, aucun des opérateurs d’affectation ne peut être surchargé. Je pense que cela est dû au fait que la récupération de place et la gestion de la mémoire auront un effet, ce qui constitue un trou de sécurité potentiel dans le monde caractérisé par le typage fort 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, compilée dans des appels de méthode spéciaux, et le 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);

Laissons voir 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 IL-code 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! Donc, l'opérateur += N'est qu'un 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

Mise à jour:

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

Si, pour une raison quelconque, vous devez modifier le premier vecteur, vous pouvez utiliser cette surcharge (mais pour moi, il s'agit d'un comportement très étrange):

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

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

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

17
pickypg

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 permettant d'exécuter 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'assignation ne peuvent pas être surchargés, mais + =, par exemple, est évalué à l'aide de +, qui peut être surchargé.

De MSDN .

16
agent-j

Vous ne pouvez pas surcharger += Car ce n'est pas vraiment un opérateur unique, c'est juste sucre syntaxique . x += y N'est qu'un moyen d'écrire x = x + y. Parce que += 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 += y Et x = x + y Ne s'est pas comporté exactement de la même manière.

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

Je peux comprendre que vous souhaitiez peut-être le traiter comme une opération distincte: dans une instruction du type x += 10, Vous savez que vous pouvez muter l'objet x en place et peut-être économiser un peu de temps/mémoire. que de créer un nouvel objet x + 10 avant de l’assigner sur l’ancienne référence.

Mais considérons ce code:

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

Est-ce que a == b À la fin? Pour la plupart des types, non, a vaut 10 de plus que b. Mais si vous pouviez surcharger l'opérateur += Pour effectuer une mutation sur place, alors oui. Maintenant, considérons que a et b pourraient être transmis à des parties distantes du programme. Votre optimisation éventuelle 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, il n'est pas trop difficile de remplacer x += 10 Par un appel de méthode tel que x.increaseBy(10), ce qui est beaucoup plus clair pour toutes les personnes impliquées.

16
benzado

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

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

MSDN

Juste surcharger + _ opérateur, à cause de

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

9
Andrew Orsich

La surcharge d'opérateur pour + Est utilisée dans l'opérateur +=, A += B Est égal à A = operator+(A, B).

6
Alex Sedow

Si vous surchargez + 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 également.

6
Bala R

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 +. Mais que se passe-t-il si j'ai un cours comme celui-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 que c’est bien que le += est "implémenté automatiquement". Si vous essayez de faire de l'informatique haute performance en C #, vous devez disposer de telles fonctionnalités pour réduire le temps de traitement et la consommation de mémoire. Si quelqu'un a une bonne solution, elle est très appréciée, mais ne me dites pas que je dois le faire.) ceci avec des méthodes statiques, ceci est seulement une solution de contournement et je ne vois aucune raison pour laquelle C # effectue le += implémentation si elle n’est pas définie et si elle est définie, elle sera utilisée. 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?

6
msedi

J'ai eu exactement la même question et je ne peux pas éventuellement y répondre mieux que cette personne a

3
user34537

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

0
N_E