Exemplos de sobrecarga do operador, que fazem sentido [fechado]


12

Enquanto eu aprendia C #, descobri que o C # suporta sobrecarga do operador. Eu tenho problema com um bom exemplo que:

  1. Faz sentido (por exemplo, adicionar classe chamada ovelha e vaca)
  2. Não é um exemplo de concatenação de duas cadeias

Exemplos da Base Class Library são bem-vindos.


10
Defina "sentido", por favor! Sério, os debates amargos e apaixonados sobre exatamente esse ponto mostram que há um grande desacordo sobre exatamente isso. Muitas autoridades rejeitam operadores sobrecarregados porque podem ser feitos para fazer coisas totalmente inesperadas. Outros respondem que os nomes de métodos também podem ser escolhidos para serem completamente pouco intuitivos, mas isso não é motivo para rejeitar blocos de código nomeados! Você quase certamente não receberá nenhum exemplo que seja geralmente considerado sensato. Exemplos que parecem sensata para você - talvez.
Kilian Foth

Concordo totalmente com @KilianFoth. Por fim, o programa que compila faz sentido para o compilador. Mas se sobrecarregar ==para fazer a multiplicação, faz sentido para mim, mas pode não fazer sentido para os outros! Trata-se de legitimidade de quais linguagens de programação de instalações ou estamos falando sobre 'práticas recomendadas de codificação'?
Dipan Mehta

Respostas:


27

Os exemplos óbvios de sobrecarga adequada do operador são quaisquer classes que se comportam da mesma maneira que os números operam. Portanto, as classes BigInt (como sugere Jalayn ), os números complexos ou as classes matriciais (como sugere Superbest ), todas têm as mesmas operações que os números comuns, portanto, mapeiam muito bem os operadores matemáticos, enquanto as operações de tempo (como sugerido por svick ) mapeiam muito bem um subconjunto dessas operações.

Um pouco mais abstratamente, os operadores podem ser usados ​​ao executar operações definidas , assim, operator+podem ser uma união , operator-podem ser um complemento etc. Isso começa a esticar o paradigma, especialmente se você usar o operador de adição ou multiplicação para uma operação que não é ' t comutativa , como você pode esperar que eles sejam.

O próprio C # possui um excelente exemplo de sobrecarga não numérica do operador. Ele usa +=e -=adiciona e subtrai delegados , ou seja, registra e cancela o registro. Isso funciona bem porque os operadores +=e -=funcionam como você esperaria, e isso resulta em um código muito mais conciso.

Para o purista, um dos problemas com o +operador de string é que ele não é comutativo. "a"+"b"não é o mesmo que "b"+"a". Entendemos essa exceção para strings porque é muito comum, mas como podemos saber se o uso operator+em outros tipos será comutativo ou não? A maioria das pessoas assume que sim, a menos que o objeto seja do tipo string , mas você nunca sabe realmente o que as pessoas assumirão.

Como nas cordas, os pontos fracos das matrizes também são bastante conhecidos. É óbvio que Matrix operator* (double, Matrix)é uma multiplicação escalar, ao passo que Matrix operator* (Matrix, Matrix)seria uma multiplicação de matrizes (por exemplo, uma matriz de multiplicações de produtos pontuais).

Da mesma forma, o uso de operadores com delegados está tão obviamente distante da matemática que é improvável que você cometa esses erros.

Aliás, na conferência da ACCU de 2011 , Roger Orr e Steve Love apresentaram uma sessão sobre Alguns objetos são mais iguais que outros - uma olhada nos muitos significados de igualdade, valor e identidade . Seus slides podem ser baixados , assim como o Apêndice de Richard Harris sobre igualdade de ponto flutuante . Resumo: Tenha muito cuidado com operator==, aqui estão os dragões!

A sobrecarga do operador é uma técnica semântica muito poderosa, mas é fácil de usar em excesso. Idealmente, você deve usá-lo apenas em situações em que o contexto de um operador sobrecarregado seja muito claro. De muitas maneiras a.union(b)é mais clara do que a+b, e a*bé muito mais obscura do que a.cartesianProduct(b), especialmente desde que o resultado de um produto cartesiano seria um SetLike<Tuple<T,T>>em vez de um SetLike<T>.

Os problemas reais com sobrecarga do operador ocorrem quando um programador assume que uma classe se comportará de uma maneira, mas na verdade se comporta de outra. Esse tipo de conflito semântico é o que estou sugerindo que é importante tentar evitar.


1
Você diz que os operadores nas matrizes mapeiam muito bem, mas a multiplicação da matriz também não é comutativa. Além disso, os operadores dos delegados são ainda mais fortes. Você pode fazer d1 + d2por dois delegados do mesmo tipo.
svick 23/02/12

1
@ Mark: O "produto escalar" é definido apenas em vetores; multiplicar duas matrizes é chamado simplesmente "multiplicação de matrizes". A distinção é mais do que apenas semântica: o produto escalar retorna um escalar, enquanto a multiplicação de matrizes retorna uma matriz (e é, a propósito, não comutativa) .
BlueRaja - Danny Pflughoeft 23/02

26

Estou surpreso que ninguém tenha mencionado um dos casos mais interessantes no BCL: DateTimee TimeSpan. Você pode:

  • adicione ou subtraia dois TimeSpans para obter outroTimeSpan
  • use menos unário em um TimeSpanpara obter um negadoTimeSpan
  • subtrair dois DateTimes para obter umaTimeSpan
  • adicionar ou subtrair TimeSpande um DateTimepara obter outroDateTime

Outro conjunto de operadores que podem fazer sentido em um monte de tipos são <, >, <=, >=. No BCL, por exemplo, os Versionimplementa.


Exemplo muito real, em vez de teorias pedantes!
SIslam

7

O primeiro exemplo que me vem à mente é a implementação do BigInteger , que permite trabalhar com números inteiros grandes assinados. Confira o link do MSDN para ver quantos operadores foram sobrecarregados (ou seja, há uma grande lista e eu não verifiquei se todos os operadores foram sobrecarregados, mas certamente parece que sim)

Além disso, como eu também faço Java e Java não permite sobrecarregar operadores, é incrivelmente mais agradável escrever

BigInteger bi = new BigInteger(0);
bi += 10;

Than, em Java:

BigDecimal bd = new BigDecimal(0);
bd = bd.add(new BigDecimal(10));

5

Fico feliz por ter visto isso porque ando brincando com o Irony e ele tem um ótimo uso de sobrecarga de operador. Aqui está uma amostra do que ele pode fazer.

Portanto, o Irony é um "Kit de Implementação de Linguagem .NET" e é um gerador de analisador (gerando um analisador LALR). Em vez de ter que aprender uma nova sintaxe / linguagem como geradores de analisador como o yacc / lex, você escreve a gramática em C # com a sobrecarga do operador. Aqui está uma gramática BNF simples

// BNF 
Expr := Term | BinExpr
Term := number | ParExpr
ParExpr := "(" + Expr + ")"
BinExpr := number + BinOp + number
BinOp := "+" | "-" | "*" | "/"
number := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Portanto, é uma gramática simples (desculpe-a se houver inconsistências, pois estou apenas aprendendo BNF e construindo gramáticas). Agora vamos ver o c #:

  var Expr = new NonTerminal("Expr");
  var Term = new NonTerminal("Term");
  var BinExpr = new NonTerminal("BinExpr");
  var ParExpr = new NonTerminal("ParExpr");
  var BinOp = new NonTerminal("BinOp");
  var Statement = new NonTerminal("Statement");
  var ProgramLine = new NonTerminal("ProgramLine");
  var Program = new NonTerminal("Program", typeof(StatementListNode));
  // BNF Rules - Overloading
  Expr.Rule = Term | BinExpr;
  Term.Rule = number | ParExpr;
  ParExpr.Rule = "(" + Expr + ")";
  BinExpr.Rule = Expr + BinOp + Expr;
  BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "**";

Como você pode ver, com a sobrecarga do operador, escrever a gramática em C # é quase exatamente escrever a gramática em BNF. Para mim, isso não apenas faz sentido, mas também é um ótimo uso da sobrecarga do operador.


3

O exemplo principal é operator == / operator! =.

Se você deseja comparar facilmente dois objetos com valores de dados em vez de por referência, sobrecarregará .Equals (e.GetHashCode!) E poderá fazer os operadores! = E == também para obter consistência.

Eu nunca vi nenhuma sobrecarga selvagem de outros operadores em C # (eu imagino que há casos extremos onde isso pode ser útil).


1

Este exemplo do MSDN mostra como implementar números complexos e fazer com que eles usem o operador + normal.

Outro exemplo mostra como fazer isso para adição de matriz e também explica como não usá-lo para adicionar um carro a uma garagem (leia o link).


0

O bom uso da sobrecarga pode ser raro, mas acontece.

operador de sobrecarga == e operador! = mostram duas escolas de pensamento: as que dizem que isso facilita as coisas e as que dizem que impede a comparação de endereços (ou seja, estou apontando exatamente para o mesmo lugar na memória, não apenas uma cópia do mesmo objeto).

