El compilador de C # requiere que siempre que un tipo personalizado defina operador ==
, también debe definir !=
(ver aquí ).
¿Por qué?
Tengo curiosidad por saber por qué los diseñadores lo consideraron necesario y por qué el compilador no puede usar una implementación razonable para cualquiera de los operadores cuando solo está presente el otro. Por ejemplo, Lua le permite definir solo el operador de igualdad y usted obtiene el otro de forma gratuita. C # podría hacer lo mismo pidiéndole que defina == o ambos == y! = Y luego compile automáticamente el operador que falta! = Como !(left == right)
.
Entiendo que hay casos extraños en los que algunas entidades pueden no ser iguales ni desiguales (como IEEE-754 NaN's), pero esas parecen ser la excepción, no la regla. Entonces, esto no explica por qué los diseñadores del compilador de C # hicieron de la excepción la regla.
He visto casos de mano de obra pobre donde se define el operador de igualdad, luego el operador de desigualdad es una copia y pega con todas y cada una de las comparaciones invertidas y cada && cambiado a || (entiendes el punto ... ¡básicamente! (a == b) expandido a través de las reglas de De Morgan). Esa es una mala práctica que el compilador podría eliminar por diseño, como es el caso de Lua.
Nota: Lo mismo vale para los operadores <> <=> =. No puedo imaginar casos en los que deba definirlos de forma poco natural. Lua le permite definir solo <y <= y define> = y> naturalmente a través de la negación de los formadores. ¿Por qué C # no hace lo mismo (al menos 'por defecto')?
EDITAR
Aparentemente, existen razones válidas para permitir que el programador implemente verificaciones de igualdad y desigualdad como quiera. Algunas de las respuestas apuntan a casos en los que eso puede ser bueno.
El núcleo de mi pregunta, sin embargo, es ¿por qué esto se requiere a la fuerza en C # cuando generalmente no es lógicamente necesario?
También contrasta notablemente con las opciones de diseño para interfaces .NET como Object.Equals
, IEquatable.Equals
IEqualityComparer.Equals
donde la falta de una NotEquals
contraparte muestra que el marco considera los !Equals()
objetos desiguales y eso es todo. Además, las clases como Dictionary
y los métodos como .Contains()
dependen exclusivamente de las interfaces mencionadas anteriormente y no utilizan los operadores directamente, incluso si están definidos. De hecho, cuando ReSharper genera miembros de igualdad, define ambos ==
y !=
en términos de, Equals()
e incluso entonces, solo si el usuario elige generar operadores. El marco no necesita los operadores de igualdad para comprender la igualdad de objetos.
Básicamente, el marco .NET no se preocupa por estos operadores, solo se preocupa por algunos Equals
métodos. La decisión de exigir que los operadores == y! = Sean definidos en conjunto por el usuario está relacionada exclusivamente con el diseño del lenguaje y no con la semántica de objetos en lo que respecta a .NET.
fuente
Respuestas:
No puedo hablar por los diseñadores de idiomas, pero por lo que puedo razonar, parece que fue una decisión de diseño intencional y adecuada.
Mirando este código básico de F #, puede compilarlo en una biblioteca de trabajo. Este es un código legal para F #, y solo sobrecarga el operador de igualdad, no la desigualdad:
Esto hace exactamente lo que parece. Solo crea un comparador de igualdad
==
y comprueba si los valores internos de la clase son iguales.Si bien no puede crear una clase como esta en C #, puede usar una que se compiló para .NET. Es obvio que usará nuestro operador sobrecargado.
==
Entonces, ¿ para qué sirve el tiempo de ejecución!=
?El estándar C # EMCA tiene un montón de reglas (sección 14.9) que explican cómo determinar qué operador usar al evaluar la igualdad. Para ponerlo demasiado simplificado y, por lo tanto, no es perfectamente preciso, si los tipos que se comparan son del mismo tipo y hay un operador de igualdad sobrecargado presente, utilizará esa sobrecarga y no el operador de igualdad de referencia estándar heredado de Object. No es sorprendente, entonces, que si solo uno de los operadores está presente, usará el operador de igualdad de referencia predeterminado, que tienen todos los objetos, no hay una sobrecarga para él. 1
Sabiendo que este es el caso, la verdadera pregunta es: ¿por qué se diseñó de esta manera y por qué el compilador no se da cuenta por sí mismo? Mucha gente dice que esto no fue una decisión de diseño, pero me gusta pensar que se pensó de esta manera, especialmente con respecto al hecho de que todos los objetos tienen un operador de igualdad predeterminado.
Entonces, ¿por qué el compilador no crea automáticamente el
!=
operador? No puedo estar seguro a menos que alguien de Microsoft confirme esto, pero esto es lo que puedo determinar a partir del razonamiento sobre los hechos.Para evitar comportamientos inesperados
Tal vez quiero hacer una comparación de valores
==
para probar la igualdad. Sin embargo, cuando se trataba de!=
eso, no me importaba en absoluto si los valores eran iguales a menos que la referencia fuera igual, porque para que mi programa los considere iguales, solo me importa si las referencias coinciden. Después de todo, esto se describe realmente como el comportamiento predeterminado de C # (si ambos operadores no estuvieran sobrecargados, como sería el caso de algunas bibliotecas .net escritas en otro idioma). Si el compilador agregaba código automáticamente, ya no podría confiar en el compilador para generar el código que debería cumplir. El compilador no debe escribir código oculto que cambie el comportamiento del suyo, especialmente cuando el código que ha escrito está dentro de los estándares de C # y la CLI.En términos de obligarlo a sobrecargarlo, en lugar de pasar al comportamiento predeterminado, solo puedo decir firmemente que está en el estándar (EMCA-334 17.9.2) 2 . El estándar no especifica por qué. Creo que esto se debe al hecho de que C # toma prestado mucho comportamiento de C ++. Vea a continuación más información sobre esto.
Cuando anula
!=
y==
, no tiene que devolver bool.Esta es otra razón probable. En C #, esta función:
es tan válido como este:
Si está devolviendo algo que no sea bool, el compilador no puede inferir automáticamente un tipo opuesto. Además, en el caso de que el operador hace return bool, simplemente no tiene sentido para ellos crean generar código que sólo existiría en ese caso en particular uno o, como he dicho anteriormente, el código que oculta el comportamiento predeterminado del CLR.
C # toma prestado mucho de C ++ 3
Cuando se introdujo C #, había un artículo en la revista MSDN que escribió, hablando de C #:
Sí, el objetivo de diseño para C # era dar casi la misma cantidad de energía que C ++, sacrificando solo un poco por comodidades como la seguridad de tipo rígido y la recolección de basura. C # fue fuertemente modelado después de C ++.
Puede que no le sorprenda saber que en C ++, los operadores de igualdad no tienen que devolver bool , como se muestra en este programa de ejemplo
Ahora, C ++ no requiere directamente que sobrecargue el operador complementario. Si compiló el código en el programa de ejemplo, verá que se ejecuta sin errores. Sin embargo, si intentaste agregar la línea:
conseguirás
Entonces, si bien C ++ en sí no requiere que se sobrecargue en pares, no le permitirá usar un operador de igualdad que no haya sobrecargado en una clase personalizada. Es válido en .NET, porque todos los objetos tienen uno predeterminado; C ++ no lo hace.
1. Como nota al margen, el estándar C # aún requiere que sobrecargue el par de operadores si desea sobrecargar cualquiera de los dos. Esta es una parte del estándar y no simplemente el compilador . Sin embargo, las mismas reglas con respecto a la determinación de a qué operador llamar se aplican cuando accede a una biblioteca .net escrita en otro idioma que no tiene los mismos requisitos.
2. EMCA-334 (pdf) ( http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf )
3. Y Java, pero ese no es realmente el punto aquí
fuente
==
devolver cualquier cosa que no sea abool
? Si devuelve unint
, ya no puede usarlo como una declaración condicional, por ejemplo.if(a == b)
ya no se compilará. ¿Cuál es el punto de?Probablemente si alguien necesita implementar una lógica de tres valores (es decir
null
). En casos como ese, por ejemplo, SQL estándar ANSI, los operadores no pueden simplemente negarse según la entrada.Podría tener un caso donde:
Y
a == true
vuelvefalse
ya == false
también vuelvefalse
.fuente
a
que no es un booleano, deberías compararlo con una enumeración de tres estados, no realtrue
ofalse
.a == true
esfalse
así, siempre esperaríaa != true
serlotrue
, a pesar dea == false
serfalse
==
, no por qué debería verse obligado a anular ambos.!=
sería correcta. En el caso extremadamente poco común en el que quisiéramosx == y
yx != y
ambos fueran falsos (no puedo pensar en un solo ejemplo que tenga sentido) , aún podrían anular la implementación predeterminada y proporcionar la suya propia. ¡Siempre haga que el caso común sea el predeterminado!Aparte de que C # difiere de C ++ en muchas áreas, la mejor explicación que puedo pensar es que en algunos casos es posible que desee adoptar un enfoque ligeramente diferente para demostrar "no igualdad" que para demostrar "igualdad".
Obviamente, con la comparación de cadenas, por ejemplo, puede probar la igualdad y
return
salir del bucle cuando vea caracteres que no coinciden. Sin embargo, puede que no sea tan limpio con problemas más complicados. El filtro de floración viene a la mente; Es muy fácil saber rápidamente si el elemento no está en el conjunto, pero es difícil saber si el elemento está en el conjunto. Si bienreturn
podría aplicarse la misma técnica, el código podría no ser tan bonito.fuente
!
encierra en una única definición de igualdad, donde un codificador informado podría optimizar ambos==
y!=
.Si observa implementaciones de sobrecargas de == y! = En la fuente .net, a menudo no se implementan! = As! (Left == right). Lo implementan completamente (como ==) con lógica negada. Por ejemplo, DateTime implementa == como
y! = como
Si usted (o el compilador si lo hiciera implícitamente) implementara! = As
entonces está asumiendo la implementación interna de == y! = en las cosas a las que hace referencia su clase. Evitar esa suposición puede ser la filosofía detrás de su decisión.
fuente
Para responder a su edición, con respecto a por qué se ve obligado a anular ambos si anula uno, todo está en la herencia.
Si anula ==, lo más probable es que proporcione algún tipo de igualdad semántica o estructural (por ejemplo, DateTimes es igual si sus propiedades de InternalTicks son iguales aunque puedan ser instancias diferentes), entonces está cambiando el comportamiento predeterminado del operador de Object, que es el padre de todos los objetos .NET. El operador == es, en C #, un método, cuya implementación base Object.operator (==) realiza una comparación referencial. Object.operator (! =) Es otro método diferente, que también realiza una comparación referencial.
En casi cualquier otro caso de anulación de métodos, sería ilógico suponer que anular un método también daría lugar a un cambio de comportamiento a un método antónimo. Si crea una clase con los métodos Incremento () y Decremento (), y anula el Incremento () en una clase secundaria, ¿esperaría que el Decremento () también se anule con lo contrario de su comportamiento anulado? El compilador no puede ser lo suficientemente inteligente como para generar una función inversa para cualquier implementación de un operador en todos los casos posibles.
Sin embargo, los operadores, aunque implementados de manera muy similar a los métodos, trabajan conceptualmente en parejas; == y! =, <y>, y <= y> =. Sería ilógico en este caso desde el punto de vista de un consumidor pensar que! = Funcionó de manera diferente a ==. Por lo tanto, no se puede hacer que el compilador suponga que a! = B ==! (A == b) en todos los casos, pero generalmente se espera que == y! = Operen de manera similar, por lo que el compilador obliga implementar en pares, sin embargo, en realidad terminas haciendo eso. Si, para su clase, a! = B ==! (A == b), simplemente implemente el operador! = Usando! (==), pero si esa regla no se cumple en todos los casos para su objeto (por ejemplo , si la comparación con un valor particular, igual o desigual, no es válida), entonces debe ser más inteligente que el IDE.
¡La pregunta REAL que debe hacerse es por qué <y> y <= y> = son pares para operadores comparativos que deben implementarse simultáneamente, cuando están en términos numéricos! (A <b) == a> = by! (A> b) == a <= b. Debería estar obligado a implementar los cuatro si anula uno, y probablemente también deba anular == (y! =) También, porque (a <= b) == (a == b) si a es semánticamente igual a b.
fuente
Si sobrecarga == para su tipo personalizado, y no! = Entonces será manejado por el operador! = Para el objeto! = Objeto ya que todo se deriva del objeto, y esto sería muy diferente de CustomType! = CustomType.
Además, los creadores del lenguaje probablemente querían que de esta manera se permitiera la mayor flexibilidad para los codificadores, y también para que no hagan suposiciones sobre lo que pretenden hacer.
fuente
Esto es lo que primero viene a mi mente:
false
tanto para==
y!=
(es decir, si no se pueden comparar por alguna razón)fuente
Dictionary
yList
si usa su tipo personalizado con ellas.Dictionary
utilizaGetHashCode
yEquals
, no los operadores.List
usosEquals
.Bueno, probablemente sea solo una opción de diseño, pero como dices,
x!= y
no tiene que ser lo mismo!(x == y)
. Al no agregar una implementación predeterminada, está seguro de que no puede olvidar implementar una implementación específica. Y si es tan trivial como dices, puedes implementar uno usando el otro. No veo cómo esto es 'mala práctica'.Puede haber algunas otras diferencias entre C # y Lua también ...
fuente
Las palabras clave en su pregunta son " por qué " y " debe ".
Como resultado:
Responder es así porque lo diseñaron para que sea así, es cierto ... pero no responder la parte del "por qué" de su pregunta.
Contestar que a veces puede ser útil anular ambos de forma independiente, es cierto ... pero no responder la parte "imprescindible" de su pregunta.
Creo que la respuesta simple es que no hay ninguna razón convincente por la que C # requiera que anule ambos.
El idioma debería permitirle anular solo
==
y proporcionarle una implementación predeterminada de!=
eso es!
eso. Si también quieres anular!=
, hazlo .No fue una buena decisión. Los humanos diseñan lenguajes, los humanos no son perfectos, C # no es perfecto. Encogimiento de hombros y QED
fuente
Solo para agregar a las excelentes respuestas aquí:
Considere lo que sucedería en el depurador , cuando intente ingresar a un
!=
operador y terminar en un==
operador! Hable acerca de confundir!Tiene sentido que CLR le permita la libertad de omitir uno u otro operador, ya que debe funcionar con muchos idiomas. Pero hay un montón de ejemplos de C # no exponer las características CLR (
ref
devoluciones y locales, por ejemplo), y un montón de ejemplos de implementación de características no en la propia CLR (por ejemplo:using
,lock
,foreach
, etc).fuente
Los lenguajes de programación son reordenamientos sintácticos de enunciados lógicos excepcionalmente complejos. Con eso en mente, ¿puedes definir un caso de igualdad sin definir un caso de no igualdad? La respuesta es no. Para que un objeto a sea igual al objeto b, entonces el inverso del objeto a no es igual a b también debe ser verdadero. Otra forma de mostrar esto es
if a == b then !(a != b)
Esto proporciona la capacidad definitiva para que el lenguaje determine la igualdad de los objetos. Por ejemplo, la comparación NULL! = NULL puede arrojar una llave en la definición de un sistema de igualdad que no implementa una declaración de no igualdad.
Ahora, en lo que respecta a la idea de! = Simplemente ser una definición reemplazable como en
if !(a==b) then a!=b
No puedo discutir con eso. Sin embargo, lo más probable es que el grupo de especificación del lenguaje C # haya decidido que el programador se vea obligado a definir explícitamente la igualdad y la no igualdad de un objeto juntos
fuente
Null
solo sería un problema porquenull
es una entrada válida para==
, lo que no debería ser, aunque obviamente, es enormemente conveniente. Personalmente, creo que permite grandes cantidades de programación vaga y descuidada.En resumen, consistencia forzada.
'==' y '! =' son siempre verdaderos opuestos, sin importar cómo los defina, definidos como tales por su definición verbal de "igual" y "no igual". Al definir solo uno de ellos, se abre a una inconsistencia del operador de igualdad donde '==' y '! =' Pueden ser verdaderos o ambos falsos para dos valores dados. Debe definir ambos, ya que cuando elige definir uno, también debe definir el otro apropiadamente para que quede claramente claro cuál es su definición de "igualdad". La otra solución para el compilador es permitirle solo anular '==' OR '! =' Y dejar que el otro lo niegue inherentemente. Obviamente, ese no es el caso con el compilador de C # y estoy seguro de que allí '
La pregunta que debe hacerse es "¿por qué tengo que anular los operadores?" Esa es una decisión fuerte de tomar que requiere un fuerte razonamiento. Para los objetos, '==' y '! =' Comparar por referencia. Si debe anularlos para NO compararlos por referencia, está creando una inconsistencia general del operador que no es evidente para ningún otro desarrollador que lea detenidamente ese código. Si está intentando hacer la pregunta "¿es equivalente el estado de estas dos instancias?", Entonces debe implementar IEquatible, definir Equals () y utilizar esa llamada al método.
Por último, IEquatable () no define NotEquals () por el mismo razonamiento: potencial para abrir inconsistencias de operador de igualdad. NotEquals () SIEMPRE debe devolver! Equals (). Al abrir la definición de NotEquals () a la clase que implementa Equals (), una vez más está forzando el tema de la coherencia en la determinación de la igualdad.
Editar: este es simplemente mi razonamiento.
fuente
operator ==
y el compilador inferiríaoperator !=
. Después de todo, esto es para lo que haceoperator +
yoperator +=
.a != b
como!(a == b)
... (que no es lo que hace C #).Probablemente, algo en lo que no pensaron no tuvo tiempo de hacer.
Siempre uso tu método cuando sobrecargo ==. Entonces solo lo uso en el otro.
Tienes razón, con una pequeña cantidad de trabajo, el compilador podría darnos esto de forma gratuita.
fuente