Polimorfismo (Guía de programación de C#)

Actualización: Julio de 2008

A menudo se hace referencia al polimorfismo como el tercer pilar de la programación orientada a objetos, tras la encapsulación y la herencia. El término polimorfismo es una palabra griega que significa "con muchas formas" y tiene dos aspectos que lo caracterizan:

  1. En tiempo de ejecución, los objetos de una clase derivada se pueden tratar como objetos de una clase base en lugares como parámetros de método y colecciones o matrices. Cuando esto sucede, el tipo declarado del objeto ya no es idéntico a su tipo en tiempo de ejecución.

  2. Las clases base pueden definir e implementar métodosvirtuales y las clases derivadas pueden invalidarlos, lo que significa que proporcionan su propia definición e implementación. En tiempo de ejecución, cuando el código de cliente llama al método, CLR busca el tipo en tiempo de ejecución del objeto e invoca esta invalidación del método virtual. Así, en el código fuente puede llamar a un método de una clase base y provocar la ejecución de la versión de clase derivada del método.

Los métodos virtuales permiten trabajar con grupos de objetos relacionados de una manera uniforme. Por ejemplo, suponga que dispone de una aplicación de dibujo que permite a un usuario crear varios tipos de formas en una superficie de dibujo. En tiempo de compilación no conoce los tipos específicos de formas que creará el usuario. Sin embargo, la aplicación tiene que realizar el seguimiento de los diferentes tipos de formas que se crean y tiene que actualizarlos como respuesta a las acciones del mouse del usuario. Puede utilizar el polimorfismo para resolver este problema en dos pasos básicos:

  1. Cree una jerarquía de clases en la que cada clase de forma específica derive de una clase base común.

  2. Utilice un método virtual para invocar el método adecuado de una clase derivada a través de una única llamada al método de clase base.

En primer lugar, cree una clase base llamada Shape y clases derivadas como Rectangle, Circle y Triangle. Incluya en la clase Shape un método virtual llamado Draw e invalídelo en cada clase derivada para dibujar la forma determinada que representa la clase. Cree un objeto List<Shape> y agregue elementos Circle, Triangle y Rectangle a él. Para actualizar la superficie de dibujo, utilice un bucle foreach para recorrer en iteración la lista y llamar al método Draw en cada objeto Shape de la lista. Aunque cada objeto de la lista tiene un tipo declarado de Shape, es el tipo en tiempo de ejecución (la versión invalidada del método en cada clase derivada) el que se invocará.

public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Polymorphism at work #1: a Rectangle, Triangle and Circle
        // can all be used whereever a Shape is expected. No cast is
        // required because an implicit conversion exists from a derived 
        // class to its base class.
        System.Collections.Generic.List<Shape> shapes = new System.Collections.Generic.List<Shape>();
        shapes.Add(new Rectangle());
        shapes.Add(new Triangle());
        shapes.Add(new Circle());

        // Polymorphism at work #2: the virtual method Draw is
        // invoked on each of the derived classes, not the base class.
        foreach (Shape s in shapes)
        {
            s.Draw();
        }

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

}

/* Output:
    Drawing a rectangle
    Performing base class drawing tasks
    Drawing a triangle
    Performing base class drawing tasks
    Drawing a circle
    Performing base class drawing tasks
 */

En C#, cada tipo es polimórfico porque todos los tipos, incluidos los tipos definidos por el usuario, heredan de Object.

Información general sobre el polimorfismo

Miembros virtuales

Cuando una clase derivada hereda de una clase base, obtiene todos los métodos, campos, propiedades y eventos de la clase base. El diseñador de la clase derivada puede elegir si

  • invalida los miembros virtuales de la clase base

  • hereda el método de clase base más parecido sin invalidarlo

  • define una nueva implementación no virtual de los miembros que ocultan las implementaciones de la clase base

Una clase derivada solo puede invalidar un miembro de la clase base si éste se declara como virtual o abstracto. El miembro derivado debe utilizar la palabra clave override para indicar explícitamente que el método va a participar en la invocación virtual. El código siguiente proporciona un ejemplo:

public class BaseClass
{
    public virtual void DoWork() { }
    public virtual int WorkProperty
    {
        get { return 0; }
    }
}
public class DerivedClass : BaseClass
{
    public override void DoWork() { }
    public override int WorkProperty
    {
        get { return 0; }
    }
}

