Polimorfizm

Polimorfizm jest często określany jako trzeci filar programowania obiektowego po hermetyzacji i dziedziczeniu. Polimorfizm to greckie słowo, które oznacza "wiele kształtów" i ma dwa odrębne aspekty:

  • W czasie wykonywania obiekty klasy pochodnej mogą być traktowane jako obiekty klasy bazowej w miejscach, takich jak parametry metody i kolekcje lub tablice. Gdy wystąpi ten polimorfizm, zadeklarowany typ obiektu nie jest już identyczny z typem czasu wykonywania.
  • Klasy bazowe mogą definiować i implementować metody wirtualne, a klasy pochodne mogą je zastąpić, co oznacza, że zapewniają własną definicję i implementację. W czasie wykonywania, gdy kod klienta wywołuje metodę, CLR wyszukuje typ czasu wykonywania obiektu i wywołuje, który zastępuje metodę wirtualną. W kodzie źródłowym można wywołać metodę w klasie bazowej i spowodować wykonanie wersji klasy pochodnej metody.

Metody wirtualne umożliwiają pracę z grupami powiązanych obiektów w jednolity sposób. Załóżmy na przykład, że masz aplikację do rysowania, która umożliwia użytkownikowi tworzenie różnych rodzajów kształtów na powierzchni rysunku. Nie wiesz w czasie kompilacji, które określone typy kształtów zostaną utworzone przez użytkownika. Jednak aplikacja musi śledzić wszystkie tworzone typy kształtów i musi je zaktualizować w odpowiedzi na akcje myszy użytkownika. Aby rozwiązać ten problem, można użyć polimorfizmu w dwóch podstawowych krokach:

  1. Utwórz hierarchię klas, w której każda konkretna klasa kształtu pochodzi ze wspólnej klasy bazowej.
  2. Użyj metody wirtualnej, aby wywołać odpowiednią metodę dla dowolnej klasy pochodnej za pomocą pojedynczego wywołania metody klasy bazowej.

Najpierw utwórz klasę bazową o nazwie Shapei klasy pochodne, takie jak Rectangle, Circlei Triangle. Nadaj Shape klasie metodę wirtualną o nazwie Drawi przesłoń ją w każdej klasie pochodnej, aby narysować konkretny kształt reprezentowany przez klasę. List<Shape> Utwórz obiekt i dodaj Circledo niego element , Trianglei Rectangle .

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");
    }
}

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

Aby zaktualizować powierzchnię rysunku, użyj pętli foreach , aby iterować listę i wywołać Draw metodę na każdym Shape obiekcie na liście. Mimo że każdy obiekt na liście ma zadeklarowany typ , jest to typ Shapeczasu wykonywania (zastąpiona wersja metody w każdej klasie pochodnej), która zostanie wywołana.

// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used wherever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
{
    new Rectangle(),
    new Triangle(),
    new Circle()
};

// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
{
    shape.Draw();
}
/* 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
*/

W języku C# każdy typ jest polimorficzny, ponieważ wszystkie typy, w tym typy zdefiniowane przez użytkownika, dziedziczą z Objectklasy .

Omówienie polimorfizmu

Wirtualne składowe

Gdy klasa pochodna dziedziczy z klasy bazowej, zawiera wszystkie elementy członkowskie klasy bazowej. Wszystkie zachowanie zadeklarowane w klasie bazowej jest częścią klasy pochodnej. Dzięki temu obiekty klasy pochodnej mogą być traktowane jako obiekty klasy bazowej. Modyfikatory dostępu (public, itdprivate.) określają, protectedczy te elementy członkowskie są dostępne z implementacji klasy pochodnej. Metody wirtualne umożliwiają projektantowi różne opcje zachowania klasy pochodnej:

  • Klasa pochodna może zastąpić wirtualne elementy członkowskie w klasie bazowej, definiując nowe zachowanie.
  • Klasa pochodna może dziedziczyć najbliższą metodę klasy bazowej bez zastępowania jej, zachowując istniejące zachowanie, ale umożliwiając dalsze klasy pochodne zastąpić metodę.
  • Klasa pochodna może definiować nową implementację niewirtuacyjną tych elementów członkowskich, które ukrywają implementacje klas bazowych.

Klasa pochodna może zastąpić składową klasy bazowej tylko wtedy, gdy składowa klasy bazowej jest zadeklarowana jako wirtualna lub abstrakcyjna. Pochodny element członkowski musi użyć słowa kluczowego zastąpienia , aby jawnie wskazać, że metoda ma uczestniczyć w wywołaniu wirtualnym. Poniższy kod zawiera przykład:

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

Pola nie mogą być wirtualne; tylko metody, właściwości, zdarzenia i indeksatory mogą być wirtualne. Gdy klasa pochodna zastępuje wirtualny element członkowski, ten element członkowski jest wywoływany nawet wtedy, gdy wystąpienie tej klasy jest dostępne jako wystąpienie klasy bazowej. Poniższy kod zawiera przykład:

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

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

Metody wirtualne i właściwości umożliwiają klasom pochodnym rozszerzanie klasy bazowej bez konieczności używania implementacji klasy bazowej metody. Aby uzyskać więcej informacji, zobacz Przechowywanie wersji za pomocą przesłonięć i nowych słów kluczowych. Interfejs zapewnia inny sposób definiowania metody lub zestawu metod, których implementacja jest pozostawiona do klas pochodnych.

Ukrywanie składowych klasy bazowej przy użyciu nowych elementów członkowskich

Jeśli chcesz, aby klasa pochodna miała składową o takiej samej nazwie jak składowa w klasie bazowej, możesz użyć nowego słowa kluczowego, aby ukryć składową klasy bazowej. Słowo new kluczowe jest umieszczane przed zwracanym typem elementu członkowskiego klasy, który jest zastępowany. Poniższy kod zawiera przykład:

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

Dostęp do ukrytych składowych klasy bazowej można uzyskać z kodu klienta przez rzutowanie wystąpienia klasy pochodnej na wystąpienie klasy bazowej. Na przykład:

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

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

Zapobiegaj zastępowaniu wirtualnych składowych klas pochodnych

Wirtualne elementy członkowskie pozostają wirtualne, niezależnie od liczby klas zadeklarowanych między elementem wirtualnym a klasą, która pierwotnie ją zadeklarowała. Jeśli klasa A deklaruje wirtualną składową, a klasa B pochodzi z klasy , a klasa pochodzi z BAklasy , C dziedziczy wirtualną składową i może ją zastąpić, niezależnie od tego, czy klasa BC zadeklarowała zastąpienie dla tego elementu członkowskiego. Poniższy kod zawiera przykład:

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

Klasa pochodna może zatrzymać dziedziczenie wirtualne, deklarując przesłonięcia jako zapieczętowane. Zatrzymanie dziedziczenia wymaga wprowadzenia słowa kluczowego sealedoverride przed słowem kluczowym w deklaracji składowej klasy. Poniższy kod zawiera przykład:

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

W poprzednim przykładzie metoda DoWork nie jest już wirtualna dla żadnej klasy pochodzącej z Cklasy . Jest ona nadal wirtualna dla wystąpień Cprogramu , nawet jeśli są rzutowania do typu B lub typu A. Metody zapieczętowane można zastąpić klasami pochodnymi przy użyciu słowa kluczowego new , jak pokazano w poniższym przykładzie:

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

W takim przypadku, jeśli DoWork jest wywoływany przy D użyciu zmiennej typu D, nowa jest wywoływana DoWork . Jeśli zmienna typu , lub jest używana do uzyskiwania dostępu do wystąpienia Dklasy , wywołanie DoWork metody do będzie zgodne z regułami dziedziczenia wirtualnego, rozsyłanie tych wywołań do implementacji DoWork klasy C.ABC

Uzyskiwanie dostępu do wirtualnych składowych klasy bazowej z klas pochodnych

Klasa pochodna, która zastąpiła lub zastąpiła metodę lub właściwość, nadal może uzyskać dostęp do metody lub właściwości w klasie bazowej przy użyciu słowa kluczowego base . Poniższy kod zawiera przykład:

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();
    }
}

Aby uzyskać więcej informacji, zobacz base.

Uwaga

Zaleca się używanie base wirtualnych elementów członkowskich do wywoływania implementacji klasy bazowej tego elementu członkowskiego we własnej implementacji. Umożliwienie zachowania klasy bazowej umożliwia klasom pochodnym skoncentrowanie się na implementowaniu zachowania specyficznego dla klasy pochodnej. Jeśli implementacja klasy bazowej nie jest wywoływana, należy do klasy pochodnej, aby ich zachowanie było zgodne z zachowaniem klasy bazowej.