Por que o compilador C # emitindo uma instrução callvirt para uma chamada de método GetType ()?
-
21-08-2019 - |
Pergunta
Estou curioso para saber por que isso está acontecendo. Por favor, leia o exemplo de código abaixo e a IL correspondente que foi emitida nos comentários abaixo de cada seção:
using System;
class Program
{
static void Main()
{
Object o = new Object();
o.GetType();
// L_0001: newobj instance void [mscorlib]System.Object::.ctor()
// L_0006: stloc.0
// L_0007: ldloc.0
// L_0008: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
new Object().GetType();
// L_000e: newobj instance void [mscorlib]System.Object::.ctor()
// L_0013: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
}
}
Por que o compilador emitir um callvirt
para a primeira seção, mas um call
para a segunda seção? Existe alguma razão para que o compilador jamais iria emitir uma instrução callvirt
por um método não-virtual? E se há casos em que o compilador irá emitir um callvirt
para um método não-virtual que isso crie problemas para o Tipo-segurança?
Solução
Apenas jogar pelo seguro.
Tecnicamente C # compilador não sempre uso callvirt
Para métodos estáticos e métodos definidos em tipos de valor, ele usa call
. A maioria é fornecido através da instrução callvirt
IL.
A diferença que balançou o voto entre os dois é o fato de que call
assume o "objeto que está sendo usado para fazer a chamada" não é nulo. callvirt
nas outras verificações mão para não nulo e lança um NullReferenceException, se necessário.
- Para métodos estáticos, o objeto é um objeto do tipo e não pode ser nulo. Ditto para tipos de valor. Daí
call
é usado para eles -. Melhor desempenho - Para os outros, os projetistas da linguagem decidiu ir com
callvirt
para que os verifica compilador JIT que o objeto que está sendo usado para fazer a chamada não é nulo. Mesmo para métodos de instância não-virtuais .. eles valorizado segurança sobre o desempenho.
Veja também: Jeff Richter faz um trabalho melhor neste - no capítulo seu 'Designing Tipos' em CLR via C # 2ª Ed
Outras dicas
este velho post por Eric Gunnerson.
Aqui está o texto da mensagem:
Por que C # sempre usar callvirt?
Esta questão surgiu em um C # apelido interno, e eu pensei que a resposta seria de interesse geral. Isso supondo que a resposta está correta - tem sido um bom tempo
.A linguagem .NET IL oferece tanto uma chamada e instrução callvirt, com o callvirt sendo usado para chamar funções virtuais. Mas se você olhar através do código que C # gera, você vai ver que ele gera um "callvirt" mesmo nos casos em que não há nenhuma função virtual envolvidos. Por que fazer isso?
Voltei através das notas de design de linguagem que eu tenho, e eles afirmam claramente que nós decidimos usar callvirt em 1999/12/13. Infelizmente, eles não captam a nossa razão para fazer isso, então eu vou ter que ir da minha memória.
Nós tínhamos chegado um relatório de alguém (um provável dos grupos .NET usando C # (pensei que ainda não foi nomeado C # naquele tempo)) que tinha escrito código que chamou um método em um ponteiro nulo, mas eles didn 't obter uma exceção porque o método não acessar quaisquer campos (ou seja, ‘este’ foi nula, mas nada no método utilizado). Esse método chamado então um outro método que usou a este ponto e lançou uma exceção, e um pouco de cabeça coçar seguiu. Depois que percebi isso, enviaram-nos uma nota sobre o assunto.
Nós pensamos que ser capaz de chamar um método em uma instância nula foi um pouco estranho. Peter Golde fiz alguns testes para ver o que o impacto perf foi sempre de usar callvirt, e foi pequena o suficiente para que nós decidimos fazer a mudança.
Como (talvez-) aparte interessante ... GetType()
é incomum em que não virtual
- Isso leva a algumas coisas muito, muito estranhas .
(marcado como wiki, uma vez que é um pouco off-topic para a questão real)
O compilador não sabe o tipo real do o
na primeira expressão, mas não sabe o tipo real no segundo expressão. Parece que ele só está olhando para uma instrução de cada vez.
Isso é bom, porque C # depende muito do JIT para otimização. É muito provável que em um caso tão simples que ambas as chamadas se tornará chamadas de instância em tempo de execução.
Eu não acredito callvirt
nunca é emitida para métodos não-virtuais, mas mesmo que fosse, não seria nenhum problema porque o método nunca seria substituído (por razões óbvias).
Eu arriscaria um palpite de que é porque os primeiros atribui a uma variável, o que potencialmente poderia conter uma instância downcasted de outro tipo que poderia ter GetType
substituído (embora nós podemos ver que não faz); o segundo nunca poderia ser outra coisa senão Object
.