Suche…


Syntax

  • Klasse DerivedClass: BaseClass
  • Klasse DerivedClass: BaseClass, IExampleInterface
  • Klasse DerivedClass: BaseClass, IExampleInterface, IAnotherInterface

Bemerkungen

Klassen können direkt von nur einer Klasse erben, können jedoch (stattdessen oder gleichzeitig) eine oder mehrere Schnittstellen implementieren.

Strukturen können Schnittstellen implementieren, können jedoch nicht explizit von jedem Typ erben. Sie erben implizit von System.ValueType , das wiederum direkt von System.Object erbt.

Statische Klassen können keine Schnittstellen implementieren.

Vererbung von einer Basisklasse

Um das Duplizieren von Code zu vermeiden, definieren Sie allgemeine Methoden und Attribute in einer allgemeinen Klasse als Basis:

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()
    {
        // ...
    }
}

Da Sie nun eine Klasse haben, die Animal im Allgemeinen darstellt, können Sie eine Klasse definieren, die die Besonderheiten bestimmter Tiere beschreibt:

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

Die Cat-Klasse erhält nicht nur Zugriff auf die explizit beschriebenen Methoden, sondern auch auf alle Methoden, die in der allgemeinen Basisklasse Animal sind. Jedes Tier (ob es eine Katze war oder nicht) konnte essen, starren oder rollen. Ein Tier kann jedoch nicht kratzen, es sei denn, es ist auch eine Katze. Sie können dann andere Klassen definieren, die andere Tiere beschreiben. (Wie Gopher mit einer Methode zur Zerstörung von Blumengärten und Faultier ohne zusätzliche Methoden.)

Von einer Klasse erben und eine Schnittstelle implementieren

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

Von einer Klasse erben und mehrere Schnittstellen implementieren

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

Vererbung testen und navigieren

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

Eine abstrakte Basisklasse erweitern

Im Gegensatz zu Schnittstellen, die als Implementierungsverträge bezeichnet werden können, fungieren abstrakte Klassen als Erweiterungsverträge.

Eine abstrakte Klasse kann nicht instanziiert werden, sie muss erweitert werden und die resultierende Klasse (oder abgeleitete Klasse) kann dann instanziiert werden.

Abstrakte Klassen werden verwendet, um generische Implementierungen bereitzustellen

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

Das obige Beispiel zeigt, wie jede Klasse, die ein Auto erweitert, automatisch die HonkHorn-Methode mit der Implementierung erhält. Dies bedeutet, dass sich jeder Entwickler, der ein neues Auto erstellt, keine Gedanken darüber machen muss, wie es sein Horn hupen wird.

Konstruktoren in einer Unterklasse

Wenn Sie eine Unterklasse einer Basisklasse erstellen, können Sie die Basisklasse mithilfe von : base nach den Parametern des Unterklassenkonstruktors erstellen.

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

Erbe. Reihenfolge der Konstrukteure

Stellen Sie sich vor, wir haben eine Klasse Animal die eine Kindklasse Dog

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

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

Standardmäßig erbt jede Klasse implizit die Object Klasse.

Dies ist derselbe Code wie oben.

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

Beim Erstellen einer Instanz der Dog Klasse wird der Standardkonstruktor der Basisklassen (ohne Parameter) aufgerufen, wenn kein expliziter Aufruf eines anderen Konstruktors in der übergeordneten Klasse erfolgt . In unserem Fall wird zuerst Object's Konstruktor, dann Animal's und am Ende Dog's Constructor genannt.

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

Ausgabe wird sein

In Animal's Konstruktor
In Dogs Konstruktor

Demo anzeigen

Rufen Sie den Konstruktor der Eltern explizit auf.

In den obigen Beispielen ruft unser Dog Klassenkonstruktor den Standardkonstruktor der Animal Klasse auf. Wenn Sie möchten, können Sie angeben, welcher Konstruktor aufgerufen werden soll: Es ist möglich, einen beliebigen Konstruktor aufzurufen, der in der übergeordneten Klasse definiert ist.

Betrachten wir diese zwei Klassen.

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

Was ist hier los?

Wir haben 2 Konstruktoren in jeder Klasse.

Was bedeutet base ?

base ist eine Referenz auf die übergeordnete Klasse. In unserem Fall, wenn wir eine Instanz der Dog Klasse wie folgt erstellen

Dog dog = new Dog();

