Szukaj…


Składnia

  • klasa DerivedClass: BaseClass
  • klasa DerivedClass: BaseClass, IExampleInterface
  • klasa DerivedClass: BaseClass, IExampleInterface, IAnotherInterface

Uwagi

Klasy mogą dziedziczyć bezpośrednio tylko z jednej klasy, ale (zamiast lub w tym samym czasie) mogą implementować jeden lub więcej interfejsów.

Struktury mogą implementować interfejsy, ale nie mogą jawnie dziedziczyć z dowolnego typu. System.ValueType dziedziczą po System.ValueType , który z kolei dziedziczy bezpośrednio po System.Object .

Klasy statyczne nie mogą implementować interfejsów.

Dziedziczenie z klasy podstawowej

Aby uniknąć powielania kodu, zdefiniuj wspólne metody i atrybuty w klasie ogólnej jako podstawę:

public class Animal 
{
    public string Name { get; set; }
    // Methods and attributes common to all animals
    public void Eat(Object dinner)
    {
        // ...
    }
    public void Stare()
    {
        // ...
    }
    public void Roll()
    {
        // ...
    }
}

Teraz, gdy masz klasę, która ogólnie reprezentuje Animal , możesz zdefiniować klasę, która opisuje osobliwości określonych zwierząt:

public class Cat : Animal
{
    public Cat() 
    {
        Name = "Cat";
    }
    // Methods for scratching furniture and ignoring owner
    public void Scratch(Object furniture)
    {
        // ...
    }
}

Klasa Cat uzyskuje dostęp nie tylko do metod opisanych wyraźnie w swojej definicji, ale także do wszystkich metod zdefiniowanych w ogólnej klasie podstawowej Animal . Każde zwierzę (bez względu na to, czy był to kot), mogło jeść, gapić się lub toczyć. Zwierzę nie byłoby jednak w stanie Drapać, chyba że byłoby to również Kot. Następnie możesz zdefiniować inne klasy opisujące inne zwierzęta. (Na przykład Gopher z metodą niszczenia ogrodów kwiatowych i Lenistwo bez żadnych dodatkowych metod.)

Dziedziczenie z klasy i implementacja interfejsu

public class Animal 
{
    public string Name { get; set; }
}

public interface INoiseMaker
{
    string MakeNoise();
}

//Note that in C#, the base class name must come before the interface names
public class Cat : Animal, INoiseMaker
{
    public Cat() 
    {
        Name = "Cat";
    }

    public string MakeNoise()
    {
        return "Nyan";
    }
}

Dziedziczenie z klasy i implementowanie wielu interfejsów

public class LivingBeing
{
    string Name { get; set; }
}

public interface IAnimal 
{
    bool HasHair { get; set; }
}

public interface INoiseMaker
{
    string MakeNoise();
}

//Note that in C#, the base class name must come before the interface names
public class Cat : LivingBeing, IAnimal, INoiseMaker
{
    public Cat() 
    {
        Name = "Cat";
        HasHair = true;
    }

    public bool HasHair { get; set; }

    public string Name { get; set; }

    public string MakeNoise()
    {
        return "Nyan";
    }
}

Testowanie i nawigacja dziedziczenia

interface BaseInterface {}
class BaseClass : BaseInterface {}

interface DerivedInterface {}
class DerivedClass : BaseClass, DerivedInterface {}

var baseInterfaceType = typeof(BaseInterface);
var derivedInterfaceType = typeof(DerivedInterface);
var baseType = typeof(BaseClass);
var derivedType = typeof(DerivedClass);

var baseInstance = new BaseClass();
var derivedInstance = new DerivedClass();  

Console.WriteLine(derivedInstance is DerivedClass); //True
Console.WriteLine(derivedInstance is DerivedInterface); //True
Console.WriteLine(derivedInstance is BaseClass); //True
Console.WriteLine(derivedInstance is BaseInterface); //True
Console.WriteLine(derivedInstance is object); //True

Console.WriteLine(derivedType.BaseType.Name);  //BaseClass
Console.WriteLine(baseType.BaseType.Name);  //Object
Console.WriteLine(typeof(object).BaseType);  //null

Console.WriteLine(baseType.IsInstanceOfType(derivedInstance));  //True
Console.WriteLine(derivedType.IsInstanceOfType(baseInstance));  //False

Console.WriteLine(
    string.Join(",", 
    derivedType.GetInterfaces().Select(t => t.Name).ToArray()));
//BaseInterface,DerivedInterface
    
Console.WriteLine(baseInterfaceType.IsAssignableFrom(derivedType)); //True
Console.WriteLine(derivedInterfaceType.IsAssignableFrom(derivedType)); //True
Console.WriteLine(derivedInterfaceType.IsAssignableFrom(baseType)); //False

Rozszerzanie abstrakcyjnej klasy bazowej

W przeciwieństwie do interfejsów, które można określić jako kontrakty implementacyjne, klasy abstrakcyjne działają jak kontrakty rozszerzające.

Nie można utworzyć instancji klasy abstrakcyjnej, należy ją rozszerzyć, a następnie utworzyć instancję klasy wynikowej (lub klasy pochodnej).

Klasy abstrakcyjne służą do zapewnienia ogólnych implementacji

public abstract class Car
{
    public void HonkHorn() {
        // Implementation of horn being honked
    }
}

public class Mustang : Car
{
    // Simply by extending the abstract class Car, the Mustang can HonkHorn()
    // If Car were an interface, the HonkHorn method would need to be included
    // in every class that implemented it.
}

Powyższy przykład pokazuje, jak każda klasa rozszerzająca samochód automatycznie otrzyma metodę HonkHorn wraz z implementacją. Oznacza to, że każdy programista tworzący nowy samochód nie będzie musiał się martwić, jak trąbie klaksonem.

Konstruktory W Podklasie

Kiedy tworzysz podklasę klasy bazowej, możesz zbudować klasę bazową, używając : base po parametrach konstruktora podklasy.

class Instrument
{
    string type;
    bool clean;

    public Instrument (string type, bool clean)
    {
        this.type = type;
        this.clean = clean;
    }
}

class Trumpet : Instrument
{
    bool oiled;

    public Trumpet(string type, bool clean, bool oiled) : base(type, clean)
    {
        this.oiled = oiled;
    }
}

Dziedzictwo. Sekwencja wywołań konstruktorów

Rozważmy, że mamy klasę Animal która ma Dog klasy Dog

class Animal
{
    public Animal()
    {
        Console.WriteLine("In Animal's constructor");
    }
}

class Dog : Animal
{
    public Dog()
    {
        Console.WriteLine("In Dog's constructor");
    }
}

Domyślnie każda klasa domyślnie dziedziczy klasę Object .

To jest to samo co powyższy kod.

class Animal : Object
{
    public Animal()
    {
        Console.WriteLine("In Animal's constructor");
    }
}

Podczas tworzenia instancji klasy Dog domyślny konstruktor klas podstawowych (bez parametrów) zostanie wywołany, jeśli nie będzie wyraźnego wywołania innego konstruktora w klasie nadrzędnej . W naszym przypadku, po pierwsze, będzie nazwane Object's konstruktora, następnie Animal's i na koniec Dog's konstruktora.

public class Program
{
    public static void Main()
    {
        Dog dog = new Dog();
    }
}

Wyjście będzie

W konstruktorze Animal
W konstruktorze Psa

Zobacz demo

Wywołaj jawnie konstruktor rodzica.

W powyższych przykładach nasz konstruktor klasy Dog wywołuje domyślnego konstruktora klasy Animal . Jeśli chcesz, możesz określić, który konstruktor ma zostać wywołany: można wywołać dowolny konstruktor zdefiniowany w klasie nadrzędnej.

Rozważmy, że mamy te dwie klasy.

class Animal
{
    protected string name;

    public Animal()
    {
        Console.WriteLine("Animal's default constructor");
    }    

    public Animal(string name)
    {
        this.name = name;
        Console.WriteLine("Animal's constructor with 1 parameter");
        Console.WriteLine(this.name);
    }
}

class Dog : Animal
{
    public Dog() : base()
    {
        Console.WriteLine("Dog's default constructor");
    }  

    public Dog(string name) : base(name)
    {
        Console.WriteLine("Dog's constructor with 1 parameter");
        Console.WriteLine(this.name);
    }
}

Co tu idzie

W każdej klasie mamy 2 konstruktorów.

Co oznacza base ?

base jest odwołaniem do klasy nadrzędnej. W naszym przypadku, gdy tworzymy taki przykład klasy Dog

Dog dog = new Dog();

Środowisko wykonawcze najpierw wywołuje funkcję Dog() , która jest konstruktorem bez parametrów. Ale jego ciało nie działa natychmiast. Po nawiasach konstruktora mamy takie wywołanie: base() , co oznacza, że kiedy wywołamy domyślnego konstruktora Dog , to z kolei wywoła domyślnego konstruktora rodzica. Po uruchomieniu konstruktora rodzica wróci, a następnie w końcu uruchomi ciało konstruktora Dog() .

