Llamada a miembro virtual en C#: cómo no romper el constructor y acelerar el código

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Virtual member call in a constructor: cuando el compilador de C# advierte, y no lo hace en vano



Cada desarrollador de C# que haya escrito clases con herencia al menos una vez, seguramente ha visto esta advertencia de ReSharper o Visual Studio: «Virtual member call in a constructor». Parece inofensiva, pero en realidad es una bandera roja que puede provocar errores difíciles de detectar y una caída en el rendimiento. Hoy analizaremos qué hay detrás de esta advertencia, cómo se relaciona con el rendimiento y por qué incluso los programadores experimentados cometen este error a veces.



El tema no surge de la nada: la pregunta «Virtual member call in a constructor» en Stack Overflow ha acumulado miles de visitas y decenas de respuestas. Esto significa que el problema es real y afecta a muchos. Pero iremos más allá: no solo explicaremos cómo solucionarlo, sino que mostraremos cómo optimizar el código, evitando asignaciones innecesarias y comportamientos inesperados.



¿Qué es «Virtual member call in a constructor»?



Imagina esta situación: creas una clase base, en cuyo constructor se llama a un método virtual. ¿Qué podría salir mal, aparentemente? Después de todo, los métodos virtuales están diseñados precisamente para ser sobrescritos. Pero el problema es que, al llamar a un método virtual desde el constructor de la clase base, el objeto de la clase derivada aún no está completamente inicializado. Esto puede provocar que se llame al método sobrescrito sobre un objeto «en bruto», donde los campos aún no están establecidos y las dependencias no están inyectadas.



public class BaseClass{    public BaseClass()    {        // ¡Atención! Llamada virtual en el constructor        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 aún no está inicializado! Console.WriteLine($"DerivedClass: {_data?.Length ?? 0}"); }}

// Usovar obj = new DerivedClass("Hello"); // Imprime: DerivedClass: 0, no 5


En este ejemplo, _data será null en el momento de la llamada a VirtualMethod, porque el constructor de la clase derivada aún no se ha ejecutado. Este es un caso clásico donde la llamada virtual en el constructor provoca un comportamiento no evidente.



¿Por qué es un problema de rendimiento?



A primera vista, esto es solo un error lógico. Pero profundicemos. Cuando llamas a un método virtual desde el constructor, el entorno de ejecución (CLR) se ve obligado a realizar comprobaciones y despachos adicionales. En una situación normal, la tabla virtual de métodos (vtable) ya está lista y la llamada es rápida. Sin embargo, en el constructor de la clase base, la vtable puede no estar completamente construida para el tipo derivado. Esto provoca:



  • Gastos generales adicionales en el despacho — el CLR pierde tiempo verificando la corrección de la llamada.
  • Potencial compilación JIT de código no óptimo — el JIT puede no ser capaz de incluir (inline) dicha llamada, lo que reduce el rendimiento.
  • Comprobaciones adicionales de null — si sobrescribes el método, tienes que escribir código defensivo, lo que aumenta el tamaño del IL y el tiempo de ejecución.


En sistemas de alta carga, donde los constructores se llaman miles de veces por segundo, estos micro-retrasos pueden acumularse. Por lo tanto, la optimización del código C# debe comenzar con estos detalles aparentemente pequeños.



¿Cuándo puede ser útil una llamada virtual en el constructor?



Spoiler: casi nunca. Pero hay raras excepciones cuando sabes exactamente lo que haces. Por ejemplo, en el patrón Template Method, donde la clase base define el esqueleto de un algoritmo y las clases derivadas

Blogs

Book Recommendations