C# Language
Erfenis
Zoeken…
Syntaxis
- klasse DerivedClass: BaseClass
- class DerivedClass: BaseClass, IExampleInterface
- class DerivedClass: BaseClass, IExampleInterface, IAnotherInterface
Opmerkingen
Klassen kunnen rechtstreeks van slechts één klasse erven, maar kunnen (in plaats van of tegelijkertijd) een of meer interfaces implementeren.
Structs kunnen interfaces implementeren, maar kunnen van geen enkel type expliciet erven. Ze nemen impliciet over van System.ValueType
, dat op zijn beurt rechtstreeks van System.Object
System.ValueType
.
Statische klassen kunnen geen interfaces implementeren.
Overerving van een basisklasse
Om dubbele code te voorkomen, definieert u algemene methoden en attributen in een algemene 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()
{
// ...
}
}
Nu je een klasse hebt die Animal
in het algemeen vertegenwoordigt, kun je een klasse definiëren die de eigenaardigheden van specifieke dieren beschrijft:
public class Cat : Animal
{
public Cat()
{
Name = "Cat";
}
// Methods for scratching furniture and ignoring owner
public void Scratch(Object furniture)
{
// ...
}
}
De Cat-klasse krijgt niet alleen toegang tot de methoden die in de definitie expliciet zijn beschreven, maar ook tot alle methoden die zijn gedefinieerd in de algemene basisklasse voor Animal
. Elk dier (ongeacht of het een kat was) kon eten, staren of rollen. Een dier zou echter niet kunnen krabben, tenzij het ook een kat was. Je zou dan andere klassen kunnen definiëren die andere dieren beschrijven. (Zoals Gopher met een methode om bloementuinen te vernietigen en Luiaard zonder extra methoden.)
Overnemen van een klasse en een interface implementeren
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";
}
}
Overnemen van een klasse en meerdere interfaces implementeren
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";
}
}
Overerving testen en navigeren
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
Een abstracte basisklasse uitbreiden
In tegenstelling tot interfaces, die kunnen worden omschreven als contracten voor implementatie, fungeren abstracte klassen als contracten voor verlenging.
Een abstracte klasse kan niet worden geïnstantieerd, deze moet worden uitgebreid en de resulterende klasse (of afgeleide klasse) kan vervolgens worden geïnstantieerd.
Abstracte klassen worden gebruikt om generieke implementaties te bieden
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.
}
Het bovenstaande voorbeeld laat zien hoe elke klasse die Auto uitbreidt automatisch de HonkHorn-methode ontvangt met de implementatie. Dit betekent dat elke ontwikkelaar die een nieuwe auto maakt, zich geen zorgen hoeft te maken over hoe hij toetert.
Aannemers in een subklasse
Wanneer u een subklasse van een basisklasse maakt, kunt u de basisklasse samenstellen met : base
achter de parameters van de subklasse constructor.
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;
}
}
Erfenis. Oproepen van constructeurs
Overweeg dat we een klasse Animal
die een kindklasse Dog
class Animal
{
public Animal()
{
Console.WriteLine("In Animal's constructor");
}
}
class Dog : Animal
{
public Dog()
{
Console.WriteLine("In Dog's constructor");
}
}
Standaard neemt elke klasse impliciet de klasse Object
.
Dit is hetzelfde als de bovenstaande code.
class Animal : Object
{
public Animal()
{
Console.WriteLine("In Animal's constructor");
}
}
Wanneer u een instantie van Dog
klasse Dog
, wordt de standaardconstructor van de basisklassen (zonder parameters) aangeroepen als er geen expliciete aanroep is naar een andere constructor in de bovenliggende klasse . In ons geval wordt deze eerst Object's
constructor van Object's
genoemd, vervolgens Object's
constructor van Animal's
en uiteindelijk Dog's
constructor van Dog's
.
public class Program
{
public static void Main()
{
Dog dog = new Dog();
}
}
Uitgang zal zijn
In de constructor van Animal
In de aannemer van Dog
Roep de constructor van de ouder expliciet op.
In de bovenstaande voorbeelden noemt onze constructeur Dog
klasse de standaardconstructor van de klasse Animal
. Als u wilt, kunt u opgeven welke constructor moet worden aangeroepen: het is mogelijk om elke constructor aan te roepen die in de bovenliggende klasse is gedefinieerd.
Overweeg dat we deze twee klassen hebben.
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);
}
}
Wat is hier aan de hand?
We hebben 2 constructeurs in elke klasse.
Wat betekent base
?
base
is een verwijzing naar de ouderklasse. In ons geval, wanneer we een instantie van de Dog
klasse zoals deze maken
Dog dog = new Dog();
De runtime roept eerst Dog()
, wat de parameterloze constructor is. Maar zijn lichaam werkt niet onmiddellijk. Na de haakjes van de constructor hebben we een dergelijke aanroep: base()
, wat betekent dat wanneer we de standaard Dog
constructor aanroepen, deze op zijn beurt de standaardconstructor van de ouder aanroept . Nadat de constructor van de ouder is uitgevoerd, keert deze terug en voert u ten slotte het constructorlichaam Dog()
.
Dus de output zal er zo uitzien:
De standaardconstructor van Animal
Standaardconstructeur van de hond
Wat nu als we Dog's
constructor van de Dog's
met een parameter noemen?
Dog dog = new Dog("Rex");
Je weet dat de leden in de bovenliggende klasse die niet privé worden overgenomen door het kind klasse zijn, wat betekent dat Dog
zal ook de name
het veld.
In dit geval hebben we een argument aan onze constructeur doorgegeven. Het op zijn beurt gaat het argument constructor de ouder klasse met een parameter, die de initialiseert name
veld.
Uitgang zal zijn
Animal's constructor with 1 parameter
Rex
Dog's constructor with 1 parameter
Rex
Samenvatting:
Elke creatie van objecten begint bij de basisklasse. In de overerving zijn de klassen in de hiërarchie aan elkaar geketend. Zoals alle klassen ontlenen Object
, de eerste constructeur genoemd te worden wanneer een object wordt gemaakt is de Object
klasse constructor; Vervolgens wordt de volgende constructor in de keten aangeroepen en pas nadat ze allemaal zijn genoemd, wordt het object gemaakt
basis trefwoord
- Het basiszoekwoord wordt gebruikt om toegang te krijgen tot leden van de basisklasse vanuit een afgeleide klasse:
- Roep een methode op de basisklasse aan die door een andere methode is overschreven. Geef op welke base-class constructor moet worden aangeroepen bij het maken van instanties van de afgeleide klasse.
Overerving methoden
Er zijn verschillende manieren waarop methoden kunnen worden geërfd
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
}
}
Overerving Antipatronen
Ongepaste erfenis
Laten we zeggen dat er 2 klassen klasse Foo
en Bar
. Foo
heeft twee functies Do1
en Do2
. Bar
moet Do1
van Foo
, maar het heeft geen Do2
nodig of heeft een functie nodig die gelijk is aan Do2
maar iets totaal anders doet.
Slechte manier : maak Do2()
op Foo
virtueel en overschrijf het in Bar
of throw Exception
gewoon in Bar
voor 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
}
}
Goede manier
Haal Do1()
uit Foo
en plaats het in de nieuwe klasse Baz
, Do1()
vervolgens zowel Foo
als Bar
van Baz
en implementeer Do2()
afzonderlijk
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
}
}
Waarom is het eerste voorbeeld slecht en het tweede is goed: wanneer ontwikkelaar nr2 een wijziging in Foo
moet doorvoeren, is de kans groot dat hij de implementatie van Bar
verbreekt, omdat Bar
nu onlosmakelijk met Foo
is verbonden. Bij het laatstgenoemde voorbeeld is het Foo
en Bar
burgerschap verplaatst naar Baz
en hebben ze geen invloed op elkaar (zoals dat niet zou moeten).
Basisklasse met recursieve typespecificatie
Eenmalige definitie van een generieke basisklasse met recursieve typespecificatie. Elk knooppunt heeft één ouder en meerdere kinderen.
/// <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; } }
}
Het bovenstaande kan opnieuw worden gebruikt telkens wanneer een boomhiërarchie van objecten moet worden gedefinieerd. Het knooppuntobject in de boom moet erven van de basisklasse met
public class MyNode : Tree<MyNode>
{
// stuff
}
elke knooppuntklasse weet waar deze zich bevindt in de hiërarchie, wat het bovenliggende object is en wat de onderliggende objecten zijn. Verschillende ingebouwde types gebruiken een boomstructuur, zoals Control
of XmlElement
en de bovenstaande Tree<T>
kan worden gebruikt als een basisklasse van elk type in uw code.
Als u bijvoorbeeld een hiërarchie van onderdelen wilt maken waarbij het totale gewicht wordt berekend op basis van het gewicht van alle kinderen, doet u het volgende:
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); } }
}
te gebruiken 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;
Een ander voorbeeld zou zijn bij de definitie van relatieve coördinaatframes. In dit geval hangt de ware positie van het coördinatenframe af van de posities van alle bovenliggende coördinatenframes.
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));
}
}
te gebruiken 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;