Acho que as sobrecargas do operador de elenco são úteis em situações específicas. Por exemplo, eu tive que serializar / desserializar em XML um booleano representado como 0 ou 1. O operador certo (implícito ou explícito, esqueço) de conversão de booleano para int e back fez o truque.


4
Isso não impede a comparação de endereços: você ainda pode usar object.ReferenceEquals().
dan04

@ dan04 Muito, muito bom saber!
MPelletier

Outra maneira de comparar endereços é forçar o uso de objetos ==lançando: (object)foo == (object)barsempre compara referências. Mas eu preferiria ReferenceEquals(), como o @ dan04 menciona, porque é mais claro o que faz.
svick

0

Eles não fazem parte da categoria em que as pessoas normalmente pensam quando sobrecarregam o operador, mas acho que um dos operadores mais importantes que podem sobrecarregar é o operador de conversão .

Os operadores de conversão são especialmente úteis para tipos de valor que podem "descodificar" para um tipo numérico ou que podem agir como um tipo numérico em alguns contextos. Por exemplo, você pode definir um Idtipo especial que representa um determinado identificador e fornecer uma conversão implícita para, intpara que você possa passar um Idpara um método que leva uma int, mas uma conversão explícita de intpara Idpara que ninguém possa passar um intpara um método que leva um Idsem lançá-lo primeiro.

Como um exemplo fora do C #, a linguagem Python inclui muitos comportamentos especiais que são implementados como operadores sobrecarregáveis. Isso inclui o inoperador para teste de associação, o ()operador para chamar um objeto como se fosse uma função e o lenoperador para determinar o comprimento ou tamanho de um objeto.

E então você tem linguagens como Haskell, Scala e muitas outras linguagens funcionais, onde nomes como +são apenas funções comuns e não são operadores (e existe suporte de linguagem para o uso de funções na posição de infix).


0

O Point Struct no espaço para nome System.Drawing usa sobrecarga para comparar dois locais diferentes usando sobrecarga do operador.

 Point locationA = new Point( 50, 50 );
 Point locationB = new Point( 50, 50 );

 if ( locationA == locationB )
    Console.WriteLine( "Their locations are the same" );
 else
    Console.WriteLine( "Their locations  are different" );

Como você pode ver, é muito mais fácil comparar as coordenadas X e Y de dois locais usando a sobrecarga.


0

Se você estiver familiarizado com o vetor matemático, poderá ser útil sobrecarregar o +operador. Você pode adicionar um vetor a=[1,3]com b=[2,-1]e obter c=[3,2].

Sobrecarregar os iguais (==) também pode ser útil (mesmo que seja provavelmente melhor implementar um equals()método). Para continuar os exemplos de vetores:

v1=[1,3]
v2=[1,3]
v1==v2 // True

-2

Imagine um pedaço de código para desenhar em um formulário

{
  Point p = textBox1.Location;
  Size dp = textBox1.Size;

  // Here the + operator has been overloaded by the CLR
  p += dp;  // Now p points to the lower right corner of the textbox.
  ..
}

Outro exemplo comum é quando uma estrutura é usada para armazenar informações de posição na forma de um vetor.

public struct Pos
{
    public double x, y, z;
    public double Distance { get { return Math.Sqrt(x * x + y * y + z * z); } }
    public static Pos operator +(Pos A, Pos B)
    {
        return new Pos() { x = A.x + B.x, y = A.y + B.y, z = A.z + B.z };
    }
    public static Pos operator -(Pos A, Pos B)
    {
        return new Pos() { x = A.x - B.x, y = A.y - B.y, z = A.z - B.z };
    }
}

apenas para ser usado mais tarde como

{
    Pos A = new Pos() { x = 4, y = -1, z = 0.5 };
    Pos B = new Pos() { x = 8, y = 2, z = 1.5 };

    double x = (B - A).Distance;
}

4
Você adiciona vetores, não posições: \ Este é um bom exemplo de quando nãooperator+ deve ser sobrecarregado (você pode implementar um ponto em termos de vetor, mas não deve poder adicionar dois pontos)
BlueRaja - Danny Pflughoeft

@ BlueRaja-DannyPflughoeft: Adicionar posições para gerar outra posição não faz sentido, mas subtraí-las (para gerar um vetor) faz, assim como a média delas. Pode-se calcular a média de p1, p2, p3 e p4 via p1+((p2-p1)+(p3-p1)+(p4-p1))/4, mas isso parece um pouco estranho.
supercat

1
Na geometria afim, você pode fazer álgebra com pontos e linhas, como adição, redimensionamento etc. A implementação requer coordenadas homogêneas, que geralmente são usadas em gráficos 3D de qualquer maneira. A adição de dois pontos resulta na média deles.
ja72
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.