Herencia: deriva tipos para crear un comportamiento más especializado

La herencia, junto con la encapsulación y el polimorfismo, es una de las tres características principales de la programación orientada a objetos. La herencia permite crear clases que reutilizan, extienden y modifican el comportamiento definido en otras clases. La clase cuyos miembros se heredan se denomina clase base y la clase que hereda esos miembros se denomina clase derivada. Una clase derivada solo puede tener una clase base directa, pero la herencia es transitiva. Si ClassC se deriva de ClassB y ClassB se deriva de ClassA, ClassC hereda los miembros declarados en ClassB y ClassA.

Nota

Los structs no admiten la herencia, pero pueden implementar interfaces.

Conceptualmente, una clase derivada es una especialización de la clase base. Por ejemplo, si tiene una clase base Animal, podría tener una clase derivada denominada Mammal y otra clase derivada denominada Reptile. Mammal es Animal y Reptile también es Animal, pero cada clase derivada representa especializaciones diferentes de la clase base.

Las declaraciones de interfaz pueden definir una implementación predeterminada de sus miembros. Estas implementaciones las heredan las interfaces derivadas y las clases que implementan esas interfaces. Para más información sobre los métodos de interfaz predeterminados, consulte el artículo sobre interfaces.

Cuando se define una clase para que derive de otra clase, la clase derivada obtiene implícitamente todos los miembros de la clase base, salvo sus constructores y sus finalizadores. La clase derivada reutiliza el código de la clase base sin tener que volver a implementarlo. Puede agregar más miembros en la clase derivada. La clase derivada amplía la funcionalidad de la clase base.

En la ilustración siguiente se muestra una clase WorkItem que representa un elemento de trabajo de un proceso empresarial. Como con todas las clases, se deriva de System.Object y hereda todos sus métodos. WorkItem agrega seis miembros propios. Estos miembros incluyen un constructor, porque los constructores no se heredan. La clase ChangeRequest hereda de WorkItem y representa un tipo concreto de elemento de trabajo. ChangeRequest agrega dos miembros más a los miembros que hereda de WorkItem y de Object. Debe agregar su propio constructor y además agrega originalItemID. La propiedad originalItemID permite que la instancia ChangeRequest se asocie con el WorkItem original al que se aplica la solicitud de cambio.

Diagram that shows class inheritance

En el ejemplo siguiente se muestra cómo se expresan en C# las relaciones de clase de la ilustración anterior. En el ejemplo también se muestra cómo WorkItem reemplaza el método virtual Object.ToString y cómo la clase ChangeRequest hereda la implementación WorkItem del método. En el primer bloque se definen las clases:

// WorkItem implicitly inherits from the Object class.
public class WorkItem
{
    // Static field currentID stores the job ID of the last WorkItem that
    // has been created.
    private static int currentID;

    //Properties.
    protected int ID { get; set; }
    protected string Title { get; set; }
    protected string Description { get; set; }
    protected TimeSpan jobLength { get; set; }

    // Default constructor. If a derived class does not invoke a base-
    // class constructor explicitly, the default constructor is called
    // implicitly.
    public WorkItem()
    {
        ID = 0;
        Title = "Default title";
        Description = "Default description.";
        jobLength = new TimeSpan();
    }

    // Instance constructor that has three parameters.
    public WorkItem(string title, string desc, TimeSpan joblen)
    {
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = joblen;
    }

    // Static constructor to initialize the static member, currentID. This
    // constructor is called one time, automatically, before any instance
    // of WorkItem or ChangeRequest is created, or currentID is referenced.
    static WorkItem() => currentID = 0;

    // currentID is a static field. It is incremented each time a new
    // instance of WorkItem is created.
    protected int GetNextID() => ++currentID;

    // Method Update enables you to update the title and job length of an
    // existing WorkItem object.
    public void Update(string title, TimeSpan joblen)
    {
        this.Title = title;
        this.jobLength = joblen;
    }

    // Virtual method override of the ToString method that is inherited
    // from System.Object.
    public override string ToString() =>
        $"{this.ID} - {this.Title}";
}

// ChangeRequest derives from WorkItem and adds a property (originalItemID)
// and two constructors.
public class ChangeRequest : WorkItem
{
    protected int originalItemID { get; set; }

