CodeGym /Cursos /C# SELF /Métodos virtuales en C#: polimorfismo y sobreescritura

Métodos virtuales en C#: polimorfismo y sobreescritura

C# SELF
Nivel 20 , Lección 2
Disponible

1. Introducción

Imagina que tienes una clase base Animal con un método Speak(). Por defecto, para todos los animales escribe "Algún sonido". Quieres crear clases hijas, por ejemplo, Dog y Cat, para que digan no "Algún sonido", sino sus frases reales ("¡Guau!" y "¡Miau!").

Podrías escribir tu propio método Speak() en cada hija, pero si trabajas con una colección tipo Animal[], la llamada animal.Speak() igual va a llamar al método de Animal, no al del perro o el gato — porque C# por defecto se fija en el tipo de la variable, no en el objeto real detrás.

Aquí va un ejemplo sencillo:


Animal dog = new Dog();
dog.Speak(); // ¿Y qué se muestra aquí?

Por defecto — "Algún sonido", no "¡Guau!", aunque en Dog tengas tu propio método Speak(). ¿Entonces qué hacemos? Hay que declarar el método como virtual (en la clase base) y override (en la derivada).

¿Qué es un método virtual?

Un método virtual es un método que se declara en la clase base con el modificador virtual y está pensado para que las hijas puedan sobreescribirlo ( override).

Cuando llamas a ese método a través de una referencia a la clase base, se ejecuta el método del objeto "real" — como en la vida: si domesticás un animal y es un gato, va a decir "¡Miau!", no "Algún sonido".

Los métodos virtuales son la base del polimorfismo en C#.

2. Declaración de métodos virtuales

Cómo declarar un método virtual

En la clase base declaramos el método con la palabra clave virtual:


public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Algún sonido");
    }
}
Declaración de un método virtual en la clase base

En la hija usamos la palabra clave override:


public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("¡Guau!");
    }
}

public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("¡Miau!");
    }
}
Sobreescritura del método virtual en las hijas

Ejemplo: demostración de polimorfismo


// Por ejemplo, en el archivo Program.cs

// Clase base Animal con método virtual Speak
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Algún sonido");
    }
}

// Clase Dog, sobreescribe el comportamiento de Speak
public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("¡Guau!");
    }
}

// Clase Cat, también sobreescribe Speak
public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("¡Miau!");
    }
}

public static class Program
{
    public static void Main()
    {
        Animal[] animals = new Animal[]
        {
            new Animal(),
            new Dog(),
            new Cat()
        };

        foreach (Animal animal in animals)
        {
            animal.Speak(); // Muestra: "Algún sonido", "¡Guau!", "¡Miau!"
        }
    }
}

¿Ves la magia de los métodos virtuales? Aunque la variable sea de tipo Animal, se llama la versión del método de la implementación concreta.

3. ¿Qué pasa "por debajo": la tabla virtual

Breve sobre el mecanismo

Cuando declaras un método como virtual, el compilador lo mete en una "tabla virtual" especial de métodos para cada tipo (como un menú en el comedor para cada plato). Cuando se llama a ese método, C# no mira el tipo de la variable, sino lo que realmente hay en la referencia — y "mete" el método que corresponde al tipo real del objeto.

Se puede decir que C# pone la receta correcta en vez de la universal, si la clase tiene su propia versión.

Analogía: contrato con posibilidad de "añadir cláusula en letra pequeña"

Imagina que tienes un contrato de alquiler (clase base), y en él dice que el inquilino paga por defecto una cantidad fija cada mes (método PayRent()). Pero tú — como propietario astuto — permites en los anexos del contrato poner condiciones especiales (método virtual). Si algún inquilino (hija) usa ese derecho, escribe su propio override, y ahora el pago se hace de forma especial.

4. ¿Cuándo usar métodos virtuales?

  • Si el método en la clase base debe funcionar igual para la mayoría de las hijas, pero algunas pueden necesitar su propio comportamiento.
  • Cuando construyes una jerarquía de clases donde se espera ampliar o cambiar parte de la lógica.
  • Cuando desarrollas una arquitectura flexible, por ejemplo, para lógica de negocio, donde diferentes tipos de operaciones pueden requerir comportamiento individual.

En proyectos reales, los métodos virtuales son la base de los patrones Método plantilla (Template Method), Estrategia (Strategy), y en general de la mitad de la POO.

Comparación con métodos normales

Método normal Método virtual
¿Se puede sobreescribir? No Sí (override)
¿Cómo se llama? Por tipo de variable Por el tipo "real" del objeto
Polimorfismo No

Preguntas frecuentes

  • ¿Se puede hacer un constructor virtual?
    ¡No! Los constructores nunca son virtuales — siempre funcionan estrictamente por el tipo del objeto.
  • ¿Pueden los métodos virtuales ser estáticos?
    No, solo los métodos de instancia pueden ser virtuales. Los métodos estáticos no participan en el polimorfismo, porque no hay objeto — ¿entonces quién los va a sobreescribir?
  • ¿Pueden los campos ser virtuales?
    No, solo métodos, propiedades y eventos.

5. Práctica: mejorando la app "Mundo animal"

En nuestra app de práctica ya hiciste una jerarquía simple de animales. ¡Vamos a añadir nuevos métodos virtuales para que los animales puedan comer de forma diferente!

Ejemplo de código con comentarios


// En la clase base ponemos el método virtual Eat
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Algún sonido");
    }

    public virtual void Eat()
    {
        Console.WriteLine("Come comida genérica.");
    }
}

// En Dog sobreescribimos Eat
public class Dog : Animal
{
    public override void Speak() => Console.WriteLine("¡Guau!");
    public override void Eat() => Console.WriteLine("Come comida para perros.");
}

// En Cat también su propio comportamiento
public class Cat : Animal
{
    public override void Speak() => Console.WriteLine("¡Miau!");
    public override void Eat() => Console.WriteLine("Come pescado.");
}

public static class Program
{
    public static void Main()
    {
        Animal[] animals = new Animal[]
        {
            new Animal(),
            new Dog(),
            new Cat()
        };
        foreach (var animal in animals)
        {
            animal.Speak();
            animal.Eat();
            Console.WriteLine("---");
        }
    }
}

¡Listo, ahora cada gatito y perrito come a su manera! Y el Animal base sigue comiendo algo raro, como un funcional con "por defecto".

6. Errores típicos y matices

  • Puedes olvidarte de override en la hija — y entonces el método de la clase base se queda igual.
  • O al revés, te olvidas de virtual en la base, entonces en la hija no puedes poner override.

No puedes poner override sin virtual

El compilador de C# no te deja sobreescribir un método que no fue declarado como virtual (o abstracto) en la clase base.

¿Para qué sirve override?

override le dice claramente al compilador y al que lee el código: "No estoy escribiendo un método nuevo — estoy cambiando a propósito lo que está en la clase base". Así se evitan errores cuando quieres "sobreescribir", pero por accidente solo escribes un método nuevo con firma parecida.

Si te olvidas de override y escribes un método nuevo

public class Dog : Animal
{
    public void Speak()
    {
        Console.WriteLine("¡Guau!");
    }
}
¡Esto NO es sobreescritura! El polimorfismo no funciona.

Pero esto no es sobreescritura! Si accedes a Dog a través de una variable de tipo Animal, se va a llamar el método del animal ("Algún sonido"), no el del perro. ¡Vaya sorpresa con los tipos!

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION