Sobrecarga do operador C # para `+ =`?

114

Estou tentando sobrecarregar o operador +=, mas não consigo. Eu só posso fazer uma sobrecarga do operador para+ .

Por quê?

Editar

O motivo pelo qual isso não está funcionando é que tenho uma classe Vector (com um campo X e Y). Considere o seguinte exemplo.

vector1 += vector2;

Se minha sobrecarga de operador estiver definida para:

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

Então, o resultado não será adicionado ao vetor1, mas, em vez disso, o vetor1 se tornará um novo vetor por referência também.

Mathias Lykkegaard Lorenzen
fonte
2
Parece que já houve uma longa discussão sobre isso: maurits.wordpress.com/2006/11/27/…
Chris S
39
Você pode explicar por que está tentando fazer isso? Você obtém um operador "+ =" sobrecarregado gratuitamente quando sobrecarrega "+". Existe alguma situação em que você não quer "+ =" a ser sobrecarregado, mas fazer não quer "+" para ser sobrecarregado?
Eric Lippert
3
Vindo do C ++, isso parece errado, mas no C # realmente faz todo o sentido.
Jon Purdy
12
@Mathias: re sua atualização: os vetores devem se comportar como objetos matemáticos imutáveis. Ao adicionar 2 a 3, você não transforma o objeto 3 no objeto 5. Você cria um objeto inteiramente novo, 5. O objetivo de sobrecarregar os operadores de adição é fazer seus próprios objetos matemáticos; torná-los mutáveis ​​funciona contra esse objetivo. Eu faria seu tipo de vetor um tipo de valor imutável.
Eric Lippert

Respostas:

147

Operadores sobrecarregáveis , do MSDN:

Os operadores de atribuição não podem ser sobrecarregados, mas +=, por exemplo, são avaliados usando +, que pode ser sobrecarregado.

Ainda mais, nenhum dos operadores de atribuição pode ser sobrecarregado. Eu acho que isso ocorre porque haverá um efeito para a coleta de lixo e gerenciamento de memória, que é uma brecha de segurança potencial no mundo de tipo forte CLR.

No entanto, vamos ver o que exatamente é um operador. De acordo com o famoso livro de Jeffrey Richter , cada linguagem de programação tem sua própria lista de operadores, que são compilados em uma chamada de método especial, e o próprio CLR não sabe nada sobre operadores. Então, vamos ver o que exatamente fica para trás dos operadores +e +=.

Veja este código simples:

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

Vamos ver o código IL para estas instruções:

  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

Agora vamos ver este código:

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

E código IL para isso:

  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

Eles são iguais! Portanto, o +=operador é apenas um açúcar sintático para seu programa em C # e você pode simplesmente sobrecarregar o +operador.

Por exemplo:

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 será compilado e executado com sucesso 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

Atualizar:

De acordo com sua atualização - como diz o @EricLippert, você realmente deveria ter os vetores como um objeto imutável. O resultado da adição dos dois vetores é um novo vetor, não o primeiro com tamanhos diferentes.

Se, por algum motivo, você precisar alterar o primeiro vetor, pode usar essa sobrecarga (mas, quanto a mim, esse é um comportamento muito estranho):

public static Vector operator +(Vector left, Vector right)
{
    left.x += right.x;
    left.y += right.y;
    return left;
}
VMAtm
fonte
2
Afirmar que é o caso não é responder por quê.
Jouke van der Maas
1
@Jouke van der Maas E como você quer que eu responda por que não é possível? É impossível por design, o que mais posso dizer?
VMAtm
2
Por que eles projetaram desta forma; essa é a questão, realmente. Veja algumas das outras respostas.
Jouke van der Maas
2
"Comportamento estranho" somente se você "nasceu" programando em C #: p. Mas, como a resposta está correta e muito bem explicada, você também recebe meu +1;)
ThunderGr
5
@ThunderGr Não, é muito estranho, não importa para qual idioma você está olhando. Fazer a declaração v3 = v1 + v2;resulta em v1ser alterada, além de v3incomum
Assimilater
17

Acho que você encontrará este link informativo: Operadores sobrecarregáveis

Os operadores de atribuição não podem ser sobrecarregados, mas + =, por exemplo, é avaliado usando +, que pode ser sobrecarregado.

pickypg
fonte
2
@pickypg - este comentário não está relacionado a este tópico: por favor, cancele a exclusão de sua resposta , ele responde minha pergunta, eu acho que não tenho escolha a não ser usar seu método, pensei que há uma maneira melhor de resolvê-lo.
Shimmy Weitzhandler,
16

Isso ocorre pelo mesmo motivo que o operador de atribuição não pode ser sobrecarregado. Você não pode escrever código que executaria a atribuição corretamente.

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

Os operadores de atribuição não podem ser sobrecarregados, mas + =, por exemplo, é avaliado usando +, que pode ser sobrecarregado.

Do MSDN .

agente-j
fonte
16

Você não pode sobrecarregar +=porque não é realmente um operador único, é apenas um açúcar sintático . x += yé apenas uma forma abreviada de escrever x = x + y. Porque +=é definido em termos dos operadores +e =, permitir que você substitua-o separadamente poderia criar problemas, nos casos em que x += ye x = x + ynão se comportasse exatamente da mesma maneira.