    // Constructors. Because neither constructor calls a base-class
    // constructor explicitly, the default constructor in the base class
    // is called implicitly. The base class must contain a default
    // constructor.

    // Default constructor for the derived class.
    public ChangeRequest() { }

    // Instance constructor that has four parameters.
    public ChangeRequest(string title, string desc, TimeSpan jobLen,
                         int originalID)
    {
        // The following properties and the GetNexID method are inherited
        // from WorkItem.
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = jobLen;

        // Property originalItemID is a member of ChangeRequest, but not
        // of WorkItem.
        this.originalItemID = originalID;
    }
}

En el bloque siguiente se muestra cómo usar las clases base y derivadas:

// Create an instance of WorkItem by using the constructor in the
// base class that takes three arguments.
WorkItem item = new WorkItem("Fix Bugs",
                            "Fix all bugs in my code branch",
                            new TimeSpan(3, 4, 0, 0));

// Create an instance of ChangeRequest by using the constructor in
// the derived class that takes four arguments.
ChangeRequest change = new ChangeRequest("Change Base Class Design",
                                        "Add members to the class",
                                        new TimeSpan(4, 0, 0),
                                        1);

// Use the ToString method defined in WorkItem.
Console.WriteLine(item.ToString());

// Use the inherited Update method to change the title of the
// ChangeRequest object.
change.Update("Change the Design of the Base Class",
    new TimeSpan(4, 0, 0));

// ChangeRequest inherits WorkItem's override of ToString.
Console.WriteLine(change.ToString());
/* Output:
    1 - Fix Bugs
    2 - Change the Design of the Base Class
*/

Métodos abstractos y virtuales

Cuando una clase base declara un método como virtual, una clase derivada puede aplicar override al método con una implementación propia. Si una clase base declara un miembro como abstract, ese método se debe reemplazar en todas las clases no abstractas que hereden directamente de dicha clase. Si una clase derivada es abstracta, hereda los miembros abstractos sin implementarlos. Los miembros abstractos y virtuales son la base del polimorfismo, que es la segunda característica principal de la programación orientada a objetos. Para obtener más información, vea Polimorfismo .

Clases base abstractas

Puede declarar una clase como abstracta si quiere impedir la creación directa de instancias mediante el operador new. Una clase abstracta solo se puede usar si a partir de ella se deriva una clase nueva. Una clase abstracta puede contener una o más firmas de método que, a su vez, se declaran como abstractas. Estas firmas especifican los parámetros y el valor devuelto, pero no tienen ninguna implementación (cuerpo del método). Una clase abstracta no tiene que contener miembros abstractos, pero si lo hace, la clase se debe declarar como abstracta. Las clases derivadas que no son abstractas deben proporcionar la implementación para todos los métodos abstractos de una clase base abstracta.

Interfaces

Una interfaz es un tipo de referencia que define un conjunto de miembros. Todas las clases y estructuras que implementan esa interfaz deben implementar ese conjunto de miembros. Una interfaz puede definir una implementación predeterminada para todos o ninguno de estos miembros. Una clase puede implementar varias interfaces, aunque solo puede derivar de una única clase base directa.

Las interfaces se usan para definir funciones específicas para clases que no tienen necesariamente una relación "es un/una". Por ejemplo, la interfaz System.IEquatable<T> se puede implementar mediante cualquier clase o estructura para determinar si dos objetos del tipo son equivalentes (pero el tipo define la equivalencia). IEquatable<T> no implica el mismo tipo de relación "es un/una" que existe entre una clase base y una clase derivada (por ejemplo, Mammal es Animal). Para más información, vea Interfaces.

Impedir la derivación adicional

Una clase puede impedir que otras clases hereden de ella, o de cualquiera de sus miembros, si se declara a sí misma o al miembro como sealed.

Clase derivada que oculta miembros de clase base

Una clase derivada puede ocultar miembros de clase base si declara los miembros con el mismo nombre y firma. Se puede usar el modificador new para indicar de forma explícita que el miembro no está diseñado para reemplazar al miembro base. No es necesario usar new, pero se generará una advertencia del compilador si no se usa new. Para obtener más información, vea Control de versiones con las palabras clave Override y New y Saber cuándo usar las palabras clave Override y New.