Udostępnij za pomocą


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ć metodywirtualne, 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; init; }
    public int Y { get; init; }
    public int Height { get; init; }
    public int Width { get; init; }

    // 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.
List<Shape> shapes =
[
    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, protected, private i tak dalej) określają, czy członkowie są dostępni z implementacji klasy pochodnej. Metody wirtualne zapewniają 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ć metodę najbliższej klasy bazowej bez zastępowania jej, zachowując istniejące zachowanie, ale umożliwiając dalszym klasom pochodnym zastąpienie metody.
  • Klasa pochodna może definiować nową, niewirtuacyjną implementację 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 => 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();
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 typem zwracanym członka 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; }
    }
}

Gdy używasz słowa kluczowego new , tworzysz metodę, która ukrywa metodę klasy bazowej, a nie zastępuje ją. Różni się to od metod wirtualnych. W przypadku ukrywania metody wywoływana metoda zależy od typu czasu kompilacji zmiennej, a nie typu czasu wykonywania obiektu.

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();
B.DoWork();  // Calls the new method.

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

W tym przykładzie obie zmienne odwołują się do tego samego wystąpienia obiektu, ale wywoływana metoda zależy od zadeklarowanego typu zmiennej: DerivedClass.DoWork() w przypadku uzyskiwania dostępu za pośrednictwem zmiennej DerivedClass, i BaseClass.DoWork() przy dostępie za pomocą zmiennej BaseClass.

Zapobiegaj zastępowaniu wirtualnych składowych klas pochodnych

Wirtualne elementy pozostają wirtualne, niezależnie od liczby klas zadeklarowanych między elementem wirtualnym a klasą, która pierwotnie go zadeklarowała. Jeśli klasa A deklaruje wirtualną składową, a klasa B pochodzi z A, a klasa C pochodzi z B, klasa C dziedziczy wirtualną składową i może ją zastąpić, niezależnie od tego, czy klasa B zadeklarowała przesłonięcie 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 C, B lub A jest używana do uzyskiwania dostępu do wystąpienia D, wywołanie do DoWork podąża za regułami dziedziczenia wirtualnego, kierując te wywołania do implementacji DoWork w klasie C.

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

Klasa pochodna, która zastępuje lub nadpisuje metodę lub właściwość, nadal może uzyskać dostęp do metody lub właściwości bazowej klasy, używając 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ę, aby wirtualni członkowie używali base do wywoływania implementacji klasy bazowej tego członka 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, do klasy pochodnej należy zachowanie zgodne z zachowaniem klasy bazowej.