CSharp

Tutorial CSharp: Métodos virtuales y sobrecarga

Aprende en C# la diferencia entre métodos virtuales, sobrecarga y sobreescritura para dominar el polimorfismo y la programación orientada a objetos.

Aprende CSharp y certifícate

Sobrecarga vs sobreescritura

En la programación orientada a objetos con C#, existen dos conceptos fundamentales que a menudo se confunden: la sobrecarga y la sobreescritura de métodos. Aunque ambos permiten definir múltiples versiones de métodos, funcionan de manera completamente diferente y tienen propósitos distintos.

Sobrecarga de métodos

La sobrecarga de métodos permite definir múltiples métodos con el mismo nombre pero con diferentes parámetros dentro de la misma clase. El compilador determina qué versión del método invocar basándose en los argumentos proporcionados.

Características principales de la sobrecarga:

  • Ocurre en la misma clase
  • Los métodos tienen el mismo nombre
  • Difieren en el número, tipo o orden de parámetros
  • No tiene relación con la herencia
  • No requiere palabras clave especiales

Veamos un ejemplo sencillo de sobrecarga:

public class Calculadora
{
    // Sobrecarga para sumar dos enteros
    public int Sumar(int a, int b)
    {
        return a + b;
    }
    
    // Sobrecarga para sumar tres enteros
    public int Sumar(int a, int b, int c)
    {
        return a + b + c;
    }
    
    // Sobrecarga para sumar dos decimales
    public double Sumar(double a, double b)
    {
        return a + b;
    }
}

En este ejemplo, tenemos tres versiones del método Sumar. El compilador seleccionará automáticamente la versión correcta según los argumentos que pasemos:

Calculadora calc = new Calculadora();
int resultado1 = calc.Sumar(5, 10);           // Llama a Sumar(int, int)
int resultado2 = calc.Sumar(5, 10, 15);       // Llama a Sumar(int, int, int)
double resultado3 = calc.Sumar(2.5, 3.5);     // Llama a Sumar(double, double)

Sobreescritura de métodos

La sobreescritura permite a una clase derivada proporcionar una implementación específica de un método que ya está definido en su clase base. A diferencia de la sobrecarga, la sobreescritura:

  • Ocurre entre clases relacionadas por herencia
  • Los métodos tienen exactamente la misma firma (nombre y parámetros)
  • Requiere que el método base esté marcado como virtual, abstract o override
  • La clase derivada usa la palabra clave override

Veamos un ejemplo básico:

public class Animal
{
    // Método virtual que puede ser sobreescrito
    public virtual string HacerSonido()
    {
        return "...";
    }
}

public class Perro : Animal
{
    // Sobreescritura del método HacerSonido
    public override string HacerSonido()
    {
        return "Guau";
    }
}

public class Gato : Animal
{
    // Otra sobreescritura del mismo método
    public override string HacerSonido()
    {
        return "Miau";
    }
}

En este ejemplo, cada clase derivada proporciona su propia implementación del método HacerSonido(). Cuando llamamos al método, se ejecuta la versión correspondiente al tipo real del objeto:

Animal miAnimal = new Perro();
Console.WriteLine(miAnimal.HacerSonido());  // Imprime "Guau"

miAnimal = new Gato();
Console.WriteLine(miAnimal.HacerSonido());  // Imprime "Miau"

Diferencias clave entre sobrecarga y sobreescritura

Para entender mejor las diferencias, veamos una comparación directa:

Característica Sobrecarga Sobreescritura
Propósito Proporcionar múltiples versiones de un método para diferentes tipos de parámetros Modificar o extender el comportamiento de un método heredado
Ubicación Misma clase Entre clase base y derivada
Firma del método Mismo nombre, diferentes parámetros Exactamente la misma firma (nombre y parámetros)
Tipo de retorno Puede ser diferente Debe ser el mismo o un tipo derivado (covarianza)
Palabras clave No requiere palabras clave especiales Requiere virtual en la base y override en la derivada
Resolución En tiempo de compilación En tiempo de ejecución (polimorfismo)

Ejemplo combinado

Veamos un ejemplo que muestra tanto sobrecarga como sobreescritura:

public class Forma
{
    // Método sobreescribible con una versión
    public virtual double CalcularArea()
    {
        return 0;
    }
    
    // Método sobreescribible con otra versión (sobrecarga)
    public virtual double CalcularArea(double factor)
    {
        return CalcularArea() * factor;
    }
}

public class Circulo : Forma
{
    private double radio;
    
    public Circulo(double radio)
    {
        this.radio = radio;
    }
    
    // Sobreescritura del primer método
    public override double CalcularArea()
    {
        return Math.PI * radio * radio;
    }
    
    // Sobreescritura del segundo método (sobrecargado)
    public override double CalcularArea(double factor)
    {
        // Podemos llamar a la implementación base si queremos
        return base.CalcularArea(factor);
        
        // O podríamos hacer nuestra propia implementación:
        // return Math.PI * radio * radio * factor;
    }
    
    // Agregamos otra sobrecarga (no sobreescritura)
    public double CalcularArea(string unidad)
    {
        double area = CalcularArea();
        return unidad.ToLower() == "cm" ? area : area * 100;
    }
}

En este ejemplo:

  1. La clase Forma define dos versiones sobrecargadas del método CalcularArea
  2. La clase Circulo sobreescribe ambas versiones
  3. Circulo también agrega una tercera versión sobrecargada que no existe en la clase base

Consideraciones importantes

  • La sobrecarga es una técnica para proporcionar diferentes formas de invocar una operación similar con diferentes tipos o cantidades de datos.
  • La sobreescritura es una característica del polimorfismo que permite a las clases derivadas proporcionar implementaciones específicas de métodos heredados.
  • No se puede sobreescribir un método que no esté marcado como virtual, abstract o ya sea override.
  • Los métodos sealed no pueden ser sobreescritos en clases derivadas.
  • Los métodos static no pueden ser sobreescritos (aunque pueden ser ocultados con new).

Entender la diferencia entre sobrecarga y sobreescritura es fundamental para aprovechar correctamente la flexibilidad que ofrece C# en el diseño orientado a objetos.

Métodos con virtual

En C#, la palabra clave virtual es fundamental para habilitar el polimorfismo en la programación orientada a objetos. Un método marcado como virtual indica que puede ser sobreescrito por clases derivadas, permitiendo diferentes implementaciones del mismo comportamiento según el tipo específico del objeto.

Declaración de métodos virtuales

Para declarar un método como virtual, simplemente se antepone la palabra clave virtual a la declaración del método:

public class ClaseBase
{
    public virtual void MiMetodo()
    {
        Console.WriteLine("Implementación en la clase base");
    }
}

Cuando un método se declara como virtual:

  • Proporciona una implementación predeterminada que las clases derivadas pueden utilizar tal cual
  • Permite a las clases derivadas modificar ese comportamiento mediante sobreescritura
  • Mantiene la misma firma (nombre, parámetros y tipo de retorno) en toda la jerarquía

Características importantes de los métodos virtuales

Los métodos virtuales tienen varias características que es importante conocer:

  • Solo pueden declararse en métodos de instancia (no en métodos estáticos)
  • No pueden ser privados (pues no tendría sentido sobreescribir algo inaccesible)
  • Deben tener una implementación (a diferencia de los métodos abstractos)
  • La sobreescritura es opcional para las clases derivadas

Cuándo usar métodos virtuales

Los métodos virtuales son especialmente útiles cuando:

  • Quieres proporcionar un comportamiento predeterminado que la mayoría de las clases derivadas pueden usar
  • Necesitas permitir que algunas clases derivadas personalicen ese comportamiento
  • Deseas trabajar con objetos a través de referencias de tipo base pero ejecutar la implementación más específica

Ejemplo práctico de métodos virtuales

Veamos un ejemplo que ilustra el uso de métodos virtuales en una jerarquía de clases:

public class Empleado
{
    public string Nombre { get; set; }
    public decimal SalarioBase { get; set; }
    
    public Empleado(string nombre, decimal salarioBase)
    {
        Nombre = nombre;
        SalarioBase = salarioBase;
    }
    
    // Método virtual con implementación predeterminada
    public virtual decimal CalcularSalario()
    {
        return SalarioBase;
    }
    
    // Otro método virtual
    public virtual string ObtenerDetalles()
    {
        return $"Empleado: {Nombre}, Salario: {CalcularSalario():C}";
    }
}

En este ejemplo, la clase Empleado define dos métodos virtuales: CalcularSalario() y ObtenerDetalles(). Ambos proporcionan una implementación predeterminada, pero pueden ser sobreescritos por clases derivadas.

Métodos virtuales vs métodos no virtuales

Para entender mejor la importancia de los métodos virtuales, comparemos el comportamiento de métodos virtuales y no virtuales:

public class Base
{
    // Método no virtual
    public void MetodoNormal()
    {
        Console.WriteLine("Base.MetodoNormal()");
    }
    
    // Método virtual
    public virtual void MetodoVirtual()
    {
        Console.WriteLine("Base.MetodoVirtual()");
    }
}

public class Derivada : Base
{
    // Oculta el método no virtual (no recomendado)
    public new void MetodoNormal()
    {
        Console.WriteLine("Derivada.MetodoNormal()");
    }
    
    // Sobreescribe el método virtual
    public override void MetodoVirtual()
    {
        Console.WriteLine("Derivada.MetodoVirtual()");
    }
}

Veamos cómo se comportan estos métodos:

Base b = new Base();
Derivada d = new Derivada();
Base bd = new Derivada();  // Referencia de tipo Base a objeto Derivada

// Llamadas a métodos no virtuales
b.MetodoNormal();    // Imprime: Base.MetodoNormal()
d.MetodoNormal();    // Imprime: Derivada.MetodoNormal()
bd.MetodoNormal();   // Imprime: Base.MetodoNormal() (¡Importante!)

// Llamadas a métodos virtuales
b.MetodoVirtual();   // Imprime: Base.MetodoVirtual()
d.MetodoVirtual();   // Imprime: Derivada.MetodoVirtual()
bd.MetodoVirtual();  // Imprime: Derivada.MetodoVirtual() (¡Importante!)

La diferencia clave está en la última línea de cada bloque. Con métodos no virtuales, el tipo de la referencia determina qué método se llama. Con métodos virtuales, el tipo del objeto real determina qué método se ejecuta, independientemente del tipo de la referencia.

Métodos virtuales y constructores

Es importante destacar que los constructores no pueden ser virtuales. Esto se debe a que los constructores no se heredan y siempre son específicos para cada clase. Sin embargo, se pueden llamar métodos virtuales desde los constructores, aunque esto requiere precaución:

public class Base
{
    public Base()
    {
        // Llamada a método virtual desde el constructor
        // ¡Precaución! Si una clase derivada sobreescribe este método,
        // se llamará a la versión sobreescrita antes de que el constructor
        // de la clase derivada se ejecute completamente
        MetodoVirtual();
    }
    
    public virtual void MetodoVirtual()
    {
        Console.WriteLine("Base.MetodoVirtual()");
    }
}

public class Derivada : Base
{
    private string dato;
    
    public Derivada()
    {
        // Este código se ejecuta después de que el constructor de Base haya terminado
        dato = "Inicializado";
    }
    
    public override void MetodoVirtual()
    {
        // ¡Peligro! Este método podría ejecutarse antes de que 'dato' esté inicializado
        Console.WriteLine($"Derivada.MetodoVirtual(), dato: {dato ?? "null"}");
    }
}

Al crear una instancia de Derivada, la salida sería:

Derivada.MetodoVirtual(), dato: null

Esto ocurre porque el constructor de la clase base se ejecuta antes que el constructor de la clase derivada, pero el método virtual ya está "vinculado" a la implementación de la clase derivada.

Rendimiento de los métodos virtuales

Los métodos virtuales tienen un pequeño impacto en el rendimiento debido a la resolución dinámica en tiempo de ejecución:

  • Los métodos no virtuales se resuelven en tiempo de compilación (enlace temprano)
  • Los métodos virtuales se resuelven en tiempo de ejecución (enlace tardío)

Este impacto es generalmente insignificante en aplicaciones modernas, pero puede ser relevante en código que se ejecuta millones de veces en bucles críticos para el rendimiento.

Métodos virtuales en interfaces (C# 8.0+)

A partir de C# 8.0, las interfaces pueden contener implementaciones predeterminadas de métodos, que funcionan de manera similar a los métodos virtuales:

public interface INotificador
{
    // Método con implementación predeterminada
    void Notificar(string mensaje)
    {
        Console.WriteLine($"Notificación estándar: {mensaje}");
    }
}

public class NotificadorUrgente : INotificador
{
    // Implementación personalizada
    public void Notificar(string mensaje)
    {
        Console.WriteLine($"¡URGENTE! {mensaje}");
    }
}

Aunque la sintaxis es diferente (no se usan las palabras clave virtual y override), el concepto es similar: proporcionar un comportamiento predeterminado que puede ser personalizado por las implementaciones.

Limitaciones de los métodos virtuales

Existen algunas limitaciones importantes al trabajar con métodos virtuales:

  • No se pueden declarar como static, private o sealed (aunque un método override puede ser sealed)
  • No se pueden cambiar los modificadores de acceso al sobreescribir (debe mantenerse la misma visibilidad)
  • No se pueden cambiar los parámetros ni el tipo de retorno (excepto por covarianza)

Ejemplo completo de jerarquía con métodos virtuales

Veamos un ejemplo más completo que muestra cómo los métodos virtuales permiten implementar el polimorfismo:

public class Figura
{
    public string Color { get; set; }
    
    public Figura(string color)
    {
        Color = color;
    }
    
    // Método virtual que calcula el área
    public virtual double CalcularArea()
    {
        return 0; // Implementación predeterminada
    }
    
    // Método virtual que describe la figura
    public virtual string Describir()
    {
        return $"Una figura de color {Color}";
    }
}

public class Circulo : Figura
{
    public double Radio { get; set; }
    
    public Circulo(string color, double radio) : base(color)
    {
        Radio = radio;
    }
    
    // Sobreescritura del método para calcular el área
    public override double CalcularArea()
    {
        return Math.PI * Radio * Radio;
    }
    
    // Sobreescritura del método para describir
    public override string Describir()
    {
        return $"Un círculo {Color} con radio {Radio} y área {CalcularArea():F2}";
    }
}

public class Rectangulo : Figura
{
    public double Ancho { get; set; }
    public double Alto { get; set; }
    
    public Rectangulo(string color, double ancho, double alto) : base(color)
    {
        Ancho = ancho;
        Alto = alto;
    }
    
    // Sobreescritura del método para calcular el área
    public override double CalcularArea()
    {
        return Ancho * Alto;
    }
    
    // Sobreescritura del método para describir
    public override string Describir()
    {
        return $"Un rectángulo {Color} de {Ancho}x{Alto} con área {CalcularArea():F2}";
    }
}

Este ejemplo muestra cómo podemos trabajar con figuras de manera polimórfica:

// Creamos una lista de figuras de diferentes tipos
List<Figura> figuras = new List<Figura>
{
    new Circulo("rojo", 5),
    new Rectangulo("azul", 4, 6),
    new Figura("verde")
};

// Iteramos sobre todas las figuras
foreach (var figura in figuras)
{
    // El método virtual correcto se llama según el tipo real
    Console.WriteLine(figura.Describir());
    Console.WriteLine($"Área: {figura.CalcularArea():F2}");
    Console.WriteLine();
}

La salida sería:

Un círculo rojo con radio 5 y área 78.54
Área: 78.54

Un rectángulo azul de 4x6 con área 24.00
Área: 24.00

Una figura de color verde
Área: 0.00

Este es el poder del polimorfismo mediante métodos virtuales: podemos tratar objetos de diferentes tipos de manera uniforme a través de su tipo base, pero cada objeto mantiene su comportamiento específico.

Métodos con override

La palabra clave override es un componente esencial del mecanismo de polimorfismo en C#. Mientras que virtual indica que un método puede ser sobreescrito, override es la herramienta que utilizamos en las clases derivadas para proporcionar una implementación específica de ese método.

Sintaxis básica de override

Para sobreescribir un método virtual, debemos usar la palabra clave override en la declaración del método en la clase derivada:

public class ClaseDerivada : ClaseBase
{
    public override void MiMetodo()
    {
        // Nueva implementación
        Console.WriteLine("Implementación en la clase derivada");
    }
}

Es importante destacar que para usar override, deben cumplirse varias condiciones:

  • El método que se sobreescribe debe estar marcado como virtual, abstract o ser ya un método override en la clase base
  • La firma del método debe ser exactamente igual (mismo nombre, parámetros y tipo de retorno)
  • El nivel de accesibilidad debe ser el mismo o menos restrictivo

Llamando al método de la clase base

Uno de los aspectos más útiles al sobreescribir métodos es la capacidad de extender la funcionalidad del método base en lugar de reemplazarla completamente. Esto se logra mediante la palabra clave base:

public class Estudiante : Persona
{
    public string Carrera { get; set; }
    
    public Estudiante(string nombre, int edad, string carrera) 
        : base(nombre, edad)
    {
        Carrera = carrera;
    }
    
    public override string ObtenerInformacion()
    {
        // Llamamos al método de la clase base y extendemos su funcionalidad
        string infoBase = base.ObtenerInformacion();
        return $"{infoBase}, Carrera: {Carrera}";
    }
}

En este ejemplo, la clase Estudiante no reemplaza completamente el comportamiento de ObtenerInformacion(), sino que lo extiende añadiendo información específica de estudiante.

Sobreescritura vs ocultamiento (new)

Es importante distinguir entre sobreescritura (override) y ocultamiento (new) de métodos:

public class Base
{
    public virtual void Metodo()
    {
        Console.WriteLine("Base.Metodo()");
    }
}

public class Derivada1 : Base
{
    // Sobreescritura - polimorfismo
    public override void Metodo()
    {
        Console.WriteLine("Derivada1.Metodo()");
    }
}

public class Derivada2 : Base
{
    // Ocultamiento - no es polimorfismo
    public new void Metodo()
    {
        Console.WriteLine("Derivada2.Metodo()");
    }
}

La diferencia clave se observa cuando accedemos a estos objetos a través de una referencia del tipo base:

Base b1 = new Derivada1();
Base b2 = new Derivada2();

b1.Metodo(); // Imprime: "Derivada1.Metodo()" - Se usa la versión sobreescrita
b2.Metodo(); // Imprime: "Base.Metodo()" - Se usa la versión de la clase base

Con override, el método que se ejecuta depende del tipo real del objeto (polimorfismo). Con new, el método que se ejecuta depende del tipo de la referencia.

Sobreescritura de propiedades

No solo los métodos pueden ser sobreescritos; las propiedades también pueden marcarse como virtual y sobreescribirse:

public class Producto
{
    public virtual decimal Precio { get; set; }
    
    public virtual decimal CalcularPrecioFinal()
    {
        return Precio * 1.21m; // Precio con IVA
    }
}

public class ProductoImportado : Producto
{
    private decimal _tasaImportacion = 1.10m;
    
    // Sobreescribimos la propiedad
    public override decimal Precio
    {
        get { return base.Precio * _tasaImportacion; }
        set { base.Precio = value; }
    }
    
    // También sobreescribimos el método
    public override decimal CalcularPrecioFinal()
    {
        // Podemos usar la implementación base
        return base.CalcularPrecioFinal();
        
        // O podríamos hacer nuestra propia implementación:
        // return Precio * 1.21m;
    }
}

En este ejemplo, la propiedad Precio se sobreescribe para incluir automáticamente la tasa de importación en el getter.

Sobreescritura de métodos abstractos

Cuando sobreescribimos métodos abstract, la mecánica es similar, pero hay una diferencia conceptual importante: los métodos abstractos deben ser sobreescritos, ya que no tienen implementación en la clase base:

public abstract class Documento
{
    public string Titulo { get; set; }
    
    // Método abstracto - sin implementación
    public abstract void Imprimir();
    
    // Método virtual - con implementación predeterminada
    public virtual string GenerarEncabezado()
    {
        return $"Documento: {Titulo}";
    }
}

public class Factura : Documento
{
    public decimal Total { get; set; }
    
    // Obligatorio implementar métodos abstractos
    public override void Imprimir()
    {
        Console.WriteLine($"Imprimiendo factura: {Titulo}");
        Console.WriteLine($"Total: {Total:C}");
    }
    
    // Opcional sobreescribir métodos virtuales
    public override string GenerarEncabezado()
    {
        return $"FACTURA: {Titulo} - TOTAL: {Total:C}";
    }
}

Cadenas de sobreescritura

Un aspecto interesante de la sobreescritura es que puede formar cadenas a través de múltiples niveles de herencia:

public class A
{
    public virtual void Metodo() { Console.WriteLine("A.Metodo()"); }
}

public class B : A
{
    public override void Metodo() { Console.WriteLine("B.Metodo()"); }
}

public class C : B
{
    public override void Metodo() { Console.WriteLine("C.Metodo()"); }
}

Cada clase en la jerarquía puede decidir sobreescribir el método o no. Si una clase intermedia no sobreescribe el método, la implementación más cercana en la cadena de herencia se utilizará.

Sellado de métodos sobreescritos

En ocasiones, queremos permitir la sobreescritura hasta cierto punto en la jerarquía, pero evitar que las clases derivadas adicionales modifiquen el comportamiento. Para esto, podemos sellar un método sobreescrito usando la palabra clave sealed:

public class Animal
{
    public virtual string HacerSonido() { return "..."; }
}

public class Perro : Animal
{
    // Sobreescribimos y sellamos el método
    public sealed override string HacerSonido() { return "Guau"; }
}

public class PerroLabrador : Perro
{
    // Error de compilación: no se puede sobreescribir un método sellado
    // public override string HacerSonido() { return "Guau Guau"; }
}

Al marcar un método como sealed override, estamos diciendo: "Este método puede sobreescribir el de la clase base, pero ninguna clase derivada de esta puede sobreescribirlo más".

Ejemplo práctico: sistema de notificaciones

Veamos un ejemplo más completo que muestra cómo la sobreescritura permite crear sistemas extensibles:

public class Notificacion
{
    public string Mensaje { get; set; }
    public string Destinatario { get; set; }
    
    public Notificacion(string mensaje, string destinatario)
    {
        Mensaje = mensaje;
        Destinatario = destinatario;
    }
    
    public virtual bool Validar()
    {
        // Validación básica
        return !string.IsNullOrEmpty(Mensaje) && !string.IsNullOrEmpty(Destinatario);
    }
    
    public virtual void Enviar()
    {
        if (Validar())
        {
            Console.WriteLine($"Enviando notificación genérica a {Destinatario}: {Mensaje}");
        }
        else
        {
            Console.WriteLine("La notificación no es válida");
        }
    }
}

public class NotificacionEmail : Notificacion
{
    public string Asunto { get; set; }
    
    public NotificacionEmail(string mensaje, string email, string asunto) 
        : base(mensaje, email)
    {
        Asunto = asunto;
    }
    
    public override bool Validar()
    {
        // Primero validamos lo básico usando la implementación base
        if (!base.Validar())
            return false;
        
        // Luego agregamos validación específica para email
        return Destinatario.Contains("@") && !string.IsNullOrEmpty(Asunto);
    }
    
    public override void Enviar()
    {
        if (Validar())
        {
            Console.WriteLine($"Enviando email a {Destinatario}");
            Console.WriteLine($"Asunto: {Asunto}");
            Console.WriteLine($"Mensaje: {Mensaje}");
        }
        else
        {
            Console.WriteLine("El email no es válido");
        }
    }
}

public class NotificacionSMS : Notificacion
{
    public NotificacionSMS(string mensaje, string telefono) 
        : base(mensaje, telefono)
    {
    }
    
    public override bool Validar()
    {
        if (!base.Validar())
            return false;
        
        // Validamos que el destinatario sea un número de teléfono
        return Destinatario.All(char.IsDigit) && Mensaje.Length <= 160;
    }
    
    public override void Enviar()
    {
        if (Validar())
        {
            Console.WriteLine($"Enviando SMS al {Destinatario}: {Mensaje}");
        }
        else
        {
            Console.WriteLine("El SMS no es válido");
        }
    }
}

Este sistema permite enviar diferentes tipos de notificaciones de manera polimórfica:

List<Notificacion> notificaciones = new List<Notificacion>
{
    new Notificacion("Mensaje genérico", "Usuario"),
    new NotificacionEmail("Por favor confirme su cuenta", "usuario@ejemplo.com", "Confirmación de cuenta"),
    new NotificacionSMS("Su código de verificación es 1234", "555123456")
};

foreach (var notificacion in notificaciones)
{
    // Polimorfismo en acción - cada tipo ejecuta su propia versión
    notificacion.Enviar();
    Console.WriteLine();
}

Consideraciones de diseño

Al trabajar con métodos override, es importante tener en cuenta algunas consideraciones de diseño:

  • Principio de sustitución de Liskov: Una clase derivada debe poder sustituir a su clase base sin alterar el comportamiento esperado del programa.
  • Coherencia semántica: La implementación sobreescrita debe mantener la semántica general del método base, aunque con comportamiento específico.
  • Evitar efectos secundarios inesperados: Especialmente cuando se llama a base.Metodo(), asegúrate de que no haya efectos secundarios no deseados.
  • Documentación clara: Documenta claramente cómo tu implementación sobreescrita difiere de la implementación base.

Errores comunes al sobreescribir métodos

Algunos errores frecuentes al trabajar con override incluyen:

  • Olvidar la palabra clave override, lo que resulta en ocultamiento (new) en lugar de sobreescritura
  • Intentar sobreescribir un método que no está marcado como virtual o abstract
  • Cambiar la firma del método (parámetros o tipo de retorno)
  • No mantener la compatibilidad semántica con el método base
public class EjemploError : Base
{
    // Error: falta override, esto es ocultamiento, no sobreescritura
    public void MetodoVirtual() { }
    
    // Error: el método base no es virtual
    public override void MetodoNoVirtual() { }
    
    // Error: firma diferente (parámetro adicional)
    public override void OtroMetodoVirtual(int parametroExtra) { }
}

La sobreescritura de métodos es una herramienta fundamental para implementar el polimorfismo en C#, permitiendo que las clases derivadas proporcionen implementaciones específicas mientras mantienen una interfaz común a través de la jerarquía de clases.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende CSharp online

Ejercicios de esta lección Métodos virtuales y sobrecarga

Evalúa tus conocimientos de esta lección Métodos virtuales y sobrecarga con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de CSharp

Accede a todas las lecciones de CSharp y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A C#

Introducción Y Entorno

Creación De Proyecto C#

Introducción Y Entorno

Variables Y Constantes

Sintaxis

Tipos De Datos

Sintaxis

Operadores

Sintaxis

Control De Flujo

Sintaxis

Funciones

Sintaxis

Estructuras De Control Iterativo

Sintaxis

Interpolación De Strings

Sintaxis

Estructuras De Control Condicional

Sintaxis

Manejo De Valores Nulos

Sintaxis

Clases Y Encapsulación

Programación Orientada A Objetos

Objetos

Programación Orientada A Objetos

Constructores Y Destructores

Programación Orientada A Objetos

Herencia

Programación Orientada A Objetos

Polimorfismo

Programación Orientada A Objetos

Genéricos

Programación Orientada A Objetos

Métodos Virtuales Y Sobrecarga

Programación Orientada A Objetos

Clases Abstractas

Programación Orientada A Objetos

Interfaces

Programación Orientada A Objetos

Propiedades Y Encapsulación

Programación Orientada A Objetos

Métodos De Extensión

Programación Orientada A Objetos

Clases Y Objetos

Programación Orientada A Objetos

Clases Parciales

Programación Orientada A Objetos

Miembros Estáticos

Programación Orientada A Objetos

Tuplas Y Tipos Anónimos

Programación Orientada A Objetos

Arrays Y Listas

Colecciones Y Linq

Diccionarios

Colecciones Y Linq

Conjuntos, Colas Y Pilas

Colecciones Y Linq

Uso De Consultas Linq

Colecciones Y Linq

Linq Avanzado

Colecciones Y Linq

Colas Y Pilas

Colecciones Y Linq

Conjuntos

Colecciones Y Linq

Linq Básico

Colecciones Y Linq

Delegados Funcionales

Programación Funcional

Records

Programación Funcional

Expresiones Lambda

Programación Funcional

Linq Funcional

Programación Funcional

Fundamentos De La Programación Funcional

Programación Funcional

Pattern Matching

Programación Funcional

Testing Unitario Con Xunit

Testing

Excepciones

Excepciones

Delegados

Programación Asíncrona

Eventos

Programación Asíncrona

Lambdas

Programación Asíncrona

Uso De Async Y Await

Programación Asíncrona

Tareas

Programación Asíncrona

Accede GRATIS a CSharp y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la diferencia entre sobrecarga y sobreescritura de métodos en C#.
  • Aprender a declarar y utilizar métodos virtuales para habilitar el polimorfismo.
  • Saber cómo usar la palabra clave override para proporcionar implementaciones específicas en clases derivadas.
  • Identificar las reglas y limitaciones de los métodos virtuales y sobreescritos.
  • Aplicar conceptos de polimorfismo mediante ejemplos prácticos y jerarquías de clases.