1. Einführung
Im echten Leben sind viele Aktionen wie ein Schweizer Taschenmesser: Ein und derselbe Befehl kann mit verschiedenen Werkzeugen arbeiten. Stell dir zum Beispiel einen Geldautomaten vor: Wenn du eine Karte einsteckst, fragt der Automat nach der PIN; wenn du eine Telefonnummer eingibst, wartet der Automat auf einen Bestätigungscode per SMS. Die Aktion ist dieselbe – "Benutzer prüfen", aber die Wege sind unterschiedlich.
In der Programmierung begegnen wir oft einer ähnlichen Situation: Man will scheinbar eine einzige Operation ausführen, aber die Daten können unterschiedliche Typen oder eine unterschiedliche Anzahl von Parametern haben. Zum Beispiel soll unsere Methode eine Begrüßung sowohl für eine Person als auch für ein Tier ausgeben, oder zwei, drei oder sogar zehn ganze Zahlen addieren.
Klar, man könnte die Methoden unterschiedlich nennen: SumTwo, SumThree, SumArray. Aber Programmierer sind faul (nicht umsonst sagt man, Faulheit ist der Motor des Fortschritts). Außerdem wird der Code so weniger lesbar.
Methodenüberladung
Methodenüberladung ist eine Möglichkeit, ein und dieselbe Methode mit verschiedenen Parameterlisten arbeiten zu lassen, ohne den Methodennamen zu ändern. Das ist eine Form von Polymorphismus, aber nicht mit Vererbung verbunden.
Methodenüberladung bedeutet, dass man in einer Klasse (oder Struktur) mehrere Methoden mit demselben Namen haben kann, aber mit unterschiedlichen Parameterlisten (nach Typ, Anzahl und/oder Reihenfolge).
Methodensignatur
Die Signatur einer Methode in C# ist ihr Name plus Typ(en) und Reihenfolge der Parameter. Der Rückgabetyp der Methode gehört nicht zur Signatur! Das führt oft zu unerwarteten Fehlern (dazu gleich mehr).
2. Überladung in Aktion: einfache Beispiele
Lass uns eine Klasse Greeter bauen, die Nutzer unterschiedlich begrüßt: nur mit Namen, mit Name und Alter oder ganz ohne Parameter.
public class Greeter
{
// Begrüßung ohne Parameter
public void Greet()
{
Console.WriteLine("Hallo, Welt!");
}
// Begrüßung mit Name
public void Greet(string name)
{
Console.WriteLine($"Hallo, {name}!");
}
// Begrüßung mit Name und Alter
public void Greet(string name, int age)
{
Console.WriteLine($"Hallo, {name}! Bist du schon {age} Jahre alt? Nicht schlecht!");
}
}
Jetzt kannst du jede dieser Methoden aufrufen und der C#-Compiler wählt automatisch die richtige Variante – je nach Anzahl und Typ der übergebenen Parameter.
var greeter = new Greeter();
greeter.Greet(); // Hallo, Welt!
greeter.Greet("Anja"); // Hallo, Anja!
greeter.Greet("Pjotr", 23); // Hallo, Pjotr! Bist du schon 23 Jahre alt? Nicht schlecht!
3. Unterschied nach Typ und Anzahl der Parameter
Überladung funktioniert, wenn sich die Methoden unterscheiden durch:
- Anzahl der Parameter,
- Typ von mindestens einem Parameter,
- Reihenfolge der Parametertypen (aber hier muss man aufpassen).
Probieren wir noch eine Überladung, die nur das Alter nimmt:
public void Greet(int age)
{
Console.WriteLine($"So viele Jahre – das ist cool! ({age} Jahre)");
}
Jetzt die Aufrufe:
greeter.Greet(10); // So viele Jahre – das ist cool! (10 Jahre)
Wichtig zu merken: Wenn sich Methoden nur im Rückgabetyp unterscheiden, kann man sie nicht überladen. Zum Beispiel führt folgender Code zu einem Fehler:
// Kompilierfehler!
public int Foo(string s) { ... }
public double Foo(string s) { ... }
Der Compiler meckert: "Die Methode Foo(string) ist schon definiert, mach mal was Komplizierteres!"
4. Überladung und die Standardbibliothek von C#
Überladung ist nicht nur unser ausgedachtes Greet. Schau mal in die .NET-Dokumentation zu Console.WriteLine:
| Signatur | Zweck |
|---|---|
|
Druckt eine leere Zeile |
|
Druckt einen String |
|
Druckt eine ganze Zahl |
|
Druckt eine Kommazahl |
|
Formatiert einen String mit einem Argument |
|
Formatiert mit mehreren Argumenten |
Das sind alles Überladungen derselben Methode – WriteLine. Jetzt weißt du, warum du immer machen kannst:
Console.WriteLine("Einfacher String");
Console.WriteLine(123);
Console.WriteLine(2.5);
Console.WriteLine("Summe: {0}", 42);
Und der Compiler versteht deinen Aufruf immer richtig!
5. Wie wählt der Compiler, welche Überladung aufgerufen wird?
Hier ist alles streng: Er schaut auf die Typen und die Anzahl der tatsächlich übergebenen Argumente. Eine kleine Tabelle zur Veranschaulichung:
| Aufruf | Welche Version wird genommen? |
|---|---|
|
|
|
|
Was passiert bei Mehrdeutigkeit?
Manchmal läuft es aus dem Ruder. Ein Beispiel für eine mehrdeutige Überladung – der Compiler kann die richtige Version nicht auswählen
public void Print(int a, double b) { ... }
public void Print(double a, int b) { ... }
printer.Print(5, 10);
// Fehler: Mehrdeutigkeit – welche Print soll aufgerufen werden? (beide passen irgendwie)
Der Compiler gibt einen Mehrdeutigkeitsfehler aus. In solchen Fällen sollte man Überladungen mit gleicher Parameteranzahl und ähnlichen Typen vermeiden, wenn das den Compiler verwirren könnte.
6. params – variable Anzahl von Parametern
Angenommen, du willst eine Methode, die eine beliebige Anzahl von Zahlen nimmt. Hier hilft das Schlüsselwort params.
public void SumAll(params int[] numbers)
{
int sum = 0;
foreach (int n in numbers)
sum += n;
Console.WriteLine($"Summe: {sum}");
}
Jetzt kannst du aufrufen:
SumAll(1, 2, 3); // Summe: 6
SumAll(10, 20); // Summe: 30
SumAll(); // Summe: 0
Methoden mit params kann man mit Überladung kombinieren, aber man sollte keine Überladungen machen, die den Compiler daran hindern, eindeutig zu verstehen, welche Version du meinst.
7. Überladung und Parameter-Modifikatoren (ref, out, in)
C# unterscheidet Methoden nach Parameter-Modifikatoren (also ist die Signatur von void Foo(int a) anders als void Foo(ref int a), und beide können in einer Klasse existieren):
public void SetValue(int a)
{
a = 42;
}
public void SetValue(ref int a)
{
a = 100;
}
Ein Aufruf ohne ref landet in der ersten Version, mit ref in der zweiten:
int n = 5;
SetValue(n); // n bleibt 5 (Wert wird kopiert)
SetValue(ref n); // n wird 100
8. Schema: Was ist Überladung?
+----------+
| MyClass |
+----------+
|
| (Ausschnitt Methoden)
+-----------------------+
| void Foo() |
| void Foo(int a) |
| void Foo(string s) |
| void Foo(int a, int b) |
+-----------------------+
Und im Code sieht das so aus:
// Wir rufen die überladenen Versionen von Foo() auf:
var mc = new MyClass();
mc.Foo(); // void Foo()
mc.Foo(5); // void Foo(int)
mc.Foo("Hello"); // void Foo(string)
mc.Foo(2, 3); // void Foo(int, int)
9. Beispiel: Methodenüberladung in unserer App
Lass uns unsere Lern-App weiterentwickeln und eine Methodenüberladung in die Tier-Hierarchie einbauen.
public class Animal
{
public string Name { get; set; }
// Methode für Geräusch
public virtual void MakeSound()
{
Console.WriteLine("Irgendein seltsames Geräusch...");
}
// Überladene Methode: Geräusch mit Lautstärke
public void MakeSound(int volume)
{
Console.WriteLine($"Das Tier macht ein Geräusch mit {volume} dB Lautstärke.");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Wuff!");
}
// Überladene Methode: Bellen mit Lautstärke
public void MakeSound(int volume)
{
Console.WriteLine($"Wuff! (Lautstärke: {volume} dB)");
}
}
Probier mal diese Aufrufe:
Dog rex = new Dog();
rex.MakeSound(); // Wuff!
rex.MakeSound(75); // Wuff! (Lautstärke: 75 dB)
Achte darauf: In der abgeleiteten Klasse (Dog) haben wir die Methode MakeSound(int volume) überladen, und jetzt gibt es beide Versionen: mit und ohne Parameter.
10. Typische Fehler bei der Methodenüberladung
Fehler Nr. 1: Versuch, nur nach Rückgabetyp zu überladen.
Das geht nicht – der Rückgabetyp gehört nicht zur Methodensignatur. Überladung muss sich in der Anzahl oder den Typen der Eingabeparameter unterscheiden, nicht nach void oder int.
Fehler Nr. 2: Mehrdeutige Überladungen, die den Compiler verwirren.
Überladungen mit gleicher Parameteranzahl und ähnlichen Typen (z.B. int und double) können den Compiler durcheinanderbringen. Beispiel: Print(int a, double b) und Print(double a, int b) – der Aufruf Print(1, 1) führt zu einem Mehrdeutigkeitsfehler.
Fehler Nr. 3: Konflikt von params mit anderen Überladungen.
Eine Methode mit params kann einen Aufruf abfangen, der eigentlich für eine andere Überladung gedacht war. Wenn die Typen passen, kann der Compiler eine andere Methode wählen als erwartet.
Fehler Nr. 4: Vergessen, dass ref und out zur Signatur gehören.
Die Methoden Do(ref int x) und Do(out int x) gelten als verschiedene Überladungen. Wenn man das nicht beachtet, kann man leicht die falsche Version aufrufen.
GO TO FULL VERSION