type tag see remarks example cref c# language-design

tag - summary example c#



¿Por qué debemos definir tanto== como!=En C#? (13)

Aparte de que C # difiere a C ++ en muchas áreas, la mejor explicación que se me ocurre es que, en algunos casos, es posible que desee adoptar un enfoque ligeramente diferente para demostrar "no la igualdad" que para demostrar la "igualdad".

Obviamente, con la comparación de cadenas, por ejemplo, puedes probar la igualdad y return fuera del bucle cuando veas caracteres no coincidentes. Sin embargo, puede que no sea tan limpio con problemas más complicados. El filtro de la 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 bien la misma técnica de return podría aplicarse, el código podría no ser tan bonito.

El compilador de C # requiere que cada vez que un tipo personalizado defina operator == , también debe definir != (Ver here ).

¿Por qué?

Tengo curiosidad por saber por qué los diseñadores pensaron que era necesario y por qué el compilador no puede establecer una implementación razonable para ninguno de los operadores cuando solo el otro está presente. Por ejemplo, Lua le permite definir solo el operador de igualdad y usted obtiene el otro gratis. C # podría hacer lo mismo pidiéndole que defina == o ambos == y! = Y luego compile automáticamente el operador faltado! = Como !(left == right) .

Entiendo que hay casos de esquinas raros en los que algunas entidades pueden no ser iguales o desiguales (como las NaN de IEEE-754), pero parecen ser la excepción, no la regla. Así que 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 deficiente en los que se define el operador de igualdad, luego el operador de desigualdad es un copiar y pegar con todas y cada una de las comparaciones invertidas y cada && cambiado a un || (¡obtienes 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 se aplica a los operadores <> <=> =. No puedo imaginar casos en los que tengas que definirlos de maneras no naturales. 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 controles de igualdad y desigualdad como les plazca. Algunas de las respuestas apuntan a casos en los que eso puede ser bueno.

Sin embargo, el núcleo de mi pregunta es ¿por qué esto se requiere forzosamente en C # cuando normalmente no es lógicamente necesario?

También está en un contraste sorprendente con las opciones de diseño para interfaces .NET como Object.Equals , IEquatable.Equals IEqualityComparer.Equals donde la falta de una contraparte de NotEquals demuestra que el marco considera que los objetos son !Equals() y eso es todo. Además, las clases como Dictionary y los métodos como .Contains() dependen exclusivamente de las interfaces mencionadas y no utilizan los operadores directamente, incluso si están definidos. De hecho, cuando ReSharper genera miembros de igualdad, define tanto == como != En términos de Equals() e incluso entonces solo si el usuario elige generar operadores. El marco no necesita a los operadores de igualdad para comprender la igualdad de objetos.

Básicamente, el framework .NET no se preocupa por estos operadores, solo se preocupa por unos pocos métodos Equals . La decisión de requerir que los operadores == y! = Se definan en tándem por el usuario está relacionada puramente con el diseño del lenguaje y no con la semántica de objetos en lo que respecta a .NET.


Bueno, probablemente solo sea una opción de diseño, pero como dices, x!= y no tiene que ser lo mismo que !(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 ...


En definitiva, consistencia forzada.

''=='' y ''! ='' son siempre opuestos verdaderos, sin importar cómo los defina, definidos como tales por su definición verbal de "iguales" y "no iguales". Al solo definir uno de ellos, se abre a una inconsistencia de operadores de igualdad donde tanto ''=='' como ''! ='' Pueden ser verdaderos o falsos para dos valores dados. Debe definir ambas, ya que cuando elige definir una, también debe definir la otra adecuadamente para que quede claramente claro cuál es su definición de "igualdad". La otra solución para el compilador es permitirle solo anular ''=='' O ''! ='' Y dejar al otro como negando inherentemente al otro. Obviamente, ese no es el caso con el compilador de C # y estoy seguro de que hay una razón válida para eso que puede atribuirse estrictamente como una elección de simplicidad.

La pregunta que debe hacerse es "¿por qué necesito anular a los operadores?" Esa es una decisión firme que requiere un razonamiento fuerte. Para los objetos, ''=='' y ''! ='' Se comparan por referencia. Si tiene que anularlos para NO comparar 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 el estado de estas dos instancias equivalentes?", Entonces debe implementar IEquatible, definir Equals () y utilizar esa llamada de método.

Por último, IEquatable () no define NotEquals () para el mismo razonamiento: potencial para abrir inconsistencias de operadores de igualdad. NotEquals () siempre debe devolver! Igual a (). Al abrir la definición de NotEquals () a la clase que implementa Equals (), una vez más está forzando el problema de la coherencia en la determinación de la igualdad.

Editar: Este es simplemente mi razonamiento.


Esto es lo que viene a mi mente primero:

  • ¿Qué pasa si probar la desigualdad es mucho más rápido que probar la igualdad?
  • ¿Qué pasa si, en algunos casos , desea devolver false tanto para == como != (Es decir, si no se pueden comparar por algún motivo)

Las palabras clave en su pregunta son " por qué " y " debe ".

Como resultado:

Responder de esta manera porque lo diseñaron para que fuera así, es cierto ... pero no responde a la parte de "por qué" de tu pregunta.

Responder que a veces puede ser útil anular ambos de forma independiente, es cierto ... pero no responder a la parte "obligada" de su pregunta.

Creo que la respuesta simple es que no hay ninguna razón convincente por la que C # requiera que usted anule ambos.

El lenguaje debería permitirle anular solo == , y proporcionarle una implementación predeterminada de != es ! ese. Si por casualidad quieres anular != También, hazlo.

No fue una buena decisión. Los humanos diseñan lenguajes, los humanos no son perfectos, C # no es perfecto. Encogiéndose de hombros y qed


Los lenguajes de programación son reordenamientos sintácticos de una declaración lógica excepcionalmente compleja. Teniendo esto en cuenta, ¿puede 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 generar 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 una decisión del grupo de especificación del lenguaje C # sea que el programador se vea obligado a definir explícitamente la igualdad y la no igualdad de un objeto juntos.


No puedo hablar por los diseñadores de idiomas, pero según mi razonamiento, parece que fue una decisión de diseño adecuada e intencional.

Al observar 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 al operador de igualdad, no a la desigualdad:

module Module1 type Foo() = let mutable myInternalValue = 0 member this.Prop with get () = myInternalValue and set (value) = myInternalValue <- value static member op_Equality (left : Foo, right : Foo) = left.Prop = right.Prop //static member op_Inequality (left : Foo, right : Foo) = left.Prop <> right.Prop

Esto hace exactamente lo que parece. Crea un comparador de igualdad solo en == , y verifica si los valores internos de la clase son iguales.

Si bien no puedes crear una clase como esta en C #, puedes usar una que fue compilada para .NET. Es obvio que utilizará nuestro operador sobrecargado para == 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 cuando se evalúa la igualdad. Para ponerlo demasiado simplificado y, por lo tanto, no es perfectamente exacto, 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 Objeto. No es sorprendente, entonces, que si solo está presente uno de los operadores, utilizará el operador de igualdad de referencia predeterminado, que todos los objetos tienen, 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 lo resuelve por sí solo? Mucha gente dice que esta no fue una decisión de diseño, pero me gusta pensar que se pensó de esta manera, especialmente en lo que respecta 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 de los hechos.

Para prevenir comportamientos inesperados.

Tal vez quiero hacer una comparación de valores en == para probar la igualdad. Sin embargo, cuando se trataba de != 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 realmente se describe como el comportamiento predeterminado de C # (si ambos operadores no estuvieran sobrecargados, como en el caso de algunas bibliotecas .net escritas en otro idioma). Si el compilador estaba agregando el código automáticamente, ya no podía confiar en el compilador para generar el código que debería cumplir. El compilador no debe escribir código oculto que cambie su comportamiento, especialmente cuando el código que ha escrito cumple con los estándares de C # y CLI.

En términos de forzarlo a sobrecargarlo, en lugar de ir al comportamiento predeterminado, solo puedo decir con firmeza que está en la norma (EMCA-334 17.9.2) 2 . La norma no especifica por qué. Creo que esto se debe al hecho de que C # toma prestado mucho comportamiento de C ++. Vea más abajo para más información sobre esto.

Cuando reemplaza != Y == , no tiene que devolver bool.

Esta es otra razón probable En C #, esta función:

public static int operator ==(MyClass a, MyClass b) { return 0; }

es tan válido como este:

public static bool operator ==(MyClass a, MyClass b) { return true; }

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 su operador devuelva bool, simplemente no tiene sentido para ellos crear código de generación que solo existiría en ese caso específico o, como dije anteriormente, código que oculta el comportamiento predeterminado del CLR.

C # toma prestado mucho de C ++ 3

Cuando se introdujo C #, hubo un artículo en la revista MSDN que escribió, hablando de C #:

Muchos desarrolladores desearían que hubiera un lenguaje que fuera fácil de escribir, leer y mantener como Visual Basic, pero que aún proporcionara el poder y la flexibilidad de C ++.

Sí, el objetivo de diseño para C # era dar casi la misma cantidad de potencia que C ++, sacrificando solo un poco por conveniencias como la seguridad de tipos rígidos y la recolección de basura. C # fue fuertemente modelado después de C ++.

Es posible que no se sorprenda al 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:

cout << (a != b);

conseguirás

error del compilador C2678 (MSVC): binario ''! ='': no ​​se encontró ningún operador que tome un operando de la izquierda del tipo ''Prueba'' (o no hay una conversión aceptable) `.

Entonces, mientras que C ++ 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 de C # aún requiere que sobrecargue el par de operadores si desea sobrecargar cualquiera de los dos. Esto 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í.


Para responder a su edición, con respecto a por qué está obligado a anular ambos, si invalida 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 incluso a través de que pueden ser instancias diferentes), entonces está cambiando el comportamiento predeterminado del operador de Objeto, 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 un método, sería ilógico suponer que anular un método también daría lugar a un cambio de comportamiento en un método antonímico. Si creara una clase con los métodos Incremento () y Decremento (), y anulara Incremento () en una clase secundaria, ¿esperaría que Decremento () también se anulara con el opuesto 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 pares; == 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! = Funcionen de manera similar, por lo que el compilador se fuerza para implementar en pares, sin embargo, en realidad terminas haciendo eso. Si, para su clase, un! = 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 tiene que ser más inteligente que el IDE.

La pregunta REAL que debe plantearse es por qué <y> y <= y> = son pares para operadores comparativos que deben implementarse simultáneamente, cuando en términos numéricos! (A <b) == a> = b y! (A> b) == a <= b. Se le debe solicitar que implemente los cuatro si anula uno, y probablemente también se le debería solicitar que anule == (y! =), Porque (a <= b) == (a == b) si a es semánticamente igual a b.


Probablemente algo que no se les ocurrió no tuvo tiempo de hacer.

Siempre uso su 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 gratis.


Probablemente para si alguien necesita implementar una lógica de tres valores (es decir, null ). En casos como ese, por ejemplo, el estándar ANSI SQL, los operadores no pueden ser negados simplemente dependiendo de la entrada.

Podría tener un caso donde:

var a = SomeObject();

Y a == true devuelve false y a == false también devuelve false .


Si observa implementaciones de sobrecargas de == y! = En la fuente .net, a menudo no implementan! = As! (Izquierda == derecha). Lo implementan completamente (como ==) con lógica negada. Por ejemplo, DateTime implementa == as

return d1.InternalTicks == d2.InternalTicks;

y! = como

return d1.InternalTicks != d2.InternalTicks;

Si usted (o el compilador si lo hizo implícitamente) debiera implementar! = Como

return !(d1==d2);

entonces está haciendo una suposición sobre la implementación interna de == y! = en las cosas a las que hace referencia su clase. Evitar ese supuesto puede ser la filosofía detrás de su decisión.


Si sobrecarga == para su tipo personalizado, y no! =, Entonces será manejado por el operador! = Para object! = Object ya que todo se deriva de object, y esto sería muy diferente a CustomType! = CustomType.

Además, los creadores del lenguaje probablemente lo querían de esta manera para permitir la mayor flexibilidad para los programadores, y también para que no hagan suposiciones sobre lo que pretendes hacer.


Solo para agregar a las excelentes respuestas aquí:

¡Considere lo que sucedería en el depurador , cuando intente ingresar a un operador != Y termine en un operador == lugar! ¡Hablar de confuso!

Tiene sentido que CLR le permita la libertad de dejar fuera a uno u otro de los operadores, ya que debe funcionar en muchos idiomas. Pero hay muchos ejemplos de C # que no exponen las características de CLR ( ref y locals, por ejemplo), y muchos ejemplos de implementación de características que no están en el CLR (por ejemplo: using , lock , foreach , etc.).