Overname : afleiden van typen om meer gespecialiseerd gedrag te creëren

Overname, samen met inkapseling en polymorfisme, is een van de drie primaire kenmerken van objectgeoriënteerde programmering. Met overname kunt u nieuwe klassen maken die het gedrag dat in andere klassen is gedefinieerd, opnieuw gebruiken, uitbreiden en wijzigen. De klasse waarvan de leden worden overgenomen, wordt de basisklasse genoemd en de klasse die deze leden over neemt, wordt de afgeleide klasse genoemd. Een afgeleide klasse kan slechts één directe basisklasse hebben. Overname is echter transitief. Als ClassC wordt afgeleid van ClassB, en ClassB is afgeleid van ClassA, neemt de ClassC leden over die zijn opgegeven in ClassB en ClassA.

Notitie

Structs bieden geen ondersteuning voor overname, maar ze kunnen interfaces implementeren.

Conceptueel is een afgeleide klasse een specialisatie van de basisklasse. Als u bijvoorbeeld een basisklasse hebt, hebt u mogelijk een afgeleide klasse Animalmet de naam Mammal en een andere afgeleide klasse met de naam Reptile. A Mammal is een Animal, en een is eenAnimalReptile, maar elke afgeleide klasse vertegenwoordigt verschillende specialisaties van de basisklasse.

Interfacedeclaraties kunnen een standaard implementatie definiëren voor de leden. Deze implementaties worden overgenomen door afgeleide interfaces en door klassen die deze interfaces implementeren. Zie het artikel over interfaces voor meer informatie over standaardinterfacemethoden.

Wanneer u een klasse definieert die moet worden afgeleid van een andere klasse, krijgt de afgeleide klasse impliciet alle leden van de basisklasse, met uitzondering van de constructors en finalizers. De afgeleide klasse hergebruikt de code in de basisklasse zonder deze opnieuw te hoeven in te vullen. U kunt meer leden toevoegen in de afgeleide klasse. De afgeleide klasse breidt de functionaliteit van de basisklasse uit.

In de volgende afbeelding ziet u een klasse WorkItem die een werkitem in een bepaald bedrijfsproces vertegenwoordigt. Net als bij alle klassen is het afgeleid van System.Object en neemt het alle bijbehorende methoden over. WorkItem voegt zes leden van zichzelf toe. Deze leden omvatten een constructor, omdat constructors niet worden overgenomen. Klasse ChangeRequest neemt over van WorkItem en vertegenwoordigt een bepaald soort werkitem. ChangeRequest voegt nog twee leden toe aan de leden van WorkItem en van Object. Het moet een eigen constructor toevoegen en voegt ook toe originalItemID. Met de eigenschap originalItemID kan het ChangeRequest exemplaar worden gekoppeld aan het origineel WorkItem waarop de wijzigingsaanvraag van toepassing is.

Diagram that shows class inheritance

In het volgende voorbeeld ziet u hoe de klasserelaties die in de vorige afbeelding zijn gedemonstreerd, worden uitgedrukt in C#. In het voorbeeld ziet u ook hoe WorkItem de virtuele methode Object.ToStringwordt overschreven en hoe de ChangeRequest klasse de WorkItem implementatie van de methode overschrijft. Het eerste blok definieert de klassen:

// 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;
    }
}

In dit volgende blok ziet u hoe u de basis- en afgeleide klassen gebruikt:

// 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
*/

Abstracte en virtuele methoden

Wanneer een basisklasse een methode declareert als virtual, kan override een afgeleide klasse de methode met een eigen implementatie. Als een basisklasse een lid declareert als abstract, moet die methode worden overschreven in een niet-abstracte klasse die rechtstreeks van die klasse overdraft. Als een afgeleide klasse zichzelf abstract is, neemt deze abstracte leden over zonder ze te implementeren. Abstracte en virtuele leden vormen de basis voor polymorfisme. Dit is het tweede primaire kenmerk van objectgeoriënteerd programmeren. Zie Polymorfisme voor meer informatie.

Abstracte basisklassen

U kunt een klasse als abstract declareren als u directe instantiëring wilt voorkomen met behulp van de nieuwe operator. Een abstracte klasse kan alleen worden gebruikt als er een nieuwe klasse uit wordt afgeleid. Een abstracte klasse kan een of meer methodehandtekeningen bevatten die zelf worden gedeclareerd als abstract. Deze handtekeningen geven de parameters en retourwaarde op, maar hebben geen implementatie (hoofdtekst van de methode). Een abstracte klasse hoeft geen abstracte leden te bevatten; Als een klasse echter wel een abstract lid bevat, moet de klasse zelf worden gedeclareerd als abstract. Afgeleide klassen die niet zelf abstract zijn, moeten de implementatie bieden voor abstracte methoden van een abstracte basisklasse.

Interfaces

Een interface is een referentietype dat een set leden definieert. Alle klassen en structs die die interface implementeren, moeten die set leden implementeren. Een interface kan een standaard implementatie definiëren voor een of al deze leden. Een klasse kan meerdere interfaces implementeren, ook al kan deze slechts worden afgeleid van één directe basisklasse.

Interfaces worden gebruikt om specifieke mogelijkheden te definiëren voor klassen die niet noodzakelijkerwijs een 'is a'-relatie hebben. De System.IEquatable<T> interface kan bijvoorbeeld door elke klasse of struct worden geïmplementeerd om te bepalen of twee objecten van het type gelijkwaardig zijn (maar het type definieert gelijkwaardigheid). IEquatable<T> impliceert niet hetzelfde soort 'is een' relatie die bestaat tussen een basisklasse en een afgeleide klasse (bijvoorbeeld een is een MammalAnimal). Zie Interfaces voor meer informatie.

Verdere afleiding voorkomen

Een klasse kan voorkomen dat andere klassen deze overnemen, of van een van de leden, door zichzelf of het lid te declareren als sealed.

Afgeleide klasse verbergen van basisklasseleden

Een afgeleide klasse kan basisklasseleden verbergen door leden met dezelfde naam en handtekening te declareren. De new wijzigingsfunctie kan worden gebruikt om expliciet aan te geven dat het lid niet bedoeld is om een onderdrukking van het basislid te zijn. Het gebruik van new is niet vereist, maar er wordt een compilerwaarschuwing gegenereerd als new deze niet wordt gebruikt. Zie Versiebeheer met de onderdrukking en nieuwe trefwoorden en weten wanneer u onderdrukking en nieuwe trefwoorden wilt gebruiken voor meer informatie.