¿Sobrecarga del operador C # para `+ =`?


114

Estoy tratando de realizar sobrecargas de operadores +=, pero no puedo. Solo puedo hacer que un operador se sobrecargue +.

¿Cómo?

Editar

La razón por la que esto no funciona es que tengo una clase Vector (con un campo X e Y). Considere el siguiente ejemplo.

vector1 += vector2;

Si mi sobrecarga de operador está configurada en:

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

Entonces el resultado no se agregará a vector1, sino que, en cambio, vector1 se convertirá en un nuevo Vector por referencia también.


2
Parece que ya se ha tenido una larga discusión sobre esto: maurits.wordpress.com/2006/11/27/…
Chris S

39
¿Puede explicar por qué está intentando hacer esto? Obtienes un operador "+ =" sobrecargado gratis cuando sobrecargas "+". ¿Hay alguna situación en la que no quiere "+ =" estar sobrecargado, pero hace no quiere "+" para ser sobrecargado?
Eric Lippert

3
Viniendo de C ++, esto se siente mal, pero en C # tiene mucho sentido.
Jon Purdy


12
@Mathias: re su actualización: los vectores deberían comportarse como objetos matemáticos inmutables. Cuando agregas 2 a 3, no mutas el objeto 3 en el objeto 5. Creas un objeto completamente nuevo, 5. El objetivo de sobrecargar los operadores de suma es crear tus propios objetos matemáticos; hacerlos mutables va en contra de ese objetivo. Haría que su tipo de vector sea un tipo de valor inmutable.
Eric Lippert

Respuestas:


147

Operadores sobrecargables , de MSDN:

Los operadores de asignación no se pueden sobrecargar, pero +=, por ejemplo, se evalúan utilizando +, que puede sobrecargarse.

Aún más, ninguno de los operadores de asignación puede estar sobrecargado. Creo que esto se debe a que habrá un efecto para la recolección de basura y la administración de memoria, que es un potencial agujero de seguridad en el mundo CLR de tipo fuerte.

Sin embargo, veamos qué es exactamente un operador. Según el famoso libro de Jeffrey Richter , cada lenguaje de programación tiene su propia lista de operadores, que se compilan en un método especial de llamadas, y CLR en sí no sabe nada sobre operadores. Así que veamos qué queda exactamente detrás de los operadores +y +=.

Vea este código simple:

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

Veamos el código IL para estas instrucciones:

  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

Ahora veamos este código:

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

Y código IL para esto:

  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

¡Son iguales! Entonces, el +=operador es solo azúcar sintáctico para su programa en C # , y puede simplemente sobrecargar al +operador.

Por ejemplo:

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

Este código se compilará y se ejecutará correctamente como:

  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

Actualizar:

De acuerdo con su Actualización, como dice @EricLippert, realmente debería tener los vectores como un objeto inmutable. El resultado de la suma de los dos vectores es un nuevo vector, no el primero con diferentes tamaños.

Si, por alguna razón, necesita cambiar el primer vector, puede usar esta sobrecarga (pero para mí, este es un comportamiento muy extraño):

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

2
Afirmar que es el caso no es responder por qué.
Jouke van der Maas

1
@Jouke van der Maas ¿Y cómo quieres que te responda por qué no es posible? Es imposible por diseño, ¿qué más puedo decir?
VMAtm

2
Por qué lo diseñaron de esta manera; esa es la pregunta, en realidad. Vea algunas de las otras respuestas.
Jouke van der Maas

2
"Comportamiento extraño" sólo si ha "nacido" programando en C #: p. Pero, dado que la respuesta es correcta y está muy bien explicada, también obtienes mi +1;)
ThunderGr

5
@ThunderGr No, es bastante extraño sin importar el idioma que estés mirando. Hacer que la declaración v3 = v1 + v2;resulte en v1un cambio y v3es inusual
Assimilater

17

Creo que este enlace le resultará informativo: Operadores para descargar

Los operadores de asignación no se pueden sobrecargar, pero + =, por ejemplo, se evalúa mediante +, que puede sobrecargarse.


2
@pickypg: este comentario no está relacionado con este hilo: recupere su respuesta , responde a mi pregunta, creo que no tengo más remedio que usar su método, pensé que hay una mejor manera de resolverlo.
Shimmy Weitzhandler

16

Esto se debe a la misma razón por la que el operador de asignación no se puede sobrecargar. No puede escribir código que realice la tarea correctamente.

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

Los operadores de asignación no se pueden sobrecargar, pero + =, por ejemplo, se evalúa mediante +, que puede sobrecargarse.

De MSDN .


16

No se puede sobrecargar +=porque no es realmente un operador único, es solo azúcar sintáctico . x += yes solo una forma abreviada de escribir x = x + y. Porque +=se define en términos de los operadores +y =, lo que le permite anularlo por separado podría crear problemas, en los casos en que x += yy x = x + yno se comportara exactamente de la misma manera.

