Compile- vs. Runtime-Polymorphismus
Teil des Abschnitts Objektorientierte Programmierung der C#-Journey von Coddy — Lektion 25 von 70.
Polymorphismus bedeutet „viele Formen“ – die Fähigkeit desselben Codes, sich je nach Kontext unterschiedlich zu verhalten. C# unterstützt zwei verschiedene Arten von Polymorphismus, und das Verständnis darüber, wann jede davon aufgelöst wird, hilft Ihnen dabei, flexibleren Code zu schreiben.
Kompilierzeit-Polymorphismus (auch statische Polymorphie genannt) wird vom Compiler aufgelöst, bevor Ihr Programm ausgeführt wird. Methodenüberladung ist das primäre Beispiel – mehrere Methoden teilen sich denselben Namen, haben aber unterschiedliche Parameter:
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
}
var calc = new Calculator();
calc.Add(5, 3); // Compiler wählt die int-Version
calc.Add(5.0, 3.0); // Compiler wählt die double-VersionDer Compiler prüft die Argumenttypen und wählt die korrekte Methode zur Kompilierzeit aus. Diese Entscheidung steht fest, bevor das Programm jemals ausgeführt wird.
Laufzeitpolymorphismus (dynamische Polymorphie) wird aufgelöst, während das Programm läuft. Dies geschieht mit virtual und override Methoden – der tatsächliche Typ des Objekts bestimmt, welche Methode ausgeführt wird:
Animal pet = new Dog(); // Als Animal deklariert, tatsächlich ein Dog
pet.Speak(); // Ruft Speak() von Dog zur Laufzeit aufDer Compiler weiß nicht, welches Speak() ausgeführt wird – diese Entscheidung fällt zur Laufzeit basierend auf dem tatsächlichen Typ des Objekts. Das ist es, was vererbungsbasierte Polymorphie so mächtig macht, um flexible, erweiterbare Systeme zu entwickeln.
Aufgabe
EinfachLassen Sie uns ein Konvertersystem bauen, das beide Arten von Polymorphismus in Aktion demonstriert. Sie werden eine Klasse erstellen, die Methodenüberladung (Kompilierzeit-Polymorphismus) zusammen mit einer Vererbungshierarchie verwendet, die virtuelle/überschreibende Methoden (Laufzeit-Polymorphismus) nutzt.
Sie werden Ihren Code über vier Dateien organisieren:
Converter.cs: Definieren Sie eineConverter-Klasse im NamespaceConversion. Diese Klasse demonstriert Kompilierzeit-Polymorphismus durch Methodenüberladung. Erstellen Sie drei überladeneConvert-Methoden:Convert(int value)gibt"Integer: {value}"zurückConvert(double value)gibt"Double: {value}"zurückConvert(string value)gibt"String: {value}"zurück
Shape.cs: Definieren Sie eine BasisklasseShapeim NamespaceConversion. Diese Klasse hat eine EigenschaftName(string) und einen Konstruktor, der diese setzt. Fügen Sie einevirtualMethode namensDescribe()hinzu, die"This is a {Name}"zurückgibt.Circle.cs: Definieren Sie eine KlasseCircleim NamespaceConversion, die vonShapeerbt. Fügen Sie eine EigenschaftRadius(double) hinzu. Der Konstruktor akzeptiert einen Radius und übergibt"Circle"an den Basis-Konstruktor. Überschreiben SieDescribe(), um"This is a Circle with radius {Radius}"zurückzugeben.Program.cs: Demonstrieren Sie in Ihrer Hauptdatei beide Arten von Polymorphismus. Erstellen Sie zuerst einenConverterund rufen Sie alle drei überladenen Methoden mit Eingabewerten auf. Erstellen Sie dann einenCircle, speichern Sie ihn in einerShape-Variablen und rufen SieDescribe()auf, um Laufzeit-Polymorphismus zu zeigen.
Sie erhalten vier Eingaben:
- Einen Integer-Wert
- Einen Double-Wert
- Einen String-Wert
- Einen Kreisradius (double)
Geben Sie die Ausgabe in diesem Format aus:
Compile-time Polymorphism:
{Convert(int) result}
{Convert(double) result}
{Convert(string) result}
Runtime Polymorphism:
{Describe() result from Shape variable holding Circle}Wenn die Eingaben beispielsweise 42, 3.14, Hello und 5.5 sind, sollte die Ausgabe wie folgt aussehen:
Compile-time Polymorphism:
Integer: 42
Double: 3.14
String: Hello
Runtime Polymorphism:
This is a Circle with radius 5.5Beachten Sie den entscheidenden Unterschied: Der Compiler entscheidet zur Kompilierzeit anhand der Argumenttypen, welche Convert-Methode aufgerufen werden soll, während die Describe()-Methode zur Laufzeit basierend auf dem tatsächlichen Objekttyp aufgelöst wird, obwohl sie in einer Shape-Variablen gespeichert ist!
Spickzettel
Polymorphismus bedeutet „viele Formen“ – die Fähigkeit desselben Codes, sich je nach Kontext unterschiedlich zu verhalten.
Kompilierzeit-Polymorphismus (statischer Polymorphismus) wird vom Compiler aufgelöst, bevor das Programm ausgeführt wird. Methodenüberladung ist das primäre Beispiel:
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
}
var calc = new Calculator();
calc.Add(5, 3); // Compiler wählt die int-Version
calc.Add(5.0, 3.0); // Compiler wählt die double-VersionDer Compiler prüft die Argumenttypen und wählt die korrekte Methode zur Kompilierzeit aus.
Laufzeit-Polymorphismus (dynamischer Polymorphismus) wird während der Programmausführung mithilfe von virtual- und override-Methoden aufgelöst:
Animal pet = new Dog(); // Als Animal deklariert, tatsächlich ein Dog
pet.Speak(); // Ruft Speak() von Dog zur Laufzeit aufDer tatsächliche Typ des Objekts bestimmt, welche Methode zur Laufzeit ausgeführt wird, nicht der deklarierte Variablentyp.
Probier es selbst
using System;
using Conversion;
class Program
{
public static void Main(string[] args)
{
// Eingaben lesen
int intValue = Convert.ToInt32(Console.ReadLine());
double doubleValue = Convert.ToDouble(Console.ReadLine());
string stringValue = Console.ReadLine();
double radius = Convert.ToDouble(Console.ReadLine());
// TODO: Kompilierzeit-Polymorphismus demonstrieren
// Erstellen Sie ein Converter-Objekt und rufen Sie alle drei überladenen Convert-Methoden auf
Console.WriteLine("Compile-time Polymorphism:");
// Rufen Sie Convert mit intValue, doubleValue und stringValue auf
// TODO: Laufzeit-Polymorphismus demonstrieren
// Erstellen Sie ein Circle-Objekt und speichern Sie es in einer Shape-Variable
// Rufen Sie dann Describe() auf der Shape-Variable auf
Console.WriteLine("Runtime Polymorphism:");
// Circle erstellen und Describe() aufrufen
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienNamespaces & DirektivenEinführung in Klassen & ObjekteDas 'this'-SchlüsselwortMethoden und ParameterFelder vs. EigenschaftenKonstruktorenObjekt-InitialisiererZusammenfassung - Einfacher Taschenrechner4Vererbung
Grundlagen der Vererbung (:) SyntaxDas 'base'-SchlüsselwortVirtual & Override SchlüsselwörterVersiegelte KlassenDie 'object'-BasisklasseZusammenfassung – Mitarbeiter-Hierarchie7Fortgeschrittene Funktionen
OperatorüberladungIndexer (this[])ToString() überschreibenErweiterungsmethodenZusammenfassung - Benutzerdefinierte Liste2Eigenschaften & Statische Member
Automatisch implementierte EigenschaftenRead-Only- & Write-Only-EigenschaftenStatische Felder & MethodenStatische KlassenExpression-Bodied Member5Polymorphismus & Interfaces
Compile- vs. Runtime-PolymorphismusInterface vs. abstrakte KlasseMehrfache InterfacesExplizite InterfacesUpcasting & DowncastingRecap – Shape Calculator3Klassenarchitektur
Instanz- vs. statische Daten'readonly' & 'const' SchlüsselwörterBacking FieldsZusammenfassung - Bankkonto-Manager6Kapselung
ZugriffsmodifikatorenProperties zur KapselungImplementierung von Data HidingImmutability-MusterRückblick – Studierendendaten