ein Projekt von goloroden.de

Vererbung

Was ist Vererbung?

Da die Entwicklung einer Klasse von Grund auf sehr aufwändig sein kann, kann statt dessen eine bestehende Klasse wiederverwendet und erweitert werden. Dieses Verfahren, das als Vererbung bezeichnet wird, erzeugt aus einer bestehenden Klasse - der sogenannten Basisklasse - eine neue Klasse - die sogenannte abgeleitete Klasse - , die über alle Felder, Eigenschaften und Methoden der Basisklasse verfügt und diese um eigene Elemente erweitern kann.

Vererbung wird in C# mit Hilfe des Operators : ausgedrückt, wobei dieser sowie der Name der Basisklasse dem Namen der abgeleiteten Klasse nachgestellt werden. Es wurde bereits der Typ object erwähnt, von dem jeder Typ ableitet. Dies kann nun präzisiert werden: Wird für eine Klasse nicht explizit eine Basisklasse angegeben, leitet sie implizit von object ab. Das heißt, dass alle Eigenschaften und Methoden, die für object definiert sind, auch in dieser Klasse zur Verfügung stehen.

Potenziell kann eine Klasse auch explizit von object abgeleitet werden, indem object als Basisklasse angegeben wird. Da dies auf Grund der impliziten Ableitung von object aber keinen Unterschied macht, wird diese Ableitung in der Regel nicht angegeben.
C#
1
2
3
4
5
6
7
8
9
10
11
12
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents a foo class that explicitly derives from
    /// object.
    /// </summary>
    public class Foo : object
    {
    }
}
ist also äquivalent zu
C#
1
2
3
4
5
6
7
8
9
10
11
12
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents a foo class that implicitly derives from
    /// object.
    /// </summary>
    public class Foo
    {
    }
}
Ein Beispiel für eine Methode, die implizit in allen Typen enthalten ist, ist die Methode ToString. Sie dient dazu, einen string zurückzugeben, der eine für Menschen lesbare Repräsentation des Objekts darstellt. Die in dem Typ object definierte Methode kann zwar an einem Objekt einer beliebigen Klasse aufgerufen werden, allerdings kennt sie die spezifischen Details der Klasse nicht. Daher gibt diese Methode standardmäßig den vollqualifizierten Typ des Objekts zurück, an dem sie aufgerufen wird.

Wird ein Objekt vom Typ ComplexNumber, der im vorangegangenen Kapitel entwickelt wurde, instanziiert, gibt die Methode ToString beispielsweise
GoloRoden.GuideToCSharp.ComplexNumber
zurück. Um eine spezifische Version der Methode ToString für den Typ ComplexNumber zu erzeugen, muss diese Methode der Klasse ComplexNumber hinzugefügt werden. Im Gegensatz zu einer klassischen Methodendefinition muss dieser Definition zwischen dem Zugriffsmodifizierer und dem Typ des Rückgabewertes das Schlüsselwort override hinzugefügt werden, um sicherzustellen, dass das Überschreiben der Methode der Basisklasse nicht aus Versehen, sondern absichtlich geschieht.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents a complex number.
    /// </summary>
    public class ComplexNumber
    {
        #region Properties
        #endregion

        #region Methods
        // ...

        /// <summary>
        /// Gets a string representation of the current
        /// instance.
        /// </summary>
        /// <returns>A string representation of the current
        /// instance.</returns>
        public override string ToString()
        {
            // TODO gr: Create the string representation and
            //          return it to the caller.
            //          2007-06-11
        }
        #endregion

        #region Constructors
        #endregion
    }
}
Wird das Schlüsselwort override weggelassen, meldet der Compiler beim Übersetzen der Anwendung eine entsprechende Warnung und fordert den Entwickler auf, das fehlende Schlüsselwort zu ergänzen.

Eine wesentliche Eigenschaft in der objektorientierten Programmierung ist in diesem Zusammenhang die Polymorphie, also die Fähigkeit eines Objekts, je nach Kontext verschiedenen Typen zu entsprechen. Jeder Typ kann durch einen übergeordneten und damit allgemeineren Typ repräsentiert werden, da dieser eine Generalisierung darstellt.

In der Praxis heißt das, dass jeder Methode, die beispielsweise einen Parmeter vom Typ object erwartet, ein Objekt eines beliebigen Typs übergeben werden kann - da jeder Typ implizit von object abgeleitet ist und object damit eine Generalisierung dieses Typs darstellt. Umgekehrt funktioniert dies allerdings nicht: Wird ein Parameter eines bestimmten Typs erwartet, können nur Objekte dieses oder eines abgeleiteten Typs übergeben werden.

Dieses System der Generalisierung und Spezialisierung ist ein Kernkonzept der objektorientierten Programmierung und stellt auch den Grund dar, warum jeder Typ mit Hilfe von Boxing in object umgewandelt werden kann - intern wird hier auf Polymorphie zurückgegriffen.

Im Allgemeinen gilt für die Beziehung zwischen einem Typ und seiner Basisklasse eine "is a"-Beziehung: Jedes Objekt vom Typ ComplexNumber ist gleichzeitig auch vom Typ object, während ein abgeleiteter Typ von ComplexNumber sogar zugleich vom Typ ComplexNumber und vom Typ object ist.

Allerdings erfordert diese Beziehung bei der Modellierung der Klassenhierarchie mehr Aufmerksamkeit, als sie zunächst vermuten lässt. Der Grund hierfür liegt in einer wesentlichen Forderung der objektorientierten Programmierung, die von Barbara Liskov formuliert wurde und daher als Liskov-Prinzip bezeichnet wird. Die Forderung besagt, dass das Verhalten einer abgeleiteten Klasse und das ihrer Basisklasse identisch sein müssen.

Dies bedeutet, dass entgegen dem umgangssprachlichen Gebrauch ein Quadrat kein Rechteck ist, weshalb eine Klasse zur Modellierung von Quadraten nicht von einer Klasse zur Modellierung von Rechtecken abgeleitet werden darf. Während die Höhe und die Breite eines Rechtecks unabhängig voneinander verändert werden können, ist dies bei einem Quadrat nicht möglich.

Angenommen, ein Typ Quadrat wäre abgeleitet von einem Typ Rechteck, dann könnte auf Grund der Polymorphie und der Generalisierung in jeder Methode, die ein Objekt vom Typ Rechteck als Parameter erwartet, auch ein Objekt vom Typ Quadrat übergeben werden. Diese Methode könnte eine Seite dieses Objektes verdoppeln, wodurch sich bei einem Objekt des Typs Rechteck der Flächeninhalt ebenfalls verdoppelt.

Wird statt dessen ein Objekt vom Typ Quadrat übergeben, gilt dies nicht - hier würde sich der Flächeninhalt vervierfachen, da die beiden Seiten nicht unabhängig voneinander verändert werden können. Weil dabei das Liskovsche Prinzip verletzt wird, ist diese Ableitung fehlerhaft.

Außer der bislang genannten Vererbung, bei der eine Klasse von genau einer Basisklasse ableitet, gibt es prinzipiell auch die Mehrfachverebung, bei der eine Klasse über mehrere Basisklassen verfügen kann. Dieses Konzept wird in C# allerdings nicht unterstützt, da Mehrfachvererbung unter Umständen keine eindeutigen Ableitungen erzeugt, und der Nutzen in keinem Verhältnis zu dem nötigen Aufwand und der hohen Komplexität steht.

Strukturen können im Gegensatz zu Klassen nicht vererbt werden.

Felder und Eigenschaften

Die einfachsten Elemente eines Typs, die vererbt werden können, sind Felder. Bisher wurden Felder in der Regel als private gekennzeichnet, um den direkten Zugriff von außerhalb der Klasse zu verhindern. Allerdings kann auf solche Felder auch aus einer Unterklasse nicht zugegriffen werden. Um dies in einem gegebenen Fall zu ermöglichen, gibt es verschiedene Alternativen.

Die einfachste Variante besteht darin, das Feld als internal oder gar als public zu kennzeichnen. Allerdings geht dabei der Zugriffsschutz von außerhalb der Klasse verloren, was der Objektorientierung in den meisten Fällen widerspricht. Eine andere Möglichkeit besteht darin, über die entsprechende Eigenschaft indirekt auf das Feld zuzugreifen, was eine im Hinblick auf die Objektorientierung deutlich sauberere Variante darstellt.

In der Praxis verfügt aber nicht jedes Feld über eine zugehörige Eigenschaft, da in der Regel nur solche Felder mit einer Eigenschaft ausgestattet werden, die für die Konfiguration eines Objektes von außen wichtig sind. Felder, die hingegen nur für interne Berechnungen oder sonstige interne Belange genutzt werden und außerhalb eines Objektes nicht zugreifbar sein sollen, bleiben üblicherweise ohne entsprechende Eigenschaft.

Abhilfe schafft in einem solchen Fall das Schlüsselwort protected, das den Zugriff nicht nur aus der Klasse, welche die Felddefinition enthält, ermöglicht, sondern auch aus jeder Unterklasse dieser Klasse. Felder, die als protected gekennzeichnet sind, stehen also von der Ebene des Zugriffs zwischen public und private.

Außerdem gibt es noch die Erweiterung des Schlüsselwortes protected auf protected internal, wodurch der Zugriff ebenfalls aus abgeleiteten Klassen ermöglicht wird, allerdings nur, sofern diese sich innerhalb der gleichen Assembly befinden.

Methoden

Werden Methoden in einem abgeleiteten Typ überschrieben, muss in dem abgeleiteten Typ die Methode explizit als override gekennzeichnet werden, um anzuzeigen, dass das Überschreiben beabsichtigt und kein Versehen ist. Allerdings kann nicht jede beliebige Methode einer Basisklasse überschrieben werden - dort muss eine Methode zunächst als überschreibbar gekennzeichnet werden. Dies geschieht mit Hilfe des Schlüsselwortes virtual.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents a base class.
    /// </summary>
    public class BaseClass
    {
        /// <summary>
        /// Represents a virtual foo method.
        /// </summary>
        public virtual void Foo();
    }
}
Methoden, die nicht als virtual gekennzeichnet werden, können von abgeleiteten Typen nicht überschrieben werden. Damit eine Methode mit dem Schlüsselwort virtual markiert werden kann, darf sie nicht mit dem Zugriffsmodifizierer private markiert sein - da sie in diesem Fall in dem abgeleiteten Typ nicht sichtbar ist. Zudem kann virtual nicht gleichzeitig mit override angegeben werden.

Wird während der Ausführung einer Anwendung eine virtuelle Methode aufgerufen, ermittelt die Common Language Runtime den tatsächlichen Typ des Objektes, an dem die Methode aufgerufen wird und ruft die zugehörige Methode auf - falls eine entsprechende überschriebene Variante verfügbar ist. Auf diese Art wird gewährleistet, dass für ein Objekt immer die korrekte Version einer Methode aufgerufen wird.

Außer override gibt es noch das Schlüsselwort new. Der Unterschied liegt in der Bindung der Methode an den Typ - bei override wird die Methode in jedem Fall für den zugehörigen Typ aufgerufen, da die Methode der Basisklasse überschrieben wurde, bei new wird die Methode unter Umständen für den Basistyp aufgerufen, da diese Methode nicht überschrieben, sondern nur ausgeblendet wurde.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents a base class.
    /// </summary>
    public class BaseClass
    {
        /// <summary>
        /// Represents a virtual foo method.
        /// </summary>
        public virtual void Foo()
        {
        }
    }

    /// <summary>
    /// Represents a class that derives from BaseClass.
    /// </summary>
    public class DerivedClassA : BaseClass
    {
        /// <summary>
        /// Represents a foo method that overwrites the base
        /// class's implementation.
        /// </summary>
        public override void Foo()
        {
        }
    }

    /// <summary>
    /// Represents another class that derives from
    /// BaseClass.
    /// </summary>
    public class DerivedClassB : BaseClass
    {
        /// <summary>
        /// Represents a foo method that shadows the base
        /// class's implementation.
        /// </summary>
        public new void Foo()
        {
        }
    }
}
Beispielhaft lässt sich das an der Klasse ComplexNumber verdeutlichen. Die Methode ToString ist dort als override gekennzeichnet. Das heißt, wird die Methode ToString an einem Objekt dieser Klasse aufgerufen, dann wird der Code ausgeführt, der in der überschriebenen Methode definiert wurde. Dieser Code wird auch dann ausgeführt, wenn das Objekt beispielsweise als object geboxt wird.

Wäre die Methode ToString statt dessen als new gekennzeichnet, würde ebenfalls der in der Klasse ComplexNumber definierte Code ausgeführt - aber nur, wenn diese Methode an dem ungeboxten Objekt aufgerufen wird. Erfolgte der Aufruf statt dessen an einer geboxten Version des Objektes, so würde der Code des geboxten Typs ausgeführt.

Würde das Objekt also als object geboxt, würde bei einem Aufruf der Methode ToString die Version ausgeführt, die in der Klasse object definiert wurde. In der Praxis wird new allerdings eher selten verwendet, in der Regel kommt das Schlüsselwort override zum Einsatz.

In einigen Fällen soll aus einer überschriebenen Methode explizit die Methode der Basisklasse aufgerufen werden, zum Beispiel, um deren Funktionalität auch in der überschreibenden Methode nutzen zu können. Dazu dient das Schlüsselwort base, das analog zu this verwendet werden kann, allerdings statt auf das eigene Objekt immer auf den Typ der Basisklasse verweist.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents a base class.
    /// </summary>
    public class BaseClass
    {
        /// <summary>
        /// Represents a virtual foo method.
        /// </summary>
        public virtual void Foo()
        {
        }
    }

    /// <summary>
    /// Represents a class that derives from BaseClass.
    /// </summary>
    public class DerivedClass : BaseClass
    {
        /// <summary>
        /// Represents a foo method that overwrites the base
        /// class's implementation.
        /// </summary>
        public override void Foo()
        {
            // Call the base method.
            base.Foo();
        }
    }
}
Prinzipiell kann eine Methode, die mit override oder new gekennzeichnet wurde, in einer weiteren abgeleiteten Klasse wiederum überschrieben werden. Das Schlüsselwort virtual bezieht sich also nicht nur auf die direkt nachfolgende Ableitung, sondern auf alle Klassen, die in der Ableitungshierarchie nachfolgen. Um dies zu verhindern und eine weitere Vererbung zu verhindern, kann eine Methode, die mit override oder new gekennzeichnet wurde, mit Hilfe des Schlüsselwortes sealed versiegelt werden, wodurch keine weitere Überschreibung dieser Methode mehr möglich ist.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents a base class.
    /// </summary>
    public class BaseClass
    {
        /// <summary>
        /// Represents a virtual foo method.
        /// </summary>
        public virtual void Foo()
        {
        }
    }

    /// <summary>
    /// Represents a class that derives from BaseClass.
    /// </summary>
    public class DerivedClass : BaseClass
    {
        /// <summary>
        /// Represents a foo method that overwrites the base
        /// class's implementation and avoids any further
        /// overwriting by sealing this method.
        /// </summary>
        public override sealed void Foo()
        {
        }
    }
}
Außerdem können vollständige Klassen versiegelt werden, was bedeutet, dass eine solche Klasse nicht vererbt werden kann. Dies ist bei Klassen sinnvoll, die eine feststehende Funktionalität bereitstellen, wie beispielsweise Klassen mit mathematischen Methoden - eine Methode zur Berechnung der Sinusfunktion zu überschreiben, ergibt wenig Sinn, schließlich ist der Sinus bereits das endgültige Resultat.

Daher ist beispielsweise die von .NET bereitgestellte Klasse Math im Namensraum System versiegelt, ebenso kann die Klasse ComplexNumber versiegelt werden.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents a complex number.
    /// </summary>
    public sealed class ComplexNumber
    {
        #region Properties
        #endregion

        #region Methods
        #endregion

        #region Constructors
        #endregion
    }
}
Unabhängig davon, ob die Klasse ComplexNumber versiegelt ist oder nicht, handelt es sich um eine konkrete Klasse. Das bedeutet, dass sie instanziiert werden kann, dass also Objekte von ihr erzeugt werden können.

Manchmal kann es sinnvoll sein, statt dessen eine sogenannte abstrakte Klasse zu erzeugen, die nicht instanziiert werden kann, die nur als Basisklasse für andere Klassen genutzt wird, um beispielsweise gemeinsam genutzte Funktionalität zentral zur Verfügung zu stellen. Eine solche Klasse wird mit dem Schlüsselwort abstract gekennzeichnet und kann nicht versiegelt werden.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents an abstract base class.
    /// </summary>
    public abstract class AbstractBaseClass
    {
        /// <summary>
        /// Represents a virtual foo method.
        /// </summary>
        public virtual void Foo()
        {
        }
    }
}
In einer abstrakten Klasse können zudem abstrakte Methoden definiert werden, die keinen Methodenrumpf enthalten, sondern nur aus dem Methodenkopf bestehen. Solche Methoden müssen mit dem Schlüsselwort abstract versehen werden und sind implizit virtual. Statt eines Methodenrumpfes, der in geschweiften Klammern angegeben wird, wird deren Methodenkopf mit einem Semikolon abgeschlosen.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents an abstract base class.
    /// </summary>
    public abstract class AbstractBaseClass
    {
        /// <summary>
        /// Represents an abstract foo method.
        /// </summary>
        public abstract void Foo();
    }

    /// <summary>
    /// Represents a class that derived from
    /// AbstractBaseClass.
    /// </summary>
    public class DerivedClass : AbstactBaseClass
    {
        /// <summary>
        /// Represents a method that implements the base
        /// class's abstract method.
        /// </summary>
        public override void Foo()
        {
            // TODO gr: Implement abstract method.
            //          2008-01-03
        }
    }
}
In einer abgeleiteten Klasse müssen abstrakte Methoden in jedem Fall implementiert werden, es sei denn, die abgeleitete Klasse wird ihrerseits wiederum als abstract gekennzeichnet.

Gelegentlich kann es notwendig sein, eine bestehende Klasse ohne Erzeugung einer abgeleiteten Klasse zu erweitern, ohne allerdings Zugriff auf ihren Quelltext zu haben. Beispielsweise würde eine Erweiterung des Typs string diesem Vorhaben entsprechen.

Zu diesem Zweck gibt es seit der Version 3.0 von C# sogenannte Erweiterungsmethoden, mit denen vorhandene Typen ergänzt werden können. Da diese Möglichkeit äußerst mächtig ist und schnell zu unübersichtlichem Code führt, wird ihr Einsatz in der Praxis als schlechter Stil angesehen. Dass Erweiterungsmethoden in C# 3.0 überhaupt in Erscheinung treten, gründet sich in der Abfragetechnik Linq, die mit C# 3.0 eingeführt wurde und auf Erweiterungsmethoden basiert.

Um einen bestehenden Typ zu erweitern, wird innerhalb einer statischen Klasse eine statische Methode definiert, welche die entsprechende Funktionalität bereitstellt. Als erster Parameter wird dieser Methode der zu erweiternde Typ übergeben, allerdings ergänzt um das Schlüsselwort this, woran C# erkennen kann, dass es sich nicht um eine normale, sondern um eine Erweiterungsmethode handelt.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Contains extension methods.
    /// </summary>
    public static class ExtensionMethods
    {
        /// <summary>
        /// Converts the specified string to its XML
        /// representation.
        /// </summary>
        /// <param name="source">The string that shall be
        /// converted to XML.</param>
        /// <returns>The XML representation of the specified
        /// string.</returns>
        public static string ToXml(this string source)
        {
            // TODO gr: Transform the source string to XML and
            //          return the result to the caller.
            //          2007-12-26
        }
    }
}
Die auf diese Art definierte Erweiterungsmethode für den Typ string kann nun an jeder Zeichenkette aufgerufen werden, als ob sie eine vordefinierte Methode wäre.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Define a foo string.
            string foo = "Hello world!";

            // Get the XML representation of the string.
            string xml = foo.ToXml();
        }
    }
}
Intern prüft C# beim Aufruf einer Methode zunächst, ob eine entsprechende Methode an dem jeweiligen Typ definiert ist. Wenn nicht, wird überprüft, ob es eine statische Methode innerhalb einer statischen Klasse gibt, deren Name dem der aufgerufenen Methode entspricht, und deren erster Parameter dem gewünschten Typ entspricht, der außerdem mit dem Schlüsselwort this gekennzeichnet wurde. Falls eine solche Methode existiert, wird diese ausgeführt, andernfalls wird ein Fehler gemeldet.

Konstruktoren

Die einzigen Elemente eines Typs, die nicht an einen abgeleiteten Typ vererbt werden, sind Konstruktoren. Der Grund dafür liegt in einer Definition der objektorientierten Programmierung, in der die Aufgabe von Konstruktoren beschrieben wird. Diese liegt darin, ein vollständig initialisiertes Objekt zurückzugeben.

Da ein abgeleiteter Typ in der Regel weitere Felder einführt, die der Konstruktor des Basistyps nicht berücksichtigt, würde dieser der Anforderung nicht mehr gerecht, ein vollständig initialisiertes Objekt zurückzugeben. Eine abgeleitete Klasse verfügt daher zunächst nur über einen parameterlosen, leeren Standardkonstruktor.

Allerdings können entsprechende Konstruktoren definiert werden. Analog zu Methoden ist auch in den Konstruktoren der Zugriff auf die Konstrutoren des Basistyps möglich, wiederum mit Hilfe des Schlüsselwortes base, das mit der gleichen Syntax wie das Schlüsselwort this bei Konstruktoren angegeben werden kann. Wird es angegeben, wird zunächst der Konstruktor des Basistyps aufgerufen, bevor der Konstruktor des zu instanziierenden Typs ausgeführt wird. Allerdings kann nur entweder base oder this angegeben werden.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents a base class.
    /// </summary>
    public class BaseClass
    {
        /// <summary>
        /// Initializes an instance of the BaseClass type.
        /// </summary>
        public BaseClass()
        {
        }
    }

    /// <summary>
    /// Represents a class that derives from BaseClass.
    /// </summary>
    public class DerivedClass : BaseClass
    {
        /// <summary>
        /// Initializes an instance of the DerivedClass
        /// type.
        /// </summary>
        public DerivedClass()
            : base()
        {
        }
    }
}