En un nivel inferior, es muy probable que el compilador de C # compile ambas expresiones en el mismo código de bytes, lo que significa que es muy probable que el tiempo de ejecución no pueda tratarlas de manera diferente durante la ejecución del programa.

Puedo entender que es posible que desee tratarlo como una operación separada: en una declaración como x += 10si supiera que puede mutar el xobjeto en su lugar y tal vez ahorrar algo de tiempo / memoria, en lugar de crear un nuevo objeto x + 10antes de asignarlo sobre la referencia anterior .

Pero considere este código:

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

¿Debería a == bal final? Para la mayoría de los tipos, no, aes 10 más que b. Pero si puede sobrecargar al +=operador para mutar en su lugar, entonces sí. Ahora considera eso ayb podría pasar a partes distantes del programa. Su posible optimización podría crear errores confusos si su objeto comienza a cambiar donde el código no lo espera.

En otras palabras, si el rendimiento es tan importante, no es demasiado difícil de reemplazar x += 10 con una llamada a método como x.increaseBy(10), y es mucho más claro para todos los involucrados.


2
Personalmente, me cambiaría it's just syntactic sugara it's just syntactic sugar in C#; de lo contrario, suena demasiado general, pero en algunos lenguajes de programación, no es solo azúcar sintáctico, sino que en realidad podría brindar beneficios de rendimiento.
Sebastian Mach

Para tipos simples (int, float, etc.), +=probablemente podría optimizarse con un compilador inteligente, porque la aritmética es simple. Pero una vez que se trata de objetos, todas las apuestas se cancelan. Cualquier idioma enfrenta prácticamente los mismos problemas. Es por eso que la sobrecarga del operador es mala.
benzado

@SebastianMach La pregunta está etiquetada específicamente con las etiquetas c # y dotnet. Obviamente, en c ++, por ejemplo, '+' y '+ =' (e incluso '=') se pueden sobrecargar por separado.
Bojidar Stanchev

1
@BojidarStanchev: Es cierto. Me disculpo por mi yo 9 años más joven :-D
Sebastian Mach

9

Esto se debe a que este operador no se puede sobrecargar:

Los operadores de asignación no se pueden sobrecargar, pero + =, por ejemplo, se evalúa mediante +, que puede sobrecargarse.

MSDN

Solo sobrecargue al +operador, debido a

x += y igual a x = x + y


6

La sobrecarga del operador para +se usa en +=operador, A += Bes igual a A = operator+(A, B).


6

Si sobrecarga al +operador así:

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

tu puedes hacer

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

o

 foo = foo + 10;

Esto se compilará y se ejecutará igualmente.


6

Siempre hay la misma respuesta a este problema: ¿Por qué necesita el +=, si lo obtiene gratis si sobrecarga el +. Pero que pasa si tengo una clase como esta.

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

¿Sigues diciendo que es bueno que +=esté "implementado automáticamente"? Si intenta hacer computación de alto rendimiento en C #, necesita tener tales características para reducir el tiempo de procesamiento y el consumo de memoria, si alguien tiene una buena solución, es muy apreciado, pero no me diga que tengo que hacer esto con métodos estáticos. , esto es solo una solución y no veo ninguna razón por la que C # realiza la +=implementación si no está definido, y si está definido, se usará. Algunas personas dicen que no tener una diferencia entre +y +=previene errores, pero ¿no es este mi propio problema?


2
Si realmente le importa el rendimiento, no va a perder el tiempo con la sobrecarga del operador, lo que solo hace que sea más difícil saber qué código se está invocando. En cuanto a si estropear la semántica de +=es su propio problema ... eso solo es cierto si nadie más tiene que leer, mantener o ejecutar su código.
benzado

2
Hola benzado. De alguna manera tiene razón, pero lo que tenemos es una plataforma de computación de alto rendimiento para crear aplicaciones prototipo. De una manera necesitamos tener el desempeño, por otra parte necesitamos una semántica simple. De hecho, también nos gusta tener más operadores de los que ofrece C # actualmente. Aquí espero que C # 5 y el compilador como técnica de servicio saquen más provecho del lenguaje C #. Sin embargo, como crecí con C ++ y agradecería que hubiera un poco más de características de C ++ en C #, aunque no me gustaría volver a tocar C ++ ya que estoy haciendo C #.
msedi

2
La ingeniería se trata de compensaciones; todo lo que quieres tiene un precio.
benzado

3
Los operadores aritméticos devuelven nuevas instancias por convención; por lo tanto, generalmente se anulan en tipos inmutables. No puede agregar un nuevo elemento a List<T>un operador como list += "new item", por ejemplo. En su Addlugar, llama a su método.
Şafak Gür


0

Un mejor método de diseño es el casting explícito. Definitivamente puedes sobrecargar el Casting.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.