Wynik będzie taki:

Domyślny konstruktor zwierzęcia
Domyślny konstruktor psa

Zobacz demo

Co teraz, jeśli nazwiemy konstruktora Dog's parametrem?

Dog dog = new Dog("Rex");

Wiesz, że członkowie klasy nadrzędnej, którzy nie są prywatni, są dziedziczeni przez klasę podrzędną, co oznacza, że Dog również będzie miał pole name .
W tym przypadku przekazaliśmy argument naszemu konstruktorowi. To z kolei przekazuje argument do konstruktora klasy nadrzędnej z parametrem , który inicjuje pole name .

Wyjście będzie

Animal's constructor with 1 parameter
Rex
Dog's constructor with 1 parameter
Rex

Streszczenie:

Każde tworzenie obiektu zaczyna się od klasy podstawowej. W spadku dziedziczone są klasy znajdujące się w hierarchii. Ponieważ wszystkie klasy pochodzą od Object , pierwszym konstruktorem wywoływanym podczas tworzenia dowolnego obiektu jest konstruktor klasy Object ; Następnie wywoływany jest następny konstruktor w łańcuchu i dopiero po wywołaniu wszystkich z nich obiekt jest tworzony

podstawowe słowo kluczowe

  1. Podstawowe słowo kluczowe służy do uzyskiwania dostępu do członków klasy podstawowej z klasy pochodnej:
  2. Wywołaj metodę w klasie bazowej, która została zastąpiona przez inną metodę. Określ, który konstruktor klasy podstawowej powinien być wywoływany podczas tworzenia instancji klasy pochodnej.

Metody dziedziczenia

Istnieje kilka sposobów dziedziczenia metod

public abstract class Car
{
    public void HonkHorn() {
        // Implementation of horn being honked
    }

    // virtual methods CAN be overridden in derived classes
    public virtual void ChangeGear() {
        // Implementation of gears being changed
    }

    // abstract methods MUST be overridden in derived classes
    public abstract void Accelerate();
}

public class Mustang : Car
{
    // Before any code is added to the Mustang class, it already contains 
    // implementations of HonkHorn and ChangeGear.

    // In order to compile, it must be given an implementation of Accelerate,
    // this is done using the override keyword
    public override void Accelerate() {
        // Implementation of Mustang accelerating
    }

    // If the Mustang changes gears differently to the implementation in Car
    // this can be overridden using the same override keyword as above
    public override void ChangeGear() {
        // Implementation of Mustang changing gears
    }
}

Anty-wzory dziedziczenia

Niewłaściwe dziedziczenie

Powiedzmy, że istnieją 2 klasy Foo i Bar . Foo ma dwie funkcje Do1 i Do2 . Bar musi używać Do1 z Foo , ale nie potrzebuje Do2 lub potrzebuje funkcji równoważnej z Do2 ale robi coś zupełnie innego.

Zły sposób : uczyń Do2() na Foo wirtualnym, a następnie zastąp go w Bar lub po prostu throw Exception w Bar dla Do2()

public class Bar : Foo
{
    public override void Do2()
    {
        //Does something completely different that you would expect Foo to do
        //or simply throws new Exception 
    }
}

Dobry sposób

Wyjmij Do1() z Foo i umieść go w nowej klasie Baz a następnie odziedzicz zarówno Foo jak i Bar z Baz i zaimplementuj Do2() osobno

public class Baz
{
    public void Do1()
    {
        // magic
    }
}

public class Foo : Baz
{
    public void Do2()
    {
        // foo way
    }
}

public class Bar : Baz
{
    public void Do2()
    {
        // bar way or not have Do2 at all
    }
}

Teraz, dlaczego pierwszy przykład jest zły, a drugi jest dobry: gdy deweloper nr2 musi dokonać zmiany w Foo , istnieje szansa, że przerwie implementację Bar ponieważ Bar jest teraz nierozerwalnie związany z Foo . Podczas wykonywania tego drugiego przykładu wspólnoty Foo i Bar zostały przeniesione do Baz i nie wpływają na siebie nawzajem (tak jak nie powinno).

Klasa podstawowa ze specyfikacją typu rekurencyjnego

