Por alguma razão, eu estava entrando na fonte do .NET Framework para a classe Double
e descobri que a declaração de ==
é:
public static bool operator ==(Double left, Double right) {
return left == right;
}
A mesma lógica se aplica a todos os operadores.
- Qual é o sentido dessa definição?
- Como funciona?
- Por que não cria uma recursão infinita?
c#
.net
language-lawyer
Thomas Ayoub
fonte
fonte
ceq
é emitido em IL. Isso é apenas para preencher alguns propósitos de documentação, embora não seja possível encontrar a fonte.Respostas:
Na realidade, o compilador transformará o
==
operador em umceq
código IL e o operador mencionado não será chamado.É provável que o motivo do operador no código-fonte possa ser chamado de outros idiomas que não o C # que não o
CEQ
convertam diretamente em uma chamada (ou através de reflexão). O código dentro do operador será compilado para aCEQ
, portanto, não haverá recursão infinita.De fato, se você chamar o operador por meio de reflexão, poderá ver que o operador é chamado (em vez de uma
CEQ
instrução) e obviamente não é infinitamente recursivo (já que o programa termina conforme o esperado):IL resultante (compilada pelo LinqPad 4):
Curiosamente - os mesmos operadores não existem (ou na fonte de referência ou através de reflexão) para tipos integrais, única
Single
,Double
,Decimal
,String
, eDateTime
, o que refuta a minha teoria de que eles existem para ser chamado de outros idiomas. Obviamente, você pode igualar dois números inteiros em outros idiomas sem esses operadores, então voltamos à pergunta "por que eles existemdouble
"?fonte
A principal confusão aqui é que você está assumindo que todas as bibliotecas .NET (neste caso, a Biblioteca Numérica Estendida, que não faz parte da BCL) são gravadas em C # padrão. Isso nem sempre é o caso, e diferentes idiomas têm regras diferentes.
No C # padrão, o trecho de código que você vê resultaria em um estouro de pilha, devido à maneira como a resolução de sobrecarga do operador funciona. No entanto, o código não está realmente no C # padrão - ele basicamente usa recursos não documentados do compilador C #. Em vez de chamar o operador, ele emite este código:
É isso :) Não há código C # 100% equivalente - isso simplesmente não é possível em C # com seu próprio tipo.
Mesmo assim, o operador real não é usado ao compilar o código C # - o compilador faz várias otimizações, como neste caso, onde substitui a
op_Equality
chamada pelo simplesceq
. Novamente, você não pode replicar isso em sua própriaDoubleEx
estrutura - é mágica do compilador.Certamente, essa não é uma situação única no .NET - há muito código que não é válido, C # padrão. Os motivos são geralmente (a) hacks de compilador e (b) um idioma diferente, com os hacks de tempo de execução ímpares (c) (estou olhando para você
Nullable
!).Como o compilador C # da Roslyn é uma fonte oepn, posso apontar o local onde a resolução de sobrecarga é decidida:
O local em que todos os operadores binários são resolvidos
Os "atalhos" para operadores intrínsecos
Ao olhar para os atalhos, você verá que a igualdade entre duplo e duplo resulta no operador duplo intrínseco, nunca no
==
operador real definido no tipo. O sistema de tipos .NET precisa fingir queDouble
é um tipo como outro qualquer, mas o C # não -double
é um primitivo no C #.fonte
#if
e outros artefatos que não estariam presentes no código compilado. Além disso, se fosse engenharia reversa paradouble
então por que não foi submetido a engenharia reversa paraint
oulong
? Eu acho que há uma razão para o código-fonte, mas acredito que o uso de==
dentro do operador é compilado paraCEQ
impedir a recursão. Como o operador é um operador "predefinido" para esse tipo (e não pode ser substituído), as regras de sobrecarga não se aplicam.double
não faz parte da BCL - está em uma biblioteca separada, que por acaso está incluída na especificação de C #. Sim, o==
compilado é para aceq
, mas isso ainda significa que este é um hack do compilador que você não pode replicar em seu próprio código e algo que não faz parte da especificação C # (assim como ofloat64
campo naDouble
estrutura). Não é uma parte contratual do C #, portanto, não faz sentido tratá-lo como um C # válido, mesmo que tenha sido compilado com o compilador C #.double
da mesma maneira queint
elong
-int
elong
são tipos primitivos aos quais todas as linguagens .NET devem suportar.float
,decimal
Edouble
não são.A fonte dos tipos primitivos pode ser confusa. Você viu a primeira linha da
Double
estrutura?Normalmente você não pode definir uma estrutura recursiva como esta:
Os tipos primitivos também têm suporte nativo no CIL. Normalmente eles não são tratados como tipos orientados a objetos. Um duplo é apenas um valor de 64 bits se for usado como
float64
no CIL. No entanto, se for tratado como um tipo .NET usual, ele conterá um valor real e métodos como qualquer outro tipo.Então, o que você vê aqui é a mesma situação para os operadores. Normalmente, se você usar o tipo de tipo duplo diretamente, ele nunca será chamado. BTW, sua fonte se parece com isso no CIL:
Como você pode ver, não há loop infinito (o
ceq
instrumento é usado em vez de chamar oSystem.Double::op_Equality
). Portanto, quando um double é tratado como um objeto, o método do operador será chamado, que eventualmente o manipulará como ofloat64
tipo primitivo no nível CIL.fonte
public struct MyNumber { internal MyNumber m_value; }
. Não pode ser compilado, é claro. O erro é o erro CS0523: O membro do Struct 'MyNumber.m_value' do tipo 'MyNumber' causa um ciclo no layout da estruturaDei uma olhada no CIL com JustDecompile. O interno
==
é traduzido para o código operacional CQ ceq . Em outras palavras, é igualdade primitiva de CLR.Fiquei curioso para ver se o compilador C # faria referência
ceq
ou o==
operador ao comparar dois valores duplos. No exemplo trivial que eu criei (abaixo), ele usouceq
.Este programa:
gera o seguinte CIL (observe a instrução com o rótulo
IL_0017
):fonte
Conforme indicado na documentação da Microsoft para o espaço para nome System.Runtime.Versioning: Os tipos encontrados neste espaço para nome destinam-se ao uso no .NET Framework e não a aplicativos do usuário.O espaço para nome System.Runtime.Versioning contém tipos avançados que oferecem suporte à versão implementações lado a lado do .NET Framework.
fonte
System.Runtime.Versioning
a ver com issoSystem.Double
?