Chamada de membro virtual em C#: como não quebrar o construtor e acelerar o código

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Virtual member call in a constructor: quando o compilador C# avisa, e não é à toa



Todo desenvolvedor C# que já escreveu classes com herança certamente já viu este aviso do ReSharper ou Visual Studio: «Virtual member call in a constructor». Parece inofensivo, mas na verdade é uma bandeira vermelha que pode levar a bugs difíceis de detectar e queda de desempenho. Hoje vamos analisar o que está por trás desse aviso, como ele se relaciona com o desempenho e por que até programadores experientes às vezes cometem esse erro.



O tema não é aleatório: a pergunta «Virtual member call in a constructor» no Stack Overflow acumulou milhares de visualizações e dezenas de respostas. Isso significa que o problema é real e afeta muitos. Mas vamos além: não apenas explicaremos como corrigir, mas mostraremos como otimizar o código, evitando alocações desnecessárias e comportamentos inesperados.



O que é «Virtual member call in a constructor»?



Imagine a situação: você cria uma classe base, cujo construtor chama um método virtual. O que poderia dar errado? Afinal, métodos virtuais foram criados para serem sobrescritos. Mas o problema é que, ao chamar um método virtual a partir do construtor da classe base, o objeto da classe derivada ainda não foi totalmente inicializado. Isso pode levar à chamada do método sobrescrito em um objeto "cru", onde os campos ainda não foram definidos e as dependências não foram injetadas.



public class BaseClass{    public BaseClass()    {        // Atenção! Chamada virtual no construtor        VirtualMethod();    }

public virtual void VirtualMethod() { Console.WriteLine("BaseClass.VirtualMethod"); }}

public class DerivedClass : BaseClass{ private readonly string _data;

public DerivedClass(string data) { _data = data; }

public override void VirtualMethod() { // _data ainda não foi inicializado! Console.WriteLine($"DerivedClass: {_data?.Length ?? 0}"); }}

// Usovar obj = new DerivedClass("Hello"); // Exibirá: DerivedClass: 0, e não 5


Neste exemplo, _data será null no momento da chamada de VirtualMethod, porque o construtor da classe derivada ainda não foi executado. Este é um caso clássico em que a chamada virtual no construtor leva a um comportamento não óbvio.



Por que isso é um problema de desempenho?



À primeira vista, é apenas um erro lógico. Mas vamos mais fundo. Quando você chama um método virtual a partir do construtor, o ambiente de execução (CLR) é forçado a realizar verificações e despachos adicionais. Em uma situação normal, a tabela virtual de métodos (vtable) já está pronta e a chamada é rápida. No entanto, no construtor da classe base, a vtable pode ainda não estar completamente construída para o tipo derivado. Isso leva a:



  • Custos adicionais de despacho — a CLR gasta tempo verificando a correção da chamada.
  • Potencial compilação JIT de código não otimizado — o JIT pode não conseguir embutir (inline) essa chamada, reduzindo o desempenho.
  • Verificações extras de null — se você sobrescreveu o método, precisa escrever código defensivo, o que aumenta o tamanho do IL e o tempo de execução.


Em sistemas de alta carga, onde construtores são chamados milhares de vezes por segundo, esses micro-atrasos podem se acumular. Portanto, a otimização de código C# deve começar por detalhes aparentemente pequenos como este.



Quando a chamada virtual no construtor pode ser útil?



Spoiler: quase nunca. Mas há raras exceções quando você sabe exatamente o que está fazendo. Por exemplo, no padrão Template Method, onde a classe base define o esqueleto de um algoritmo e as classes derivadas

Blogs

Book Recommendations