Jednorazowa definicja ogólnej klasy bazowej ze specyfikatorem typu rekurencyjnego. Każdy węzeł ma jednego rodzica i wiele dzieci.

/// <summary>
/// Generic base class for a tree structure
/// </summary>
/// <typeparam name="T">The node type of the tree</typeparam>
public abstract class Tree<T> where T : Tree<T>
{
    /// <summary>
    /// Constructor sets the parent node and adds this node to the parent's child nodes
    /// </summary>
    /// <param name="parent">The parent node or null if a root</param>
    protected Tree(T parent)
    {
        this.Parent=parent;
        this.Children=new List<T>();
        if(parent!=null)
        {
            parent.Children.Add(this as T);
        }
    }
    public T Parent { get; private set; }
    public List<T> Children { get; private set; }
    public bool IsRoot { get { return Parent==null; } }
    public bool IsLeaf { get { return Children.Count==0; } }
    /// <summary>
    /// Returns the number of hops to the root object
    /// </summary>
    public int Level { get { return IsRoot ? 0 : Parent.Level+1; } }
}

Powyższe można wykorzystać ponownie za każdym razem, gdy trzeba zdefiniować hierarchię drzew obiektów. Obiekt węzła w drzewie musi dziedziczyć z klasy podstawowej za pomocą

public class MyNode : Tree<MyNode>
{
    // stuff
}

każda klasa węzłów wie, gdzie jest w hierarchii, czym jest obiekt nadrzędny, a także czym są obiekty podrzędne. Kilka wbudowanych typów wykorzystuje strukturę drzewa, np. Control lub XmlElement a powyższe Tree<T> może być użyte jako klasa podstawowa dowolnego typu w kodzie.


Na przykład, aby utworzyć hierarchię części, w której całkowita waga jest obliczana na podstawie masy wszystkich dzieci, wykonaj następujące czynności:

public class Part : Tree<Part>
{
    public static readonly Part Empty = new Part(null) { Weight=0 };
    public Part(Part parent) : base(parent) { }
    public Part Add(float weight)
    {
        return new Part(this) { Weight=weight };
    }
    public float Weight { get; set; }

    public float TotalWeight { get { return Weight+Children.Sum((part) => part.TotalWeight); } }
}

do wykorzystania jako

// [Q:2.5] -- [P:4.2] -- [R:0.4]
//    \
//      - [Z:0.8]
var Q = Part.Empty.Add(2.5f);
var P = Q.Add(4.2f);
var R = P.Add(0.4f);
var Z = Q.Add(0.9f);

// 2.5+(4.2+0.4)+0.9 = 8.0
float weight = Q.TotalWeight;

Innym przykładem może być definicja względnych ramek współrzędnych. W takim przypadku prawdziwa pozycja ramki współrzędnych zależy od pozycji wszystkich nadrzędnych ramek współrzędnych.

public class RelativeCoordinate : Tree<RelativeCoordinate>
{
    public static readonly RelativeCoordinate Start = new RelativeCoordinate(null, PointF.Empty) { };
    public RelativeCoordinate(RelativeCoordinate parent, PointF local_position)
        : base(parent)
    {
        this.LocalPosition=local_position;
    }
    public PointF LocalPosition { get; set; }
    public PointF GlobalPosition
    {
        get
        {
            if(IsRoot) return LocalPosition;
            var parent_pos = Parent.GlobalPosition;
            return new PointF(parent_pos.X+LocalPosition.X, parent_pos.Y+LocalPosition.Y);
        }
    }
    public float TotalDistance
    {
        get
        {
            float dist = (float)Math.Sqrt(LocalPosition.X*LocalPosition.X+LocalPosition.Y*LocalPosition.Y);
            return IsRoot ? dist : Parent.TotalDistance+dist;
        }
    }
    public RelativeCoordinate Add(PointF local_position)
    {
        return new RelativeCoordinate(this, local_position);
    }
    public RelativeCoordinate Add(float x, float y)
    {
        return Add(new PointF(x, y));
    }
}

do wykorzystania jako

// Define the following coordinate system hierarchy
//
// o--> [A1] --+--> [B1] -----> [C1]
//             |     
//             +--> [B2] --+--> [C2]
//                         |
//                         +--> [C3]

var A1 = RelativeCoordinate.Start;
var B1 = A1.Add(100, 20);
var B2 = A1.Add(160, 10);

var C1 = B1.Add(120, -40);
var C2 = B2.Add(80, -20);
var C3 = B2.Add(60, -30);

double dist1 = C1.TotalDistance;


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow