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
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
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
- Das Basisschlüsselwort wird verwendet, um innerhalb einer abgeleiteten Klasse auf Mitglieder der Basisklasse zuzugreifen:
- 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;