Ejemplos de sobrecarga del operador, que tienen sentido [cerrado]

12

Mientras aprendía C #, descubrí que C # admite la sobrecarga del operador. Tengo un problema con un buen ejemplo que:

  1. Tiene sentido (por ejemplo, agregar una clase llamada oveja y vaca)
  2. No es un ejemplo de concatenación de dos cadenas.

Los ejemplos de Base Class Library son bienvenidos.

Paweł Sołtysiak
fuente
10
¡Defina 'sentido', por favor! En serio, los debates amargos y apasionados sobre precisamente este punto muestran que hay un gran desacuerdo sobre exactamente esto. Muchas autoridades rechazan a los operadores sobrecargados porque se les puede obligar a hacer cosas completamente inesperadas. Otros responden que los nombres de métodos también se pueden elegir para que sean completamente intuitivos, ¡pero esa no es razón para rechazar bloques de código con nombre! Es casi seguro que no obtendrá ejemplos que generalmente se consideren razonables. Los ejemplos que parece sensato que - tal vez.
Kilian Foth
Completamente de acuerdo con @KilianFoth. Finalmente, el programa que compila tiene sentido para el compilador. Pero si sobrecarga ==para multiplicar, ¡tiene sentido para mí, pero puede que no tenga sentido para otros! ¿Es esta pregunta sobre la legitimidad de qué lenguajes de programación de instalaciones o estamos hablando de 'mejores prácticas de codificación'?
Dipan Mehta

Respuestas:

27

Los ejemplos obvios de sobrecarga apropiada del operador son las clases que se comportan de la misma manera que operan los números. Por lo tanto, las clases BigInt (como sugiere Jalayn ), los números complejos o las clases matriciales (como sugiere Superbest ) tienen las mismas operaciones que los números ordinarios, por lo que se asignan muy bien a los operadores matemáticos, mientras que las operaciones de tiempo (como lo sugiere svick ) se asignan muy bien a un subconjunto de esas operaciones.

Un poco más abstracto, los operadores podrían usarse al realizar operaciones de conjunto , por lo que operator+podría ser una unión , operator-podría ser un complemento, etc. Sin embargo, esto comienza a estirar el paradigma, especialmente si usa el operador de suma o multiplicación para una operación que no es t conmutativo , como es de esperar que sean.

C # en sí tiene un excelente ejemplo de sobrecarga de operadores no numéricos . Utiliza +=y -=para sumar y restar delegados , es decir, registrarlos y anular su registro. Esto funciona bien porque los operadores +=y -=funcionan como cabría esperar, y esto da como resultado un código mucho más conciso.

Para el purista, uno de los problemas con el +operador de cadena es que no es conmutativo. "a"+"b"No es lo mismo que "b"+"a". Entendemos esta excepción para las cadenas porque es muy común, pero ¿cómo podemos saber si el uso operator+en otros tipos será conmutativo o no? La mayoría de las personas supondrá que lo es, a menos que el objeto tenga forma de cadena , pero nunca se sabe realmente qué asumirá la gente.

Al igual que con las cadenas, las debilidades de las matrices también son bastante conocidas. Es obvio que Matrix operator* (double, Matrix)es una multiplicación escalar, mientras Matrix operator* (Matrix, Matrix)que sería una multiplicación matricial (es decir, una matriz de multiplicaciones de productos de puntos), por ejemplo.

Del mismo modo, el uso de operadores con delegados está tan alejado de las matemáticas que es poco probable que cometa esos errores.

Por cierto, en la conferencia ACCU 2011 , Roger Orr y Steve Love presentaron una sesión sobre Algunos objetos son más iguales que otros: una mirada a los muchos significados de igualdad, valor e identidad . Sus diapositivas se pueden descargar , al igual que el Apéndice de Richard Harris sobre la igualdad de coma flotante . Resumen: ¡ Ten mucho cuidado con operator==, aquí hay dragones!

