O compilador C # exige que sempre que um tipo personalizado defina operador ==
, ele também deve definir !=
(veja aqui ).
Por quê?
Estou curioso para saber por que os designers consideraram necessário e por que o compilador não pode usar como padrão uma implementação razoável para um dos operadores quando apenas o outro está presente. Por exemplo, Lua permite definir apenas o operador de igualdade e você obtém o outro gratuitamente. O C # pode fazer o mesmo solicitando que você defina == ou ambos == e! = E depois compile automaticamente o operador! = Ausente como !(left == right)
.
Entendo que existem casos de canto estranhos em que algumas entidades podem não ser iguais nem desiguais (como as NaN da IEEE-754), mas essas parecem ser a exceção, não a regra. Portanto, isso não explica por que os designers do compilador C # fizeram da exceção a regra.
Eu já vi casos de mão de obra deficiente em que o operador de igualdade é definido, então o operador de desigualdade é um copiar-colar com todas as comparações revertidas e todos os&& alternados para um || (você entendeu ... basicamente! (a == b) expandido pelas regras de De Morgan). Essa é uma prática ruim que o compilador poderia eliminar por design, como é o caso de Lua.
Nota: O mesmo vale para os operadores <> <=> =. Não consigo imaginar casos em que você precisará defini-los de maneiras não naturais. Lua permite definir apenas <e <= e define> = e> naturalmente através da negação dos formadores. Por que o C # não faz o mesmo (pelo menos 'por padrão')?
EDITAR
Aparentemente, existem razões válidas para permitir que o programador implemente verificações de igualdade e desigualdade da maneira que desejar. Algumas das respostas apontam para casos em que isso pode ser bom.
O núcleo da minha pergunta, no entanto, é por que isso é exigido à força no C # quando normalmente não é logicamente necessário?
Também está em contraste impressionante com as opções de design para interfaces .NET como Object.Equals
, IEquatable.Equals
IEqualityComparer.Equals
onde a falta de uma NotEquals
contrapartida mostra que a estrutura considera os !Equals()
objetos como desiguais e é isso. Além disso, classes Dictionary
e métodos como .Contains()
dependem exclusivamente das interfaces mencionadas acima e não usam os operadores diretamente, mesmo que sejam definidos. De fato, quando o ReSharper gera membros de igualdade, ele define ambos ==
e !=
em termos de, Equals()
e mesmo assim, somente se o usuário optar por gerar operadores. Os operadores de igualdade não são necessários pela estrutura para entender a igualdade de objetos.
Basicamente, a estrutura .NET não se preocupa com esses operadores, apenas se preocupa com alguns Equals
métodos. A decisão de exigir que os operadores == e! = Sejam definidos em conjunto pelo usuário está relacionada exclusivamente ao design da linguagem e não à semântica de objetos no que diz respeito ao .NET.
fonte
Respostas:
Não posso falar pelos designers da linguagem, mas pelo que posso raciocinar, parece que foi uma decisão de design intencional e adequada.
Observando esse código F # básico, você pode compilar isso em uma biblioteca de trabalho. Este é um código legal para o F # e sobrecarrega apenas o operador de igualdade, não a desigualdade:
Isso faz exatamente o que parece. Ele cria apenas um comparador de igualdade
==
e verifica se os valores internos da classe são iguais.Embora não seja possível criar uma classe como esta em C #, você pode usar uma que foi compilada para .NET. É óbvio que ele usará nosso operador sobrecarregado para
==
Então, para que o tempo de execução usa!=
?O padrão C # EMCA possui várias regras (seção 14.9) que explicam como determinar qual operador usar ao avaliar a igualdade. Para simplificar demais e, portanto, não ser perfeitamente preciso, se os tipos que estão sendo comparados forem do mesmo tipo e houver um operador de igualdade sobrecarregado, ele usará essa sobrecarga e não o operador de igualdade de referência padrão herdado de Object. Não é surpresa, portanto, que, se apenas um dos operadores estiver presente, ele usará o operador de igualdade de referência padrão, que todos os objetos possuem, não haverá sobrecarga para ele. 1
Sabendo que esse é o caso, a verdadeira questão é: por que isso foi projetado dessa maneira e por que o compilador não o descobre por si próprio? Muitas pessoas estão dizendo que essa não foi uma decisão de design, mas eu gosto de pensar que foi pensado dessa maneira, especialmente no fato de todos os objetos terem um operador de igualdade padrão.
Então, por que o compilador não cria automaticamente o
!=
operador? Não sei ao certo, a menos que alguém da Microsoft confirme isso, mas é isso que posso determinar com base nos fatos.Para evitar comportamento inesperado
Talvez eu queira fazer uma comparação de valores
==
para testar a igualdade. No entanto, quando se tratava de!=
eu não me importava se os valores eram iguais, a menos que a referência fosse igual, porque para o meu programa considerá-los iguais, eu me importo apenas se as referências corresponderem. Afinal, isso é descrito como comportamento padrão do C # (se os dois operadores não estivessem sobrecarregados, como seria o caso de algumas bibliotecas .net escritas em outro idioma). Se o compilador estava adicionando código automaticamente, eu não podia mais confiar no compilador para gerar o código que deveria ser compatível. O compilador não deve escrever código oculto que altera o seu comportamento, especialmente quando o código que você escreveu está dentro dos padrões de C # e da CLI.Em termos de forçá- lo a sobrecarregá-lo, em vez de seguir o comportamento padrão, posso apenas afirmar com firmeza que ele está no padrão (EMCA-334 17.9.2) 2 . O padrão não especifica o porquê. Eu acredito que isso se deve ao fato de que o C # empresta muito comportamento do C ++. Veja abaixo mais sobre isso.
Quando você substitui
!=
e==
, não precisa retornar bool.Este é outro motivo provável. Em C #, esta função:
é tão válido quanto este:
Se você estiver retornando algo diferente de bool, o compilador não poderá inferir automaticamente um tipo oposto. Além disso, no caso em que o seu operador faz retorno bool, ele simplesmente não faz sentido para eles criar gerar código que só existiria no caso específico de um ou, como eu disse acima, o código que esconde o comportamento padrão do CLR.
C # empresta muito do C ++ 3
Quando o C # foi introduzido, havia um artigo na revista MSDN que escrevia, falando sobre C #:
Sim, o objetivo do projeto do C # era fornecer quase a mesma quantidade de energia do C ++, sacrificando apenas um pouco por conveniências como segurança rígida de tipos e coleta de lixo. C # foi fortemente modelado após C ++.
Você não ficará surpreso ao saber que, em C ++, os operadores de igualdade não precisam retornar bool , conforme mostrado neste programa de exemplo
Agora, o C ++ não exige diretamente que você sobrecarregue o operador complementar. Se você compilou o código no programa de exemplo, verá que ele é executado sem erros. No entanto, se você tentou adicionar a linha:
você vai ter
Portanto, embora o próprio C ++ não exija sobrecarga em pares, ele não permitirá que você use um operador de igualdade que você não sobrecarregou em uma classe personalizada. É válido no .NET, porque todos os objetos têm um padrão; C ++ não.
1. Como observação lateral, o padrão C # ainda exige que você sobrecarregue o par de operadores, se desejar sobrecarregar um dos dois. Isso faz parte do padrão e não simplesmente do compilador . No entanto, as mesmas regras relativas à determinação de qual operador chamar se aplicam quando você está acessando uma biblioteca .net escrita em outro idioma que não possui os mesmos requisitos.
2. EMCA-334 (pdf) ( http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf )
3. E Java, mas esse não é realmente o ponto aqui
fonte
==
retornar algo além de umbool
? Se você retornar umint
, não poderá mais usá-lo como uma declaração condicional, por exemplo.if(a == b)
não será mais compilado. Qual é o objetivo?Provavelmente, se alguém precisar implementar lógica de três valores (ou seja
null
). Em casos como esse - SQL padrão ANSI, por exemplo - os operadores não podem simplesmente ser negados, dependendo da entrada.Você poderia ter um caso em que:
E
a == true
retornafalse
ea == false
também retornafalse
.fonte
a
não é um booleano, você deve compará-lo com um enum de três estados, não um realtrue
oufalse
.a == true
éfalse
, então eu sempre esperara != true
para sertrue
, apesar dea == false
estarfalse
==
, não por que eles devem ser forçados a substituir ambos.!=
estaria correta. No extremamente raro caso em que nós queremosx == y
ex != y
para ser ambos falsos (Eu não consigo pensar em um único exemplo que faz sentido) , eles ainda poderiam substituir a implementação padrão e fornecer a sua própria. Sempre torne o caso comum o padrão!Fora isso, o C # difere o C ++ em muitas áreas, a melhor explicação que posso pensar é que, em alguns casos, você pode querer adotar uma abordagem ligeiramente diferente para provar "não igualdade" do que provar "igualdade".
Obviamente, com a comparação de cadeias, por exemplo, você pode apenas testar a igualdade e
return
sair do loop quando vir caracteres sem correspondência. No entanto, pode não ser tão limpo com problemas mais complicados. O filtro de floração vem à mente; é muito fácil saber rapidamente se o elemento não está no conjunto, mas difícil saber se o elemento está no conjunto. Embora a mesmareturn
técnica possa ser aplicada, o código pode não ser tão bonito.fonte
!
trava você em uma única definição de igualdade, na qual um codificador informado pode otimizar ambos==
e!=
.Se você observar implementações de sobrecargas de == e! = Na fonte .net, elas geralmente não implementam! = As! (Esquerda == direita). Eles o implementam totalmente (como ==) com lógica negada. Por exemplo, DateTime implementa == como
e! = as
Se você (ou o compilador fez isso implicitamente) implementasse! = As
então você está assumindo a implementação interna de == e! = nas coisas que sua classe está referenciando. Evitar essa suposição pode ser a filosofia por trás de sua decisão.
fonte
Para responder sua edição, a respeito de por que você é forçado a substituir ambos, se você substituir um, está tudo na herança.
Se você substituir ==, provavelmente fornecerá algum tipo de igualdade semântica ou estrutural (por exemplo, DateTimes serão iguais se suas propriedades InternalTicks forem iguais, mesmo que possam haver instâncias diferentes), você estará alterando o comportamento padrão do operador de Object, que é o pai de todos os objetos .NET. O operador == é, em C #, um método cuja implementação básica Object.operator (==) executa uma comparação referencial. O Object.operator (! =) É outro método diferente, que também executa uma comparação referencial.
Em quase qualquer outro caso de substituição de método, seria ilógico presumir que a substituição de um método também resultaria em uma mudança comportamental em um método antônimo. Se você criou uma classe com os métodos Increment () e Decrement () e substituiu Increment () em uma classe filho, esperaria que Decrement () também fosse substituído pelo oposto do seu comportamento substituído? O compilador não pode ser inteligente o suficiente para gerar uma função inversa para qualquer implementação de um operador em todos os casos possíveis.
No entanto, os operadores, embora implementados de maneira muito semelhante aos métodos, trabalham conceitualmente em pares; == e! =, <e> e <= e> =. Seria ilógico, neste caso, do ponto de vista de um consumidor, pensar que! = Funcionou de maneira diferente de ==. Portanto, o compilador não pode assumir que a! = B ==! (A == b) em todos os casos, mas geralmente é esperado que == e! = Operem de maneira semelhante, portanto o compilador força você implementar em pares, no entanto, você acaba fazendo isso. Se, para a sua classe, a! = B ==! (A == b), basta implementar o operador! = Usando! (==), mas se essa regra não se aplicar em todos os casos do seu objeto (por exemplo, , se a comparação com um valor específico, igual ou desigual, não for válida), você deverá ser mais inteligente que o IDE.
A pergunta REAL que deve ser feita é por que <e> e <= e> = são pares para operadores comparativos que devem ser implementados simultaneamente, quando em termos numéricos! (A <b) == a> = be! (A> b) == a <= b. Você deve implementar todos os quatro se substituir um e provavelmente também substituir == (e! =), Porque (a <= b) == (a == b) se a for semanticamente igual a b.
fonte
Se você sobrecarregar == para o seu tipo personalizado, e não! =, Ele será tratado pelo operador! = Para objeto! = Objeto, pois tudo é derivado do objeto, e isso seria muito diferente de CustomType! = CustomType.
Além disso, os criadores de idiomas provavelmente desejavam dessa maneira permitir a maior flexibilidade aos codificadores e também para que eles não fizessem suposições sobre o que você pretende fazer.
fonte
É isso que me vem à mente primeiro:
false
, tanto para==
e!=
(ou seja, se eles não podem ser comparados, por algum motivo)fonte
Dictionary
eList
se você usar seu tipo personalizado com elas.Dictionary
usaGetHashCode
eEquals
, não os operadores.List
usosEquals
.Bem, provavelmente é apenas uma opção de design, mas como você diz,
x!= y
não precisa ser o mesmo que!(x == y)
. Ao não adicionar uma implementação padrão, você tem certeza de que não pode esquecer de implementar uma implementação específica. E se é realmente tão trivial quanto você diz, você pode simplesmente implementar um usando o outro. Não vejo como isso é 'má prática'.Também pode haver outras diferenças entre C # e Lua ...
fonte
As palavras-chave na sua pergunta são " por que " e " deve ".
Como um resultado:
Responder dessa maneira, porque eles o projetaram para ser assim, é verdade ... mas não responde à parte "por que" da sua pergunta.
Responder que às vezes pode ser útil substituir ambos independentemente, é verdade ... mas não responder à parte "obrigatória" da sua pergunta.
Eu acho que a resposta simples é que não há nenhuma razão convincente para que o C # exija que você substitua os dois.
A linguagem deve permitir-lhe apenas para substituir
==
, e fornecer-lhe uma implementação padrão!=
que é!
isso. Se você deseja substituir!=
também, participe.Não foi uma boa decisão. Humanos projetam linguagens, humanos não são perfeitos, C # não é perfeito. Encolher de ombros e QED
fonte
Apenas para adicionar as excelentes respostas aqui:
Considere o que aconteceria no depurador , ao tentar entrar em um
!=
operador e acabar em um==
operador! Fale sobre confuso!Faz sentido que o CLR permita a você deixar de fora um ou outro dos operadores - pois ele deve funcionar com muitos idiomas. Mas há uma abundância de exemplos de C # não expondo características CLR (
ref
retornos e habitantes locais, por exemplo), e uma abundância de exemplos de características de execução não no próprio CLR (por exemplo:using
,lock
,foreach
, etc).fonte
Linguagens de programação são rearranjos sintáticos de afirmações lógicas excepcionalmente complexas. Com isso em mente, você pode definir um caso de igualdade sem definir um caso de não-igualdade? A resposta é não. Para que um objeto a seja igual ao objeto b, o inverso do objeto a não é igual a b também deve ser verdadeiro. Outra maneira de mostrar isso é
if a == b then !(a != b)
isso fornece a capacidade definida para a linguagem determinar a igualdade de objetos. Por exemplo, a comparação NULL! = NULL pode lançar uma chave inglesa na definição de um sistema de igualdade que não implementa uma declaração de não igualdade.
Agora, no que diz respeito à idéia de! = Simplesmente ser uma definição substituível como em
if !(a==b) then a!=b
Eu não posso discutir com isso. No entanto, provavelmente foi uma decisão do grupo de especificação de linguagem C # que o programador fosse forçado a definir explicitamente a igualdade e a não igualdade de um objeto juntas.
fonte
Null
seria apenas um problema porquenull
é uma entrada válida para a==
qual não deveria ser, embora obviamente seja massivamente conveniente. Pessoalmente, acho que isso permite grandes quantidades de programação vaga e desleixada.Em suma, consistência forçada.
'==' e '! =' são sempre verdadeiros opostos, não importa como você os defina, definidos como tal pela definição verbal de "iguais" e "não iguais". Ao definir apenas um deles, você se abre para uma inconsistência do operador de igualdade em que '==' e '! =' Podem ser verdadeiros ou falsos para dois valores fornecidos. Você deve definir ambos, pois, ao escolher definir um, também deve definir o outro adequadamente, para que fique claramente claro qual é a sua definição de "igualdade". A outra solução para o compilador é permitir apenas que você substitua '==' OR '! =' E deixe o outro como inerentemente negando o outro. Obviamente, esse não é o caso do compilador C # e tenho certeza de que existe '
A pergunta que você deve fazer é "por que preciso substituir os operadores?" Essa é uma decisão forte a ser tomada, que requer um forte raciocínio. Para objetos, '==' e '! =' Compare por referência. Se você deseja substituí-los para NÃO comparar por referência, você está criando uma inconsistência geral do operador que não é aparente para qualquer outro desenvolvedor que examine esse código. Se você está tentando fazer a pergunta "o estado dessas duas instâncias é equivalente?", Você deve implementar IEquatible, definir Equals () e utilizar essa chamada de método.
Por fim, IEquatable () não define NotEquals () pelo mesmo raciocínio: potencial para abrir inconsistências no operador de igualdade. NotEquals () deve SEMPRE retornar! Equals (). Ao abrir a definição de NotEquals () para a classe que implementa Equals (), você está forçando novamente a questão da consistência na determinação da igualdade.
Edit: Este é simplesmente o meu raciocínio.
fonte
operator ==
e o compilador infeririaoperator !=
. Afinal, é para isso que serveoperator +
eoperator +=
.a != b
como!(a == b)
... (o que não é o que o C # faz).Provavelmente, apenas algo em que não pensaram não teve tempo de fazer.
Eu sempre uso seu método quando sobrecarrego ==. Então eu apenas uso no outro.
Você está certo, com uma pequena quantidade de trabalho, o compilador pode nos fornecer isso de graça.
fonte