Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A virtual method in C# is an instance method declared in a base class using the virtual keyword, permitting derived classes to replace its implementation using the override keyword. It establishes the foundation for runtime polymorphism by instructing the runtime to use late binding (dynamic dispatch). This ensures that method invocation resolves to the most derived implementation based on the object’s runtime type, rather than its compile-time reference type.
using System;

public class BaseClass
{
    // The virtual keyword enables overriding and provides a default implementation
    public virtual void Execute()
    {
        Console.WriteLine("Base implementation");
    }
}

public class DerivedClass : BaseClass
{
    // The override keyword replaces the base implementation in the vtable
    public override void Execute()
    {
        Console.WriteLine("Derived implementation");
        
        // The base implementation can still be accessed explicitly
        base.Execute(); 
    }
}

Technical Mechanics

At the Intermediate Language (IL) level, the C# compiler emits the callvirt instruction for almost all instance method invocations on reference types, regardless of whether the method is virtual or non-virtual. This is primarily because callvirt automatically performs a null check on the instance. The actual distinction between static and dynamic dispatch is resolved later by the Just-In-Time (JIT) compiler. When the JIT compiler processes a callvirt instruction for a method explicitly marked as virtual, it generates native code that performs a Virtual Method Table (vtable) lookup. The Common Language Runtime (CLR) maintains a vtable for every class, which is an array of function pointers pointing to the method implementations for that specific class. At runtime, the CLR inspects the actual type of the object instance in memory, locates its corresponding vtable, and dereferences the pointer to invoke the correct overridden method implementation.

Rules and Constraints

  • Implementation Requirement: Unlike abstract methods, virtual methods must provide a default implementation body.
  • Optional Overriding: Derived classes are not required to override a virtual method. If no override is provided, the CLR falls back to the closest base implementation in the inheritance hierarchy.
  • Access Modifiers: A virtual method cannot be declared as private. The overriding method in the derived class must generally maintain the exact same access modifier as the base virtual method. However, there is a notable cross-assembly exception: if a base virtual method is marked protected internal, a derived class in a different assembly must override it using the protected modifier.
  • Incompatible Modifiers: A method cannot be simultaneously virtual and static, abstract, or override (though an overridden method is implicitly virtual to its own derived classes).
  • Signature Matching and Covariance: The override method must have an identical signature—including method name, parameter types, and parameter order—to the virtual method. Historically, the return type also had to be identical. However, starting with C# 9.0, C# supports covariant return types, allowing an overriding method to return a type that is more derived than the return type of the base virtual method.

Virtual Overriding vs. Method Hiding

If a derived class declares a method with the exact same signature as a base class method but omits the override keyword (typically using the new keyword instead), it performs method hiding (shadowing) rather than overriding.
using System;

public class ShadowingClass : BaseClass
{
    // Hides the base method; does NOT participate in dynamic dispatch
    public new void Execute() 
    {
        Console.WriteLine("Shadowing implementation");
    }
}
In method hiding, the runtime uses early binding (static dispatch). The method invoked is determined strictly by the compile-time type of the reference variable, bypassing the vtable lookup entirely. If a ShadowingClass instance is cast to a BaseClass reference, invoking Execute() will execute the BaseClass implementation, whereas a true virtual/override pair would execute the derived implementation regardless of the reference type.
Master C# with Deep Grasping Methodology!Learn More