Em um nível inferior, é muito provável que o compilador C # compile ambas as expressões no mesmo bytecode, o que significa que é muito provável que o tempo de execução não possa trate de maneira diferente durante a execução do programa.

Eu posso entender que você pode querer tratá-lo como uma operação separada: em uma declaração como x += 10você sabe que você pode transformar o xobjeto no local e talvez economizar algum tempo / memória, em vez de criar um novo objeto x + 10antes de atribuí-lo sobre a referência antiga .

Mas considere este código:

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

Deve a == bno final? Para a maioria dos tipos, não, aé 10 a mais que b. Mas se você pudesse sobrecarregar o +=operador para sofrer mutação no local, então sim. Agora considere isso aeb poderia ser transmitido para partes distantes do programa. Sua possível otimização pode criar bugs confusos se o seu objeto começar a mudar onde o código não espera.

Em outras palavras, se o desempenho é tão importante, não é muito difícil substituí-lo x += 10por uma chamada de método como x.increaseBy(10), e é muito mais claro para todos os envolvidos.

benzado
fonte
2
Pessoalmente, eu mudaria it's just syntactic sugarpara it's just syntactic sugar in C#; caso contrário, soa muito geral, mas em algumas linguagens de programação, não é apenas um açúcar sintático, mas pode realmente oferecer benefícios de desempenho.
Sebastian Mach
Para tipos simples (int, float, etc.), +=provavelmente poderia ser otimizado por um compilador inteligente, porque a aritmética é simples. Mas, uma vez que você está lidando com objetos, todas as apostas estão canceladas. Qualquer idioma enfrenta praticamente os mesmos problemas. É por isso que a sobrecarga do operador é prejudicial.
benzado
@SebastianMach A questão é especificamente marcada com as tags c # e dotnet. Obviamente, em c ++ por exemplo, '+' e '+ =' (e mesmo '=') podem ser sobrecarregados separadamente.
Bojidar Stanchev
1
@BojidarStanchev: É verdade. Peço desculpas por ser 9 anos mais jovem :-D
Sebastian Mach
9

Isso ocorre porque este operador não pode ser sobrecarregado:

Os operadores de atribuição não podem ser sobrecarregados, mas + =, por exemplo, é avaliado usando +, que pode ser sobrecarregado.

MSDN

Apenas sobrecarregue o +operador, por causa de

x += y igual a x = x + y

Andrew Orsich
fonte
6

Sobrecarga do operador para +é usado em +=operador, A += Bé igual a A = operator+(A, B).

Alex Sedow
fonte
6

Se você sobrecarregar o +operador assim:

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

você pode fazer

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

ou

 foo = foo + 10;

Isso irá compilar e rodar igualmente.

Bala R
fonte
6

Sempre há a mesma resposta para esse problema: Por que você precisa do +=, se você o obtém gratuitamente se sobrecarregar o +. Mas o que acontece se eu tiver uma aula como essa.

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

Você ainda diz que é bom que o +=seja "autoimplementado". Se você tentar fazer computação de alto desempenho em C #, você precisa ter esses recursos para reduzir o tempo de processamento e o consumo de memória, se alguém tiver uma boa solução, isso é muito apreciado, mas não me diga que tenho que fazer isso com métodos estáticos , esta é apenas uma solução alternativa e não vejo razão para que o C # faça a +=implementação se não estiver definido e, se estiver definido, será usado. Algumas pessoas dizem que não ter diferença +e +=evita erros, mas não é problema meu?

msedi
fonte
2
Se você realmente se preocupa com o desempenho, não vai mexer com a sobrecarga de operadores, o que apenas torna mais difícil saber qual código está sendo invocado. Se bagunçar a semântica do +=é problema seu ... isso só é verdade se ninguém mais tiver que ler, manter ou executar seu código.
benzado
2
Olá, benzado. De alguma forma você está certo, mas o que temos é uma plataforma de computação de alto desempenho para criar aplicativos protótipos. Por um lado, precisamos ter o desempenho, por outro lado, precisamos de uma semântica simples. Na verdade, também gostamos de ter mais operadores do que o C # atualmente oferece. Espero que o C # 5 e o compilador como uma técnica de serviço aproveitem melhor a linguagem C #. No entanto, como eu cresci com C ++ e gostaria de saber se há um pouco mais de recursos do C ++ em C #, embora eu não gostaria de tocar em C ++ novamente, já que estou fazendo C #.
msedi de
2
Engenharia tem tudo a ver com compensações; tudo o que você quer tem um preço.
benzado
3
Operadores aritméticos retornam novas instâncias por convenção - portanto, eles geralmente são substituídos em tipos imutáveis. Você não pode adicionar um novo elemento a um List<T>usando um operador como list += "new item", por exemplo. Em Addvez disso, você chama seu método.
Şafak Gür
3

Eu tinha exatamente a mesma pergunta e não posso respondê-la melhor do que esta pessoa

Comunidade
fonte
0

Um método de design melhor é a fundição explícita. Você pode definitivamente sobrecarregar o Casting.

N_E
fonte