La sobrecarga del operador es una técnica semántica muy poderosa, pero es fácil de usar en exceso. Idealmente, solo debe usarlo en situaciones en las que está muy claro por contexto cuál es el efecto de un operador sobrecargado. En muchos sentidos a.union(b)es más claro que a+b, y a*bes mucho más oscuro que a.cartesianProduct(b), especialmente porque el resultado de un producto cartesiano sería SetLike<Tuple<T,T>>más bien un a SetLike<T>.

Los problemas reales con la sobrecarga del operador se producen cuando un programador supone que una clase se comportará de una manera, pero en realidad se comportará de otra. Este tipo de choque semántico es lo que sugiero que es importante tratar de evitar.

Mark Booth
fuente
1
Usted dice que los operadores en las matrices se mapean muy bien, pero la multiplicación de matrices tampoco es conmutativa. También los operadores de los delegados son aún más fuertes. Puede hacerlo d1 + d2para dos delegados del mismo tipo.
svick
1
@Mark: El "producto punto" solo se define en vectores; multiplicar dos matrices se llama simplemente "multiplicación de matrices". La distinción es más que simplemente semántica: el producto de puntos devuelve un escalar, mientras que la multiplicación matricial devuelve una matriz (y, por cierto, no es conmutativa) .
BlueRaja - Danny Pflughoeft
26

Me sorprende que nadie haya mencionado uno de los casos más interesantes en BCL: DateTimey TimeSpan. Usted puede:

  • suma o resta dos TimeSpans para obtener otroTimeSpan
  • use menos unario en un TimeSpanpara obtener un negadoTimeSpan
  • reste dos DateTimes para obtener unTimeSpan
  • sumar o restar TimeSpande un DateTimepara obtener otroDateTime

Otro conjunto de operadores que podría tener sentido en una gran cantidad de tipos son <, >, <=, >=. En el BCL, por ejemplo, los Versionimplementa.

svick
fuente
¡Un ejemplo muy real en lugar de teorías pedantes!
SIslam
7

El primer ejemplo que me viene a la mente es la implementación de BigInteger , que le permite trabajar con grandes enteros con signo. Consulte el enlace de MSDN para ver cuántos operadores se han sobrecargado (es decir, hay una lista grande y no verifiqué si todos los operadores se han sobrecargado, pero ciertamente parece que sí)

Además, como también hago Java y Java no permite la sobrecarga de operadores, es increíblemente más dulce escribir

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

Que, en Java:

BigDecimal bd = new BigDecimal(0);
bd = bd.add(new BigDecimal(10));
Jalayn
fuente
5

Me alegro de haberlo visto porque he estado jugando con Irony y tiene un GRAN uso de la sobrecarga del operador. Aquí hay una muestra de lo que puede hacer.

Así que Irony es un ".NET Language Implementation Kit" y es un generador de analizador (que genera un analizador LALR). En lugar de tener que aprender una nueva sintaxis / lenguaje como generadores de analizadores sintácticos como yacc / lex, usted escribe la gramática en C # con la sobrecarga del operador. Aquí hay una gramática simple de BNF

// 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

Por lo tanto, es una pequeña gramática simple (disculpe si hay inconsistencias ya que solo estoy aprendiendo BNF y construyendo gramáticas). Ahora veamos el 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 puede ver, con la sobrecarga del operador, escribir la gramática en C # es casi exactamente escribir la gramática en BNF. Para mí, eso no solo tiene sentido, sino que es un gran uso de la sobrecarga del operador.

Jetti
fuente
3

El ejemplo clave es operator == / operator! =.

Si desea comparar fácilmente dos objetos con valores de datos en lugar de por referencia, querrá sobrecargar .Equals (y.GetHashCode!), Y puede querer hacer los operadores! = Y == también para mantener la coherencia.

Sin embargo, nunca he visto ninguna sobrecarga salvaje de otros operadores en C # (imagino que hay casos extremos en los que podría ser útil).

Ed James
fuente
1

Este ejemplo de MSDN muestra cómo implementar números complejos y hacer que usen el operador normal +.

Otro ejemplo muestra cómo hacerlo para la suma de matrices, y también explica cómo no usarlo para agregar un automóvil a un garaje (lea el enlace).

Superbest
fuente
0

El buen uso de la sobrecarga puede ser raro, pero sucede.