Die Laufzeit ruft zuerst Dog() , den parameterlosen Konstruktor. Aber sein Körper funktioniert nicht sofort. Nach den Klammern des Konstruktors haben wir einen solchen Aufruf: base() , das heißt, wenn wir den Standard- Dog Konstruktor aufrufen, wird dieser wiederum den Standardkonstruktor des übergeordneten Objekts aufrufen. Nachdem der Konstruktor des übergeordneten Elements ausgeführt wurde, wird der Dog() Konstruktorkörper zurückgegeben und anschließend ausgeführt.

So wird die Ausgabe so sein:

Der Standardkonstruktor von Animal
Standardkonstruktor des Hundes

Demo anzeigen

Was ist nun, wenn wir den Konstruktor des Dog's mit einem Parameter aufrufen?

Dog dog = new Dog("Rex");

Sie wissen , dass die Mitglieder in der übergeordneten Klasse , die nicht privat sind , werden von der untergeordneten Klasse geerbt, was bedeutet , dass Dog auch die haben name Feld.
In diesem Fall haben wir unserem Konstruktor ein Argument übergeben. Er seinerseits geht das Argument der übergeordneten Klasse Konstruktor mit einem Parameter, der den initialisiert name Feld.

Ausgabe wird sein

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

Zusammenfassung:

Jede Objekterstellung beginnt bei der Basisklasse. In der Vererbung werden die Klassen, die sich in der Hierarchie befinden, verkettet. Da alle Klassen von Object , ist der erste Konstruktor, der beim Erstellen eines Objekts aufgerufen wird, der Object Klassenkonstruktor. Dann wird der nächste Konstruktor in der Kette aufgerufen und erst nachdem alle aufgerufen werden, wird das Objekt erstellt

Basis-Schlüsselwort

  1. Das Basisschlüsselwort wird verwendet, um innerhalb einer abgeleiteten Klasse auf Mitglieder der Basisklasse zuzugreifen:
  2. Rufen Sie eine Methode für die Basisklasse auf, die von einer anderen Methode überschrieben wurde. Geben Sie an, welcher Basisklassenkonstruktor beim Erstellen von Instanzen der abgeleiteten Klasse aufgerufen werden soll.

Vererbung von Methoden

Es gibt mehrere Möglichkeiten, Methoden zu vererben

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

Vererbung Anti-Muster

Nicht ordnungsgemäße Vererbung

Nehmen wir an, es gibt 2 Klassen Foo und Bar . Foo hat zwei Funktionen Do1 und Do2 . Bar muss Do1 von Foo , aber es braucht kein Do2 oder eine Funktion, die Do2 entspricht, aber etwas völlig anderes macht.

Schlechter Weg : Machen Sie Do2() auf Foo virtuell, überschreiben Sie es in Bar oder throw Exception einfach throw Exception in Bar für 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 
    }
}

Gute Möglichkeit

Nehmen Sie Do1() von Foo und legen Sie es in die neue Klasse Baz Dann erben Sie Foo und Bar von Baz und implementieren Sie Do2() separat

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

Nun , warum erstes Beispiel ist schlecht und das zweite ist gut: Wenn Entwickler NR2 eine Änderung zu tun hat Foo , stehen die Chancen er Implementierung brechen Bar , weil Bar jetzt untrennbar ist Foo . Bei letzterem Beispiel wurde Foo and Bar commonalty zu Baz und sie beeinflussen sich nicht (wie das sollte).

Basisklasse mit rekursiver Typangabe

Einmalige Definition einer generischen Basisklasse mit rekursivem Typbezeichner. Jeder Knoten hat ein Elternteil und mehrere Kinder.

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

Das Obige kann jedes Mal wiederverwendet werden, wenn eine Baumhierarchie von Objekten definiert werden muss. Das Knotenobjekt in der Baumstruktur muss von der Basisklasse mit erben

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

Jede Knotenklasse weiß, wo sie sich in der Hierarchie befindet, was das übergeordnete Objekt ist und was die untergeordneten Objekte sind. Mehrere eingebaute Typen verwenden eine Baumstruktur wie Control oder XmlElement Der oben angegebene Tree<T> kann als Basisklasse eines beliebigen Typs in Ihrem Code verwendet werden.


Um beispielsweise eine Hierarchie von Teilen zu erstellen, bei denen das Gesamtgewicht aus dem Gewicht aller untergeordneten Elemente berechnet wird, gehen Sie wie folgt vor:

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

verwendet werden als

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

Ein anderes Beispiel wäre die Definition relativer Koordinatenrahmen. In diesem Fall hängt die wahre Position des Koordinatenrahmens von den Positionen aller übergeordneten Koordinatenrahmen ab.

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

verwendet werden als

// 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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow