Freigeben über


Dr. GUI .NET 1.1 #2

 

Überarbeitet für Version 1.1 des Microsoft .NET Framework

30. Mai 2003

Zusammenfassung: Bietet eine kurze Übersicht über objektorientierte Programmierung, einschließlich Vererbung, virtuellen (überschreibbaren) Methoden und Eigenschaften, Schnittstellen und der Verwendung dieser Features zum Erreichen von Polymorphismus. (22 gedruckte Seiten)

Inhalte

Einführung
Wo wir waren; Wohin wir gehen
Vererbung
Polymorphismus, Virtual und Außerkraftsetzungen
Schnittstellen
Versuch es doch mal!
Was wir getan haben; Was kommt als nächstes

Sehen Sie sich den Quellcode für diesen Artikel in einem neuen Fenster an.

Einführung

Willkommen zurück zum third.NET Artikel von Dr. GUI (die ersten beiden Artikel, die nach Microsoft® .NET Framework Arrayelementnummerierung benannt sind, beginnen bei 0 statt 1 und heißen Dr. GUI .NET #0 und Dr. GUI .NET #1). Wenn Sie eine Übersicht über die früheren Artikel suchen, sehen Sie sich die Dr. GUI .NET-Homepage an.

Sie sollten sich auch die Dr. GUI .NET-Meldungstafel und die Homepage der Dr. GUI .NET-Beispiele ansehen.http://www.coldrooster.com/DrGUIdotNet/2/. Oder wenn Sie aus einem seltsamen Grund daran interessiert sind, was Dr. GUI denkt, sehen Sie sich seinen Blog Dr. GUI's Bits and Bytes an.

Dr. GUI hofft, dass Sie am Message Board teilnehmen. Es gibt bereits einige gute Diskussionen, aber sie werden besser sein, wenn Sie mitmachen! Jeder kann die Nachrichten lesen, aber Sie benötigen ein Microsoft® .NET Passport-Konto, um Sie für die Veröffentlichung zu authentifizieren. Aber keine Sorge : Es ist einfach, einen .NET Passport einzurichten, und Sie können ihn jedem Ihrer E-Mail-Konten zuordnen. (Sie benötigen kein MSN- oder Hotmail-Konto, um Passport verwenden zu können.)

Die Microsoft® ASP.NET-Anwendungen werden tatsächlich auf einem Server ausgeführt: Sehen Sie sich die Cold Rooster Consulting-Website an, um alle ASP.NET Anwendungen aus dieser Reihe zu sehen.

Wo wir waren; Wohin wir gehen

Beim letzten Mal haben wir über die Grundlagen von Typen gesprochen, hauptsächlich Klassen – aber auch Strukturen und Aufzählungen – sowie einige andere in der .NET Framework.

Dieses Mal werden wir eine kurze Überprüfung der objektorientierten Programmierung durchführen, einschließlich Vererbung, virtuellen (überschreibbaren) Methoden und Eigenschaften, Schnittstellen und der Verwendung dieser Features zum Erreichen von Polymorphismus.

Aber zuerst beginnen wir mit der Vererbung.

Vererbung

Haben Sie jemals ein Objekt gewünscht, das wie ein vorhandenes Objekt mit einigen kleineren Spezialisierungen ist? Wenn Sie dies bereits haben, ist die Vererbung für Sie geeignet.

Wenn ein Typ von einem anderen erbt, erhält er alle Member. Sie können Elemente in Ihrem geerbten Typ ausblenden, aber sie nicht loswerden. Natürlich können Sie Mitglieder hinzufügen. Und wie wir sehen werden, können Sie einige Mitglieder überschreiben . (Was bedeutet das? Es ist ein Geheimnis, zumindest für den Moment. Aber alles wird später enthüllt.)

Um eine geerbte Klasse zu erstellen, leiten wir von einer Basisklasse ab. Alle Klassen werden implizit von Object abgeleitet, sodass die folgenden beiden Deklarationen in C# und Microsoft® Visual Basic® .NET identisch sind:

C#

   class Foo { /* ... */ }
   class Goo : Object { /* ... */ }

Visual Basic .NET

   Class Foo
      ' ...
   End Class
   Class Goo
Inherits Object
' ...
   End Class

In beiden Fällen ist Object die Basisklasse. Im Fall von Goo wird dies explizit gemacht.

Sie können natürlich von einem Typ neben Object ableiten. Beispiel:

C# (Siehe Code.)

// compile with: csc InheritanceCS.cs
using System;
class Base {
   protected int i = 5;
   public void Print() {
      Console.WriteLine("i is {0}", i);
   }
}
class Derived : Base {
   double d = 7.3;
   public void PrintBoth() {
      Console.WriteLine("i is {0}, d is {1}", i, d);
   }
}
class Test {
   static void Main() {
        Base b = new Base();
        Console.WriteLine("b:");
        b.Print();

        Derived d = new Derived();
        Console.WriteLine("d:");
        d.Print();
        d.PrintBoth();
        Console.ReadLine();
  }
}

Visual Basic .NET (Siehe Code.)

' compile with: vbc InheritanceVB.vb
Imports System
Class Base
    Protected i As Integer = 5
    Public Sub Print()
        Console.WriteLine("i is {0}", i)
    End Sub
End Class
Class Derived
    Inherits Base
    Dim d As Double = 7.3
    Public Sub PrintBoth()
        Console.WriteLine("i is {0}, d is {1}", i, d)
    End Sub
End Class
Class Tester
    Shared Sub Main()
        Dim b As New Base()
        Console.WriteLine("b:")
        b.Print()

        Dim d As New Derived()
        Console.WriteLine("d:")
        d.Print()
        d.PrintBoth()
        Console.ReadLine()
    End Sub
End Class

Beachten Sie, dass die Abgeleitete Klasse über vier Member verfügt: i, d, Print() und PrintBoth(). Wenn wir einen Print()-Wert in Abgeleitet deklariert haben, würde die Print-Methode in der Basisklasse ausgeblendet, aber nicht eliminiert. (Wir könnten es trotzdem von einem abgeleiteten Klassenmember aufrufen, indem base aufgerufen wird . Print() in C# oder MyBase.Print() in Visual Basic .NET.)

In ASP.NET ist der Code für die beiden Klassen nahezu identisch: Da wir Console.WriteLine nicht verwenden können, ändern wir die Methoden so, dass die von String.Format formatierte Zeichenfolge zurückgegeben wird (die Parameter aufweist, die mit Console.WriteLine identisch sind), und benennen die Methoden um:

ASP.NET mit Visual Basic .NET (Sie können den Code anzeigen und diese Anwendung ausführen.)

Class Base
    Protected i As Integer = 5
    Public Function GetValue() As String
        Return String.Format("i is {0}", i)
    End Function
End Class
Class Derived
    Inherits Base
    Dim d As Double = 7.3
    Public Function GetBoth() As String
        Return String.Format("i is {0}, d is {1}", i, d)
    End Function
End Class

Die andere Änderung besteht darin, dass die Methodenaufrufe, die sich in main in der Konsolenanwendung befanden, in die Ereignishandler für die Schaltflächen verschoben werden:

Private Sub Button1_Click(ByVal sender As System.Object, _
       ByVal e As System.EventArgs) Handles Button1.Click
    Dim b As New Base()
    Label1.Text = "b's GetValue: " + b.GetValue()
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, _
       ByVal e As System.EventArgs) Handles Button2.Click
    Dim d As New Derived()
    Label2.Text = "d's GetValue: " + d.GetValue()
    Label3.Text = "d's GetBoth:  " + d.GetBoth()
End Sub

Private Sub Button3_Click(ByVal sender As System.Object, _
       ByVal e As System.EventArgs) Handles Button3.Click
    Dim a = New ArrayList()
    a.Add(5)  ' converted automatically to Object (using boxing)
    Dim five As Integer = CType(a(0), Integer)
    ' object unboxed, put in five
    Label4.Text = String.Format( _
        "The integer {0} has been converted to Object and back " + _
        "(boxed and unboxed).", five)
End Sub

Das Formular ist wie erwartet: eine Reihe von ASP.NET Schaltflächen und Bezeichnungen, z. B.:

ASP.NET (Der vollständige Code wird angezeigt.)

      <form id="Form1" method="post" runat="server">
         <asp:Button id="Button1" runat="server" 
            Text="Show base class's value"></asp:Button>&nbsp;
         <asp:Label id="Label1" runat="server">
            What will Label1 show?</asp:Label>

... usw.

Einzelne Vererbung nur der Implementierung

Die .NET Framework unterstützt nur eine einzelne Vererbung: In jeder bestimmten Klasse können Sie nur von einer Basisklasse ableiten. Diese Basisklasse wurde jedoch möglicherweise von einer anderen abgeleitet usw. – bis hin zu Object. (Obwohl Sie nur von einer Klasse direkt erben können, kann Ihre Klasse eine beliebige Anzahl von Schnittstellen implementieren. Darüber werden wir später sprechen.)

Jedes Objekt hat genau einen Typ. Der Typ des Objekts stellt genau dar, um welche Art von Objekt es sich handelt. Denken Sie daran, dass Typen von anderen Typen abgeleitet werden können. Für instance wird der Typ CaseInsensitiveSortedList von SortedList abgeleitet, die von Object abgeleitet ist.

Obwohl die vollständige Typidentität eines CaseInsensitiveSortedList-ObjektsCaseInsensitiveSortedList ist, ist es auch eine SortedList und ein Object und kann so behandelt werden, als ob es eine dieser Klassen wäre, indem Code, der allgemeiner sein möchte.

Für instance können Sie einige Methoden schreiben, die wissen, wie mit SortedList-Objekten oder mit Objekten im Allgemeinen umzugehen ist (alle Objekte werden von Object abgeleitet). Dieser Code kann auch mit CaseInsensitiveSortedList-Objekten oder einer anderen Klasse verwendet werden, die von der SortedList abgeleitet ist, d. h. von jedem Typ, den die Methode verwendet.

Die Konvertierung eines abgeleiteten Typs in einen seiner Basistypen erfolgt automatisch. Für instance, wenn eine Methode für die Aufnahme eines Basisklassenobjekts definiert war:

C#

   void Foo(Base b) {
      // ...
   }

Visual Basic .NET

   Sub Foo(ByVal b as Base)
      ' ...
   End Sub

Wir könnten ein abgeleitetes Objekt übergeben:

C#

   Derived d = new Derived();
   Foo(d);

Visual Basic .NET

   Dim d as New Derived()
   Foo(d)

... ohne explizite Konvertierung. Dies ist möglich, da es immer sicher ist, ein abgeleitetes Objekt so zu behandeln, als wäre es ein Basisklassenobjekt.

Das Gegenteil ist nicht der Fall: Sie schlagen fehl, wenn Sie versuchen, eine abgeleitete Methode für ein Basisklassenobjekt aufzurufen. Sie können das Objekt jedoch explizit in den abgeleiteten Typ umwandeln. Wenn das Objekt wirklich dieser Typ ist (oder ein von ihm abgeleiteter Typ), ist die Umwandlung erfolgreich. Ist dies nicht der Fall, wird eine Ausnahme ausgelöst.

Sie können auch überprüfen, ob ein Objekt in einen anderen Typ mit dem is-Operator in C# oder dem TypeOf... Ist Operator in Visual Basic .NET. Beide geben true zurück, wenn das Objekt vom angegebenen Typ oder einem von diesem Typ abgeleiteten Typ ist. Beispiel:

C#

   Derived d = new Derived();
   Console.WriteLine(d is Derived);   // true
   Console.WriteLine(d is Base);      // true
   Console.WriteLine(d is object);   // true
   Console.WriteLine(d is int);      // false

Visual Basic .NET

   Dim d as New Derived()
   Console.WriteLine(TypeOf d is Derived)   ' true
   Console.WriteLine(TypeOf d is Base)      ' true
   Console.WriteLine(TypeOf d is Object)    ' true
   Console.WriteLine(TypeOf d is Integer)   ' false

Wir sind alle Objekte

Sie werden oben feststellen, dass alle Objekte letztendlich von Object/Object abgeleitet sind – wie Sie auch bemerken werden, ist/TypeOf... Der Operator gibt true zurück, wenn überprüft wird, ob ein Objekt vom Typ Object ist bzw. vom Typ Object abgeleitet ist .

Da Object die ultimative Basisklasse aller Klassen ist, werden Methoden häufig deklariert, um Object-Objekte zu übernehmen, wenn sie für einen beliebigen Typ generisch sind. Da alle Typen, auch integrierte Typen wie int, implizit in Object konvertiert werden können (dazu später mehr), können solche Methoden einen beliebigen Parametertyp verwenden. (Beachten Sie, dass die Möglichkeit, integrierte Typen in Objekte zu konvertieren, Boxing verwendet, wie zuletzt erläutert. Diese Funktion ist praktisch, aber selten und wird in der .NET Framework, aber nicht in "Brand J" unterstützt.

Das Behandeln verschiedener Typen als Object wird häufig in Auflistungen von Objekten verwendet. Sie können ein Objekt in eine Auflistung einfügen, ohne es umzuverteilen, da die Add-Funktion in der Regel einen Object-Parameter verwendet und es eine implizite Konvertierung von jedem Typ in Object gibt. Die get-Methode gibt jedoch auch Object zurück. Wenn Sie Ihr Objekt also wieder aus der Auflistung abrufen, müssen Sie den Objektverweis wieder auf den Typ umwandeln, den es tatsächlich ist, damit Sie einen entsprechend typisierten Verweis speichern und verwenden können.

Im folgenden Beispiel ist ArrayList eine Standardklasse .NET Framework, die eine Liste von Objects enthält. Wir fügen ein Objekt, ein boxed int/Integer, in die ArrayList ein und ruft es ab. Der Verweis, den wir abrufen, ist auf ein Objekt/Objekt. Daher müssen wir es in int/Integer umwandeln, um es einer ordnungsgemäß typisierten Variablen zuweisen zu können. Beachten Sie, dass die Umwandlung eine Ausnahme auslöst, wenn das Objekt nicht in int/Integer umgewandelt werden kann.

Außerdem mussten wir mithilfe von/ImportsSystem.Collections auf ArrayList zugreifen.

C# (Siehe Code.)

   ArrayList a = new ArrayList();
   a.Add(5);   // converted automatically to Object (using boxing)
   int five = (int)a[0];   // object unboxed, put in five

Visual Basic .NET (Sie können den vollständigen Code für die Konsole und für ASP.NET anzeigen.)

   Dim a = New ArrayList()
   a.Add(5)      ' converted automatically to Object (using boxing)
   Dim  five as Integer = CType(a[0], Integer)
   ' object unboxed, put in five

Polymorphismus, Virtual und Außerkraftsetzungen

Eines der leistungsfähigeren Konzepte in der objektorientierten Programmierung ist Polymorphismus. Die Wurzeln dieses Worts, "poly-" und "-morph" bedeuten, wenn man es zusammen setzt, "viele Formen". Insbesondere kann das Verhalten eines polymorphen Methodenaufrufs viele Formen annehmen, auch wenn er mit genau demselben Code aufgerufen wird – abhängig vom Typ des aufgerufenen Objekts.

Betrachten Sie als Beispiel die ToString-Methode . Int32.ToString gibt eine Zeichenfolge zurück, die die Zeichenfolgendarstellung der ganzen Zahl enthält. Double.ToString gibt eine Zeichenfolge zurück, die die Zeichenfolgendarstellung einer Gleitkommazahl enthält. Das Standardverhalten von ToString besteht darin, den Namen des Typs zurückzugeben. Offensichtlich implementieren verschiedene Klassen dieses Verhalten unterschiedlich – mit vielen, vielen Formen. (In Kürze wird erläutert, wie Ihre Klassen ihre eigene ToString-Methode implementieren können.)

Direkt anrufen

Der zu befolgende Code ist also ziemlich einfach. Wir erstellen eine Reihe von Objekten und rufen ToString für jedes auf:

C# (Siehe Code.)

   object o = new object();
   int i = 5;
   double d = 3.7;
   YourType y = new YourType();  // has its own ToString
   Console.WriteLine("o: {0}, i: {1}, d: {2}, y: {3}", 
                     o.ToString(), i.ToString(),
                     d.ToString(), y.ToString()
   );

Visual Basic .NET (Sie können den vollständigen Code für die Konsole und für ASP.NET anzeigen und diese Anwendung ausführen.)

    Dim o As New Object()
    Dim i As Integer = 5
    Dim d As Double = 3.7
    Dim y As New YourType()   ' has its own ToString
    Console.WriteLine("o: {0}, i: {1}, d: {2}, y: {3}", _
          o.ToString(), i.ToString(), _
          d.ToString(), y.ToString())

Die Ausgabe in beiden Fällen lautet:

   o: System.Object, i: 5, d: 3.7, y: YourType's ToString!

Beachten Sie, dass sich die ASP.NET Anwendung beim Festlegen einer Bezeichnung mithilfe von String.Format (anstelle des Aufrufens von Console.WriteLine) und beim Behandeln von Schaltflächenklicks unterscheidet, genau wie in der Vererbungsanwendung.

Jeder Aufruf von ToString ruft eine andere Methode auf , die für diese Klasse geeignet ist. Und es ist leicht zu erkennen, welche Methode aufgerufen wird: Sehen Sie sich einfach den Typ des Objekts an, für das ToString aufgerufen wird.

Aufrufen eines Verweises auf eine Basisklasse

Angenommen, wir hatten eine praktische Hilfsfunktion zum Schreiben einer Variablen. Wir nennen es WriteNameValue und implementieren sie wie folgt. Da der zweite Parameter ein Object ist, können wir einen beliebigen Typ an ihn übergeben:

C# (Siehe Code.)

   static public void WriteNameValue(String name, Object value) {
      Console.WriteLine("{0}: {1}", name, value.ToString());
   }

Visual Basic .NET (Siehe Code.)

Visual Basic .NET
    Public Sub WriteNameValue(ByVal name As String, ByVal value As Object)
        Console.WriteLine("{0}: {1}", name, value.ToString())
    End Sub

ASP.NET mit Visual Basic .NET (Der vollständige Code wird angezeigt.)

Public Function WriteNameValue(ByVal name As String, _
       ByVal value As Object) As String
    Return String.Format("{0}: {1}", name, value.ToString())
End Function

Angenommen, wir haben vier Objekte deklariert (wie oben) und WriteNameValue vierMal aufgerufen:

(Sie können diesen Code für Visual Basic .NET und für C# anzeigen. Fügen Sie für C# am Ende jeder Zeile ein Semikolon hinzu.)

   WriteNameValue("o", o)
   WriteNameValue("i", i)
   WriteNameValue("d", d)
   WriteNameValue("y", y)

In ASP.NET verwenden wir den Rückgabewert der WriteNameValue-Methode und verketten ihn mit einer Zeichenfolge mit Zeilenumbrüchen, bevor die Zeichenfolge in ein Textfeld eingefügt wird.

ASP.NET (Der vollständige Code wird angezeigt.)

   TextBox1.Text = _
        WriteNameValue("o", o) + Chr(13) + _
        WriteNameValue("i", i) + Chr(13) + _
        WriteNameValue("d", d) + Chr(13) + _
        WriteNameValue("y", y)

Der Code in WriteNameValue bleibt für alle vier Aufrufe gleich. Es kann sich sogar in einer anderen Assembly befinden, die separat kompiliert wird. Werden wir also immer toString in Object aufrufen? Schließlich ist der Verweistyp Object, unabhängig vom Typ des Objekts, auf das sich der Verweis tatsächlich bezieht. Sollten die vier Aufrufe also nicht viermal "Objekt" ausgeben? Es ist vielleicht nicht das, was wir möchten, aber es ist sinnvoll, zumindest basierend auf dem Typ des Verweises ( in diesem Fall Object ) zu wählen, welches ToString aufgerufen werden soll. Tatsächlich ist dies das Standardverhalten für die .NET Framework (genau wie in C++).

Wenn Sie dieses Programm jedoch ausführen, werden Sie feststellen, dass es sich anders verhält– dass die vier verschiedenen Aufrufe des exakt gleichen Codes in WriteNameValue vier verschiedene Methodenaufrufe geben, die jeweils vom tatsächlichen Typ des übergebenen Objekts abhängen. Anders ausgedrückt: Viele Formen des Methodenaufrufs werden ausgeführt. Das ist genau das, was wir wollen. Aber warum funktioniert es?

Nun, wenn Sie sich die Dokumentation (oder die Metadaten) für ToString ansehen (weitermachen, tun Sie es!), werden Sie sehen, dass es als virtuell (überschreibbar in Visual Basic .NET) deklariert ist. Dies bedeutet, dass Sie beim Aufrufen dieser Methode unabhängig vom Typ des Verweises, den Sie für den Aufruf verwenden (sogar Object), immer die am häufigsten abgeleitete Verfügbare Methode aufrufen, d. h. die richtige methode für den tatsächlichen Typ des Objekts.

Methoden oder Eigenschaften können virtuell/überschreibbar sein. In einer angegebenen Klassenhierarchie wird der virtuelle/überschreibbare Schlüsselwort (keyword) angezeigt, wenn die Methode zum ersten Mal deklariert wird (in der meisten Basisklasse). Sie darf niemals in einer Methode derselben Signatur in einer abgeleiteten Klasse angezeigt werden. Stattdessen MÜSSEN Sie die Schlüsselwort (keyword) außer Kraft setzen, um dem Compiler mitzuteilen, dass Sie die Implementierung der Basisklasse explizit überschreiben.

Wenn Sie die Schlüsselwort (keyword) außer Kraft setzen, blenden Sie die Basisklassenimplementierung aus (oder überschatten), anstatt sie zu überschreiben, sodass Sie nicht die Ergebnisse erhalten, die Sie von polymorphen Aufrufen erwarten. Wenn Sie dies im obigen Programm getan haben, funktioniert das Aufrufen von ToString mit dem Objekt selbst ordnungsgemäß, aber wenn Sie es über einen Verweis vom Typ Object aufrufen, wird die -Methode der Basisklasse aufgerufen, nicht die am häufigsten abgeleitete Methode (insbesondere wird Object.ToString aufgerufen, das den Typnamen des Objekts ausgibt, "YourType"). Laden Sie den Code, und probieren Sie ihn aus! Beachten Sie außerdem, dass Sie beim Kompilieren eine Warnung und keinen Fehler erhalten, wenn Sie Außerkraftsetzungen/Außerkraftsetzungen vergessen.

Lassen Sie uns unser früheres Base/Derived-Beispiel überarbeiten, um die Vorteile virtueller (überschreibbarer) Methoden zu nutzen:

C# (Siehe Code.)

// compile with: csc PolyExampleCS.cs
   class BaseClass {
      int i = 5;
      public virtual void Print() {
         Console.WriteLine("i is {0}", i);
      }
      public override String ToString() {
         return i.ToString();
      }
   }
   class DerivedClass : BaseClass {
      double d = 2.1;
      public override void Print() {
         base.Print();
         Console.WriteLine("d is {0}", d);
      }
      public override String ToString() {
         return base.ToString() + " " + d.ToString();
      }
   }
   class TestBaseDerived {
      public static void Main() {
         BaseClass b = new BaseClass();
         DerivedClass d = new DerivedClass();
         BaseClass bd = new DerivedClass();   // note this!
         b.Print();                   // BaseClass.Print()
         d.Print(); bd.Print();       // BOTH Derived.PrintClass()
         Console.WriteLine("b: {0}, d: {1}, bd: {2}", b, d, bd);
      }
   }

Visual Basic .NET (Siehe Code.)

' compile with: vbc PolyExampleVB.vb
Class BaseClass
    Dim i As Integer = 5
    Public Overridable Sub Print()
        Console.WriteLine("i is {0}", i)
    End Sub
    Public Overrides Function ToString() As String
        Return i.ToString()
    End Function
End Class
Class DerivedClass
    Inherits BaseClass
    Dim d As Double = 2.1
    Public Overrides Sub Print()
        MyBase.Print()
        Console.WriteLine("d is {0}", d)
    End Sub
    Public Overrides Function ToString() As String
        Return MyBase.ToString() + " " + d.ToString()
    End Function
End Class
Class TestBaseDerived
    Public Shared Sub Main()
        Dim b As New BaseClass()
        Dim d As New DerivedClass()
        Dim bd As BaseClass = New DerivedClass() ' note this!
        b.Print()    ' BaseClass.Print()
        d.Print()
        bd.Print()   ' BOTH Derived.PrintClass()
        Console.WriteLine("b: {0}, d: {1}, bd: {2}", b, d, bd)
        Console.ReadLine()
    End Sub
End Class

Die ASP.NET Version ist identisch mit der Visual Basic .NET-Version, mit der Ausnahme, dass die Methoden, die Console.WriteLine aufrufen, wie im Vererbungsbeispiel geändert werden, um stattdessen die Zeichenfolge zu formatieren und zurückzugeben. Beispiel:

ASP.NET mit Visual Basic .NET (Sie können den vollständigen Code anzeigen und diese Anwendung ausführen.)

' in class BaseClass
    Public Overridable Function GetValue() As String
        Return String.Format("i is {0}", i)
    End Function

Und natürlich werden die Anweisungen in Main in der Konsolenanwendung in verschiedene Schaltflächen-Klick-Handler verschoben. Beispiel:

ASP.NET mit Visual Basic .NET (Der vollständige Code wird angezeigt.)

Private Sub Button1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
    Dim b As New BaseClass()
    Label1.Text = "b: " + b.GetValue()    ' BaseClass.GetValue()
End Sub

Sie können auch die ASPX-Datei sehen.

Beachten Sie, wie die Schlüsselwörter virtual/Overridable, override/Overrides und base/MyBase verwendet werden. Das Muster einer abgeleiteten Klassenmethode, die eine Basisklassenmethode aufruft, ist äußerst häufig – manchmal am Anfang, manchmal am Ende, manchmal in der Mitte.

Beachten Sie unter Main, wie wir über einen BaseClass-Verweis ( bd) verfügen, der auf ein DerivedClass-Objekt verweist. Wenn wir die Print-Methode aufrufen, wird die abgeleitete Methode aufgerufen, da Print virtuell/überschreibbar ist.

Polymorphismus ist am leistungsfähigsten, wenn Sie Ihre Klassen so erweitern können, dass sie in ein vorhandenes Framework passen. Beachten Sie in diesem Beispiel, dass wir unsere eigenen Außerkraftsetzungen von ToString bereitgestellt haben. Daher können wir unsere Objekte im Standardaufruf Console.WriteLine verwenden – und das funktioniert!

Ein weiteres Beispiel ist die Möglichkeit, das Verhalten eines Steuerelements (entweder Microsoft Windows® Forms oder Web Form) durch Ableiten von der .NET Framework bereitgestellten Basisklasse zu überschreiben und dann die Zu ändernden Methoden wie OnInit oder OnCreateControl zu überschreiben.

Und das letzte Beispiel, das wir besprechen, ist die Möglichkeit, Dispose außer Kraft zu setzen, damit die Runtime Ihre Dispose-Methode aufruft, wenn das Objekt bereinigt werden muss – vielleicht sogar von einer using-Anweisung in C#!

In unserem Beispiel mit ToString wird die Version von Console.WriteLine aufgerufen, die eine Zeichenfolge und eine Reihe von Object-Objekten akzeptiert. Jede von b, d und bd wird mithilfe der impliziten Konvertierung in Object konvertiert und übergeben.

Console.WriteLine ruft ToString für jedes übergebene Objekt auf, um eine Zeichenfolgendarstellung des Objektwerts abzurufen. (Es gibt weitere Optionen. Weitere Informationen finden Sie in der Dokumentation.) Da ToString eine virtuelle Methode in Object ist, werden unsere ToString-Außerkraftsetzungen aufgerufen, und das Leben ist wunderbar.

Wenn Sie übrigens jemals eine virtuelle Methode in Ihrer Basisklasse ausblenden und nicht außer Kraft setzen möchten, können Sie die Schlüsselwort (keyword) außer Kraft setzen. Wenn Sie dies tun, erhalten Sie jedoch eine Warnung. Um die Warnung zu vermeiden, und um Ihre Absicht klar zu machen, verwenden Sie die neue Schlüsselwort (keyword)/Schatten anstelle der außer Kraft setzen Schlüsselwort (keyword).

Klassen können abstrakt (MustInherit) oder Sealed (NotInheritable) sein.

Eine abstrakte/MustInherit-Klasse kann nicht instanziiert werden, was bedeutet, dass keine Instanzen dieser Klasse erstellt werden können. Warum sollten Sie so etwas wollen?

Sie markieren eine Klasse abstract (MustInherit in Visual Basic .NET), wenn sie nur eine Basisklasse für eine andere Klasse sein soll. Der übliche Grund dafür ist, dass Sie über einige allgemeine Funktionen verfügen, für die Sie generischen Code schreiben können, aber sie können keine konkrete Implementierung einer oder mehrerer Methoden und/oder Eigenschaften schreiben.

Das kanonische Beispiel ist eine Reihe von Objekten, die Sie zeichnen können: Kreise, Rechtecke, Linien, Punkte usw. Es wäre praktisch, eine generische drawable-Objektklasse zu haben, um jedes zeichnungsfähige Objekt darzustellen. Die Draw-Methode in der generischen Klasse muss jedoch abstrakt sein, da keine generische Implementierung möglich ist.

Wenn Sie ein bestimmtes drawable-Objekt von der generischen drawable-Objektklasse ableiten, implementieren Sie die richtige Draw-Methode . Dies hat den Vorteil, dass Sie jedes zeichnungsfähige Objekt als generisches zeichnungsfähiges Objekt behandeln können, sodass Sie für instance Sammlungen dieser Objekte haben und einfach die Auflistung durchlaufen können, wobei jedes Objekt ohne Rücksicht auf seinen tatsächlichen Typ gezeichnet wird.

Im nächsten Artikel finden Sie ein Beispielprogramm, das genau dies tut.

Diese Methoden und Eigenschaften, die Sie nicht implementieren können, werden als abstrakte Methoden (oder MustOverride in Visual Basic .NET) und/oder Eigenschaften bezeichnet. Sie können die Schnittstelle in die abstrakte/MustInherit-Basisklasse schreiben, aber nicht die -Implementierung. Eine abstrakte Methode oder Eigenschaft wird deklariert, aber nicht in dieser Klasse implementiert und ist automatisch virtuell/überschreibbar. In der geerbten Klasse müssen Sie die abtract/MustOverride-Methoden und -Eigenschaften implementieren, es sei denn, die geerbte Klasse ist ebenfalls abstrakt. In jedem Fall müssen Sie, bevor Sie ein tatsächliches Objekt erstellen können, über eine konkrete (nicht abstrakte) Klasse verfügen, was bedeutet, dass es keine verbleibenden abstrakten/MustOverride-Methoden oder -Eigenschaften gibt.

Die versiegelte/NotInheritable-Schlüsselwort (keyword) hat eine nahezu entgegengesetzte Bedeutung: Sie besagt, dass Sie von dieser Klasse nicht erben können. Das Versiegeln einer Klasse teilt Ihren Benutzern mit, dass Sie nicht erwarten, dass sie von Ihnen erben. Die ordnungsgemäße Verwendung von sealed/NotInheritable kann auch dazu führen, dass Ihr Code effizienter ausgeführt wird, da Aufrufe von virtuellen/überschreibbaren Funktionen in Ihrer Klasse in effizientere direkte Aufrufe konvertiert werden können.

Sie können auch einzelne Methoden und Eigenschaften versiegeln (NotOverridable in Visual Basic .NET). Dadurch können Sie das Überschreiben einer bestimmten Methode in abgeleiteten Klassen beenden und gleichzeitig abgeleitete Klassen zulassen.

Offensichtlich kann eine Klasse nicht sowohl abstrakt/MustInherit als auch sealed/NotInheritable sein; ebenso wenig kann eine Methode oder Eigenschaft sowohl abstrakt/MustOverride als auch sealed/NotOverridable sein. (Die Visual Basic .NET-Schlüsselwörter machen dies wirklich offensichtlich, oder?)

Schnittstellen

Manchmal ist es einem egal, was ein Objekt ist – man kümmert sich nur darum, was es tut. Und es ist möglich, dass Klassen, die nicht in der Vererbungsstruktur verknüpft sind (mit der Ausnahme, dass sie beide von Object abgeleitet sind) einige allgemeine Funktionen implementieren. Diese allgemeine Funktionalität kann in einer Schnittstelle gruppiert werden , die keine oder mehr abstrakte Methoden enthält. Visual Basic- und COM-Programmierer werden Schnittstellen kennen.

Sie verfügen fast immer über mindestens eine Methode in einer Schnittstelle in .NET Framework Programmen. Wenn Sie nur eine leere Schnittstelle verwenden möchten, um eine Klasse als mit einem Attribut zu markieren, sollten Sie stattdessen ein Attribut verwenden (z. B. das Serializable-Attribut , das Wir müssen Objekte als Sitzungs- oder Ansichtszustand freigeben können). Wir werden ein anderes Mal mehr über Attribute sprechen.

Erstellen und Implementieren einer Schnittstelle

Als Beispiel erstellen wir eine Schnittstelle namens ICopyable, die die Methode beschreibt, die zum Kopieren eines Objekts erforderlich ist (beachten Sie, dass es eine Standardschnittstelle namens ICloneable gibt, die dasselbe tut):

C# (Siehe Code.)

   interface ICopyable {
      object Copy(); // returns a reference to a copy of the object
   }

(Sie können diesen Code für Visual Basic .NET und für ASP.NET anzeigen. Der Code ist für beide identisch.)

Interface ICopyable
    Function Copy() As Object   ' returns ref to a copy of the object
End Interface

Die Copy-Methode ist wie alle Methoden in einer Schnittstelle automatisch öffentlich und abstrakt.

Klassen, die die Möglichkeit zum Kopieren bieten wollten, könnten ICopyable implementieren:

C# (Siehe Code.)

   class Hoo : ICopyable {
      public int i; // using public ONLY for demo since shorter!
      public Hoo(int i) {
         this.i = i;
      }
      public object Copy() {
         return new Hoo(i);
      }
   }
   class TestHoo {
      public static void Main() {
         Hoo h1 = new Hoo(5);
         Hoo h2 = (Hoo)h1.Copy(); // note cast necessary
         Console.WriteLine("Original: {0}; Copy: {1}", h1.i, h2.i);
         Console.ReadLine();
      }
   }

Visual Basic .NET (Der Code wird angezeigt, ASP.NET Code für die Hoo-Klasse identisch ist.)

Class Hoo
    Implements ICopyable
    Public i As Integer ' using public ONLY for demo since shorter!
    Public Sub New(ByVal i As Integer)
        Me.i = i
    End Sub
    Public Function Copy() As Object Implements ICopyable.Copy
        Return New Hoo(i)
    End Function
End Class
Class TestHoo
    Public Shared Sub Main()
        Dim h1 As New Hoo(5)
        Dim h2 As Hoo = CType(h1.Copy(), Hoo) ' note cast necessary
        Console.WriteLine("Original: {0}; Copy: {1}", h1.i, h2.i)
        Console.ReadLine()
    End Sub
End Class

Die Copy-Methode dieser Klasse ist einfach, aber komplexere Klassen mit Verweisen auf andere Objekte, z. B. Datensammlungen, wären viel komplizierter.

Da wir Console.WriteLine nicht verwenden, ist der ASP.NET Code identisch, mit Ausnahme der Klasse TestHoo, die wie zuvor als Klickereignishandler für Schaltflächen ausgeführt wird. (Sie können den Visual Basic .NET- und ASPX-Code anzeigen und diese Anwendung ausführen.)

Details zur Implementierung

Der Code ist in beiden Sprachen sehr ähnlich, aber die Art und Weise, wie Sie dem Compiler mitteilen, welche Methode in Ihrer Klasse Sie verwenden, um zu implementieren, welche Methode in welcher Schnittstelle implementiert wird, ist in den beiden Sprachen sehr unterschiedlich.

In Visual Basic .NET müssen Sie explizit angeben, welche Methode in welcher Schnittstelle Ihre Methode (in diesem Fall Copy) implementiert. Da Sie dies explizit angeben, können Sie die Methode beliebig benennen, obwohl es am häufigsten ist, sie mit dem Namen der Methode zu benennen, die sie implementiert. Sie können auch eine Methode in Ihrer Klasse als Implementierung für beliebig viele Methoden verwenden.

C# verwendet den Namen der Methode, die Sie schreiben, um Ihre Implementierung mit der Methode (und Schnittstelle) abzugleichen, die Sie implementieren. Sie können den Namen der Schnittstelle angeben, die Sie implementieren. Dies wird als explizite Implementierung bezeichnet. Wenn Sie dies tun, muss die Methode privat sein, damit sie nur über einen Schnittstellenverweis aufgerufen werden kann, nicht mit einem -Objekt direkt. Die explizite Implementierung ermöglicht es Ihnen auch, mehr als eine Schnittstelle zu implementieren, die Methoden mit derselben Signatur enthält.

Hinweis zur ICopyable-Schnittstelle : Da es eine Standardschnittstelle namens ICloneable gibt, die eine Methode namens Clone definiert, die denselben Auftrag wie mit ICopyable.Copy ausführt, sollten Sie ICloneable anstelle unserer benutzerdefinierten Schnittstelle verwenden. Die Verwendung der Standardschnittstelle ist weit überlegen, da sie bedeutet, dass Ihre Klasse von jedem Code verwendet werden kann, der die Standardschnittstelle verwendet. Wir haben unsere eigene ICopyable-Schnittstelle nur zu Demonstrationszwecken geschrieben.

Schnittstellenpolymorphismus

Ebenso wie eine implizite Konvertierung von einer abgeleiteten Klasse in eine ihrer Basisklassen erfolgt, gibt es auch eine implizite Konvertierung von einer Klasse in eine der Schnittstellen, die sie oder ihre Basisklassen implementieren. Daher könnten wir eine Methode schreiben, die jedesICopyable-Objekt akzeptiert:

C# (Siehe Code.)

   static Object CopyMe(ICopyable ic) {
      return ic.Copy();
   }

   // in some other method...
   Hoo h3 = new Hoo(7);
   Hoo h4 = (Hoo)CopyMe(h3);
   // some other class that implements ICopyable
   Woo w3 = new Woo();
   Woo w4 = (Woo)CopyMe(w3); // EXACT SAME METHOD as above!

Visual Basic .NET (Sie können den Code sehen; der ASP.NET Code ist identisch.)

    Public Shared Function CopyMe(ByVal ic As ICopyable) As Object
        Return ic.Copy()
    End Function

    ' in some other method...
    Dim h3 As New Hoo(7)
    Dim h4 As Hoo = CType(TestCopy.CopyMe(h3), Hoo) ' method call
    Dim w5 As New Woo(3.8) ' some other class that implements ICopyable
    Dim w6 As Woo = CType(TestCopy.CopyMe(w5), Woo) 
    ' EXACT SAME METHOD as above!

(Für ASP.NET können Sie hier für Visual Basic .NET klicken und hier für ASPX klicken, um den vollständigen Code anzuzeigen.)

Auch hier würden Sie die Standardschnittstelle ICloneable verwenden, anstatt eine eigene für diesen Fall zu schreiben.

Hier ist ein weiteres Beispiel: Es gibt eine IComparable-Schnittstelle , die eine CompareTo-Methode definiert, die zwei Objekte desselben Typs vergleicht. Jede Klasse, die Vergleiche kleiner/gleich/größer als unterstützt, kann die IComparable-Schnittstelle (und die zugehörige CompareTo-Methode ) implementieren. Objekte, deren Klassen diese Schnittstelle implementieren, können in sortierten Datenstrukturen verwendet werden, indem sie einfach hinzugefügt werden: Das Datenstrukturobjekt kümmert sich um den Aufruf der CompareTo-Methode , indem jedes Objekt in seine IComparable-Schnittstelle umgewandert wird.

Durch das gleiche Token verfügen Klassen, die IFormattable implementieren, über ihre Format-Methode , die von der Überladung von Console.WriteLine aufgerufen wird, die eine Formatierungszeichenfolge verwendet (anstelle von ToString, die nur aufgerufen wird, wenn das Objekt IFormattable nicht implementiert).

Wie Sie sehen können, ist es Ihnen egal, was das Objekt ist, wenn Sie Code schreiben, um mit Schnittstellen zu arbeiten. Die Vererbungsliste des Objekts gibt an, was das Objekt ist. Seine Schnittstellenimplementierungsliste gibt an, was er tun kann. Daher kann ein Objekt, das IFormattable implementiert, formatiert werden. eine, die IConvertable implementiert, kann konvertiert werden. IClonable kann geklont (kopiert) werden usw.

In C# können Sie überprüfen, ob ein Objekt in eine bestimmte Klasse oder Schnittstelle umgewandelt werden kann, indem Sie den is-Operator wie in "if (o is Hoo)" oder "if (o is IClonable)" verwenden. In Visual Basic .NET verwenden Sie typeOf... Ist Operator auf ähnliche Weise, wie in "If TypeOf o is Hoo Then ...". Es wird als schlechter objektorientierter Programmierstil angesehen, um überprüfen zu müssen, ob ein Objekt ein bestimmter Typ ist, aber manchmal kann ein Hack wie dieser viel Trauer sparen, also ist es das kleinere von zwei Übeln. Dr. GUI versucht jedoch, dies zu vermeiden.

Schnittstellen im Vergleich zu abstrakten Basisklassen

Eines der interessanten Argumente ist, ob Sie eine Schnittstelle oder eine abstrakte Klasse verwenden sollten, um einen Satz von Methoden und Eigenschaften zu beschreiben, die geerbte Klassen implementieren müssen.

Dr. GUI löst dies mithilfe der Regel "IS A": Wenn der wesentliche Typ der abgeleiteten Klassen "IS A" Spezialisierung des Basistyps, dann erben Sie von einer abstrakten Klasse. Verwenden Sie eine Schnittstelle, wenn die Schnittstelle nicht mit dem wesentlichen Typ der tatsächlichen Objekte verknüpft ist, da Schnittstellen ausdrücken, was ein Objekt "KANN", nicht das, was es im Wesentlichen ist.

Da also ein generisches zeichnungsfähiges Kreisobjekt "IS A" ist, leiten wir die circle-Klasse von der abstrakten Basisklasse des generischen drawable-Objekts ab, um diese Beziehung auszudrücken. Aber der Kreis auch "CAN DO"-Klonen, sodass wir die IClonable-Schnittstelle implementieren.

Es gibt auch andere Möglichkeiten, dies zu betrachten. Für instance können Sie vielleicht feststellen, dass Sie eine partielle Implementierung einer abstrakten Klasse erben können, aber keine Implementierung einer Schnittstelle erben können. Daher können Sie möglicherweise eine Schnittstelle bevorzugen, wenn keine gemeinsame Implementierung zum Erben vorhanden ist, insbesondere wenn die Schnittstelle von einer Vielzahl von Klassen aus vielen Vererbungshierarchien implementiert werden kann (z. B. ICloneable). Auf der anderen Seite können Sie eine abstrakte Klasse für eng verwandte Klassen bevorzugen, die ihre Implementierungen gemeinsam nutzen und die Sie gleichzeitig mit einer Version erstellen möchten.

Weitere Informationen hierzu finden Sie in den Empfehlungen in der Visual Basic- und Microsoft Visual C#-™ Produktdokumentation. Möglicherweise möchten Sie auch den gesamten Abschnitt zur Programmierung mit Komponenten lesen.

Ein wichtiger Hinweis: Seien Sie vorsichtig, wenn Sie Ihre Schnittstellen entwerfen. Sobald Sie sie veröffentlicht haben, sollten Sie sie nicht ändern, auch nicht, um eine Methode hinzuzufügen. (Wenn Sie eine Methode hinzufügen, muss jede Klasse, die Ihre Schnittstelle implementiert, auch diese Methode implementieren, und sie wird unterbrochen, bis dies der Fall ist.) Sie können Jedoch Methoden zu Klassen hinzufügen, ohne abgeleitete Klassen zu unterbrechen.

Versuch es doch mal!

Wenn Sie .NET haben, ist es die Möglichkeit, es zu lernen, es auszuprobieren ... und wenn nicht, erwägen Sie es. Wenn Sie eine Stunde oder so eine Woche mit Dr. GUI .NET verbringen, sind Sie ein Experte im .NET Framework, bevor Sie es wissen.

Seien Sie der Erste in Ihrem Block – und laden Sie einige Freunde ein!

Es ist immer gut, der Erste zu sein, der eine neue Technologie lernt, aber noch mehr Spaß, dies mit einigen Freunden zu tun. Organisieren Sie für mehr Spaß eine Gruppe von Freunden, um gemeinsam .NET zu lernen!

Einige Dinge, die Sie ausprobieren können...

Testen Sie zunächst den hier gezeigten Code. Ein Teil davon stammt aus größeren Programmen; Sie müssen das Programm um diese Codeausschnitte herum aufbauen. Das ist eine bewährte Methode. (Oder verwenden Sie den Code, den der gute Arzt bereitstellt, wenn Sie müssen.) In beiden Fällen, spielen Sie mit dem Code einige.

Schreiben Sie einige geerbte Klassen, z. B. drei Ebenen tief, und probieren Sie die verschiedenen Schutzmodifizierer aus. Verwenden Sie sowohl virtuelle als auch nicht virtuelle Methoden und Eigenschaften.

Schreiben Sie eine Schnittstelle und ein kleines Framework, das diese Schnittstelle verwendet, und schreiben Sie dann einige nicht verwandte Typen, die Ihre Schnittstelle implementieren und zeigen, dass sie in Ihrem Framework gut funktionieren.

Was wir getan haben; Was kommt als nächstes

Dieses Mal haben wir die Vererbung und Polymorphie überprüft und gezeigt, wie die .NET Framework sie implementiert.

Beim nächsten Mal sehen wir ein vollständiges Beispiel für Vererbung, abstrakte Basisklassen und Schnittstellen in einer niedlichen Zeichnungsanwendung. Dies ist keine Konsolenanwendung. Da es grafisch ist, wird es stattdessen eine Windows Forms Anwendung sein. Die ASP.NET-Version veranschaulicht die Verwendung von benutzerdefinierten Bitmaps auf Webseiten . Dies ist in den meisten Webprogrammiersystemen schwierig, aber mit ASP.NET einfach.