Los campos no pueden ser virtuales. Solo los métodos, propiedades, eventos e indizadores pueden serlo. Cuando una clase derivada reemplaza un miembro virtual, se llama a ese miembro aunque se tenga acceso a una instancia de esa clase como instancia de la clase base. El código siguiente proporciona un ejemplo:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Also calls the new method.

Los métodos y propiedades virtuales permiten a las clases derivadas extender una clase base sin necesidad de utilizar la implementación de clase base de un método. Para obtener más información, vea Control de versiones con las palabras clave Override y New (Guía de programación de C#). Una interfaz proporciona otra manera de definir un método o un conjunto de métodos cuya implementación se deja a las clases derivadas. Para obtener más información, vea Interfaces (Guía de programación de C#) y Elegir entre clases e interfaces.

Ocultar miembros de la clase base con nuevos miembros

Si desea que el miembro derivado tenga el mismo nombre que un miembro de una clase base, pero no desea que participe en la invocación virtual, puede utilizar la palabra clave new. La palabra clave new se coloca antes del tipo de valor devuelto de un miembro de clase que se reemplaza. El código siguiente proporciona un ejemplo:

public class BaseClass
{
    public void DoWork() { WorkField++; }
    public int WorkField;
    public int WorkProperty
    {
        get { return 0; }
    }
}

public class DerivedClass : BaseClass
{
    public new void DoWork() { WorkField++; }
    public new int WorkField;
    public new int WorkProperty
    {
        get { return 0; }
    }
}

Aún así, se puede tener acceso a los miembros de clase base ocultos desde el código de cliente si se la instancia de la clase derivada se convierte a una instancia de la clase base. Por ejemplo:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

Evitar que las clases derivadas invaliden los miembros virtuales

Los miembros virtuales siguen siendo virtuales de forma indefinida, independientemente de cuántas clases se hayan declarado entre el miembro virtual y la clase que originalmente lo declaró. Si la clase A declara un miembro virtual, la clase B deriva de A y la clase C deriva de B, la clase C hereda el miembro virtual y tiene la opción de reemplazarlo, independientemente de si la clase B declaró la sustitución de ese miembro. El código siguiente proporciona un ejemplo:

public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}

Una clase derivada puede detener la herencia virtual si la invalidación se declara como sealed. Para ello es necesario incluir la palabra clave sealed antes de la palabra clave override en la declaración del miembro de clase. El código siguiente proporciona un ejemplo:

public class C : B
{
    public sealed override void DoWork() { }
}

En el ejemplo anterior, el método DoWork ya no es virtual para ninguna clase derivada de C. Todavía es virtual para instancias de C, aunque se conviertan al tipo B o A. Los métodos sellados se pueden reemplazar por clases derivadas mediante la palabra clave new, como se muestra en el ejemplo siguiente:

public class D : C
{
    public new void DoWork() { }
}

En este caso, si se llama a DoWork en D mediante una variable de tipo D, se llama al nuevo DoWork. Si se utiliza una variable de tipo C, B o A para tener acceso a una instancia de D, una llamada a DoWork seguirá las reglas de la herencia virtual y enrutará esas llamadas a la implementación de DoWork en la clase C.

Obtener acceso a miembros virtuales de la clase base desde clases derivadas

Una clase derivada que ha reemplazado un método o propiedad todavía puede tener acceso al método o propiedad de la clase base utilizando la palabra clave base. El código siguiente proporciona un ejemplo:

public class Base
{
    public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
    public override void DoWork()
    {
        //Perform Derived's work here
        //...
        // Call DoWork on base class
        base.DoWork();
    }
}

Para obtener más información, vea base.

Nota:

Se recomienda que los miembros virtuales utilicen la palabra clave base para llamar a la implementación de la clase base de ese miembro en su propia implementación. Al permitir que se produzca el comportamiento de la clase base, se permite que la clase derivada se concentre en implementar el comportamiento específico de la clase derivada. Si no se llama a la implementación de la clase base, la compatibilidad del comportamiento de la clase derivada con el comportamiento de la clase base depende de la clase derivada.

En esta sección

Vea también

Conceptos

Guía de programación de C#

Guía de programación de C#

Referencia

Herencia (Guía de programación de C#)

Clases y miembros de clase abstractos y sellados (Guía de programación de C#)

Métodos (Guía de programación de C#)

Eventos (Guía de programación de C#)

Propiedades (Guía de programación de C#)

Indizadores (Guía de programación de C#)

Tipos (Guía de programación de C#)

Historial de cambios

Fecha

Historial

Motivo

Julio de 2008

Se ha agregado contenido y nuevos ejemplos.

Mejora de la información.