sobrecargar operator == y operator! = muestra dos escuelas de pensamiento: las que dicen que hacen las cosas más fáciles y las que no dicen que evitan comparar direcciones (es decir, estoy señalando exactamente el mismo lugar en la memoria, no solo una copia de la misma objeto).

Encuentro que las sobrecargas del operador de reparto son útiles en situaciones específicas. Por ejemplo, tuve que serializar / deserializar en XML un booleano representado como 0 o 1. El operador de conversión correcto (implícito o explícito, lo olvido) de booleano a int y viceversa hizo el truco.

MPelletier
fuente
44
No evita la comparación de direcciones: aún puede usar object.ReferenceEquals().
dan04
@ dan04 Muy, muy bueno saberlo!
MPelletier
Otra forma de comparar direcciones es forzar el uso de objetos ==mediante la conversión: (object)foo == (object)barsiempre compara referencias. Pero preferiría ReferenceEquals(), como @ dan04 menciona porque es más claro lo que hace.
svick
0

No están en la categoría de cosas en las que la gente suele pensar cuando se trata de la sobrecarga del operador, pero creo que uno de los operadores más importantes para poder sobrecargar es el operador de conversión .

Los operadores de conversión son especialmente útiles para los tipos de valor que pueden "quitar el azúcar" a un tipo numérico, o pueden actuar como un tipo numérico en algunos contextos. Por ejemplo, puede definir un Idtipo especial que represente un determinado identificador, y puede proporcionar una conversión implícita a intpara que pueda pasar un Idmétodo que tome un int, pero una conversión explícita de inta Idpara que nadie pueda pasar un inta método que toma un Idsin lanzarlo primero.

Como ejemplo fuera de C #, el lenguaje Python incluye muchos comportamientos especiales que se implementan como operadores sobrecargables. Estos incluyen el inoperador para la prueba de membresía, el ()operador para llamar a un objeto como si fuera una función y el lenoperador para determinar la longitud o el tamaño de un objeto.

Y luego tiene idiomas como Haskell, Scala y muchos otros lenguajes funcionales, donde los nombres como +solo son funciones ordinarias, y no operadores en absoluto (y hay soporte de idiomas para usar funciones en posición infija).

Daniel Pryden
fuente
0

El Struct punto en el System.Drawing espacio de nombres utiliza la sobrecarga para comparar dos ubicaciones diferentes utilizando la sobrecarga de operadores.

 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 puede ver, es mucho más fácil comparar las coordenadas X e Y de dos ubicaciones utilizando la sobrecarga.

Karthik Sreenivasan
fuente
0

Si está familiarizado con el vector matemático, puede ver un uso al sobrecargar el +operador. Puede agregar un vector a=[1,3]con b=[2,-1]y obtener c=[3,2].

Sobrecargar los iguales (==) también puede ser útil (aunque probablemente sea mejor implementar un equals()método). Para continuar con los ejemplos de vectores:

v1=[1,3]
v2=[1,3]
v1==v2 // True
MartinHaTh
fuente
-2

Imagine una pieza de código para dibujar en un formulario

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

Otro ejemplo común es cuando se usa una estructura para contener información de posición en forma de un vector.

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

solo para ser usado más 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;
}
ja72
fuente
44
Agrega vectores, no posiciones: \ Este es un buen ejemplo de cuándo operator+debería no ser sobrecargado (se puede implementar un punto en términos de un vector, pero no debería ser capaz de añadir dos puntos)
BlueRaja - Danny Pflughoeft
@ BlueRaja-DannyPflughoeft: Agregar posiciones para obtener otra posición no tiene sentido, pero restarlas (para obtener un vector) sí lo tiene, al igual que promediarlas . Uno podría calcular el promedio de p1, p2, p3 y p4 a través de p1+((p2-p1)+(p3-p1)+(p4-p1))/4, pero eso parece algo incómodo.
supercat
1
En geometría afinada, puedes hacer álgebra con puntos y líneas, como sumar, escalar, etc. Sin embargo, la implementación requiere coordenadas homogéneas, que normalmente se usan en gráficos 3D de todos modos. La suma de dos puntos en realidad resulta en su promedio.
ja72