Überladung und Signaturen in C♯
Einen einfachen Namen einer Methode (also einen Bezeichner) bezeichnen wir auch als ein Verb (vom lateinischen „verbum“ – „Wort“).
Ein Verb kann zu verschiedenen Methoden derselben Klasse gehören, welche sich durch die Anzahl und/oder den Typ ihrer Parameter unterscheiden.
Die Methode »global::System.Math.Abs« ergibt den Betrag ihres Argumentes (also den Abstand des Arguments zur Zahl 0).
In der Klasse »global::System.Math« gibt es zwei Methoden mit dem Verb »Abs«, aber nur eine mit dem Verb »Floor«.
- Dokumentationen zum Verb »Abs« der Klasse »global::System.Math« (verändert und übersetzt)
- Namensraum »global::System«
Klasse »Math«
public static double Abs( double d )
Ergibt den Betrag des Argumentwerts.
public static int Abs( int d )
Ergibt den Betrag des Argumentwerts.
Man sagt, daß ein Verb, das mehr als nur einen Eintrag in der Dokumentation eines Typs hat, überladen (d.h. mehrfach mit einer Bedeutungen „beladen“) sei. Und man spricht auch von einer „überladenen Methode “, um zu sagen, daß es noch andere Methoden mit demselben Verb im selben Referenztyp gibt.
Methodensignatur
Um eine Methode eindeutig bezeichnen zu können (wenn ihre Klasse schon festgelegt ist), ist es angesichts der Möglichkeit der Überladung notwendig, zusätzlich zum Verb auch noch die Parametertypen mit anzugeben. Die Kombination aus Verb und den Parametertypen nennt man Methodensignatur oder kurz Signatur. Die obige Dokumentation behandelt also unter anderem die Signatur »Abs(double)« und die Signatur »Abs(int)«.
- Mögliche Aussprachen von »Abs(double)«
- „Abs von double“
„Abs double“ (kurz)
Angabe einer Methode
Da erst die Signatur eine Methode innerhalb einer Klasse genau bestimmt und nicht schon das Verb der Methode, gibt man Methoden mit ihrer Signatur an.
Man spricht also beispielsweise von „der Methode mit der Signatur »Abs(double)«“ oder einfach von „der Methode »Abs(double)«“ und nicht einfach von der Methode »Abs«.
Wir unterscheiden daher zwischen der Methode »Abs(double)« und der Methode »Abs(int)« (das sind zwei verschiedene Methoden (Signaturen), die aber dasselbe Verb miteinander teilen).
Man kann jedoch von „der Methode »Abs«“ sprechen, wenn es nicht darauf ankommt, welche Überladung genau gemeint ist, oder man sich auf alle Überladungen gemeinsam beziehen will, obwohl der Singular nicht ganz korrekt ist, da es ja mehrere Methoden mit dem Verb »Abs« gibt.
Angabe einer Methode ohne Parameter
Man spricht von der Methode »random()« mit leeren Klammern, also der Information, daß sie keine Parameter hat, statt nur von der Methode »random«. Allerdings darf man »random()« in solchen Fällen nicht mit dem Aufruf der Methode verwechseln – bei Methoden ohne Parameter sieht die Angabe der Methodensignatur nämlich wie ein Aufruf der Methode aus. Man muß manchmal aus dem Zusammenhang entnehmen, ob mit solch einer Angabe mit leeren Klammern eine Methodensignatur oder ein Aufruf gemeint ist.
Qualifizierte Signaturen
Um eine Methode eindeutig zu bezeichnen, wenn nicht schon aus dem Zusammenhang klar ist, zu welcher Klasse sie gehört, ist es nötig, zusätzlich zur Methodensignatur auch noch die Klasse anzugeben, wie beispielsweise in »global::System.Math.Abs(int)«, der Methodensignatur »Abs(int)« in der Klasse »global::System.Math«. Wir bezeichnen eine um den Klassennamen erweiterte Methodensignatur hier manchmal als qualifizierte Methodensignatur.
Aufrufsignatur
Neben der Signatur einer Methode aus der Dokumentation gibt es auch noch die Signatur eines Aufrufs aus dem Quelltext, die Aufrufsignatur.
Die Aufrufsignatur eines Aufrufs erhält man, indem man alle Argumente durch ihren Typ ersetzt.
So hat der Aufruf »Abs( 2 )« die Aufrufsignatur »Abs(int)«, und der Aufruf »Abs( 2.0 )« hat die Aufrufsignatur »abs(double)«.
Auflösung von Ausdrücken
In dem folgenden Programm wird »global::System.Math.Abs« mit einem Argument vom Typ »double« aufgerufen. Ersetzen wir im Aufruf den Argumentausdruck »-2.0« durch seinen Typ »double«, so erhalten wir die Aufrufsignatur »Abs(double)«. Diese paßt nur zur Methodensignatur »Abs(double)« aus der Dokumentation (in dem Sinne, daß der Aufruf »java.lang.Math.Abs( -2.0 )« korrekt wäre, wenn es in der Klasse nur diese Methode »Abs(double)« geben würde). Daher wird die Methode aufgerufen, welche die Methodensignatur »Abs(double)« hat.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( global::System.Math.Abs( -2.0 )/4 ); }}Protokoll
0.5
An der Ausgabe »0.5« erkennen wir, daß der Dividend »global::System.Math.Abs( -2.0 )« den Typ »double« haben muß.
In dem folgenden Programm wird »global::System.Math.Abs« mit einem Argument vom Typ »int« aufgerufen. Ersetzen wir im Aufruf den Argumentausdruck »-2« durch seinen Typ »int«, so erhalten wir die Aufrufsignatur »Abs(int)«. Diese paßt sowohl zur Methodensignatur »Abs(int)« als auch zur Methodensignatur »Abs(double)« aus der Dokumentation, da der Argumenttyp »int« sowohl implizit in den Parametertyps »int« als auch in den Parametertyps »double« gewandelt werden kann (in dem Sinne, daß der Aufruf »global::System.Math.Abs( -2 )« sowohl korrekt wäre, wenn es in der Klasse die Methode »Abs(int)« geben würde, als auch, wenn es in der Klasse nur die Methode »Abs(double)« geben würde.). In solche Fällen wird unter den passenden Signaturen die am besten passende Methodensignatur ausgewählt. Dies ist im folgenden Programm die Methodensignatur »Abs(int)«, da sie genau zur Aufrufsignatur »Abs(int)« paßt.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( global::System.Math.Abs( -2 )/4 ); }}Protokoll
0
An der Ausgabe »0« erkennen wir, daß der Dividend »global::System.Math.Abs( -2 )« den Typ »int« haben muß.
Im allgemeinen wird die Methode aufgerufen, deren Signatur am besten zur Signatur des Aufrufs paßt.
Die komplizierten Details des Auswahlverfahrens in Fällen, in denen die am besten passende Signatur nicht so eindeutig erkannt werden kann wie in den obigen Beispielen, sind in C# Language Specification, Version 6, 7.5.3 Overload resolution beschrieben.
Es gilt als Fehler, wenn es mehrere Methodensignaturen gibt, die gleich gut zu einer Aufrufsignatur passen.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( global::System.Math.Floor( 2 )); }}- Protokoll
Program.cs(4,27): error CS0121: The call is ambiguous between the following methods or properties: 'Math.Floor(decimal)' and 'Math.Floor(double)'
Gründe für eine Überladung
Der Typ des Aufrufs verschiedener Methoden mit demselben Verb muß nicht gleich sein, sondern kann für jede Signatur willkürlich festgelegt werden. Im Falle des qualifizierten Verbs »java.lang.Math.abs« hat der Aufruf der Methode mit einem double-Parameter den Typ »double«, während der Aufruf der Methode mit einem int-Parameter den Typ »int« hat. Dies bedeutet, daß in diesem Falle der Typ des Arguments auch der Typ des Aufrufs ist. Durch die Überladung bleibt in diesem Fall also der Typ des Arguments „erhalten“, was manchmal erwünscht ist.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( global::System.Math.Abs( 2.0 )/4 ); }}- Protokoll
0.5
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( global::System.Math.Abs( 2 )/4 ); }}- Protokoll
0
Bei der Methode »global::System.Math.Sqrt« (Quadratwurzel) geht der Typ des Arguments hingegen verloren. Der Aufruf hat immer den Typ »double«.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( global::System.Math.Sqrt( 4.0 )/4 ); }}- Protokoll
0.5
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( global::System.Math.Sqrt( 4 )/4 ); }}- Protokoll
0.5
- Tabelle
Aufrufsignatur aufgerufene Methode Aufruftyp
Abs(double) Abs(double) double
Abs(int) Abs(int) intSqrt(double) sqrt(double) double
Sqrt(int) sqrt(double) double
Eine Überladung eines Verbs mit einer int- und einer double-Signatur wird vorgenommen, wenn das Verb int-Werte anders als double-Werte behandeln soll. Würde es nur eine einzige double-Methode geben, so würde diese Methode es nämlich gar nicht feststellen können, wenn sie mit einem int-Werte aufgerufen wird, da dieser beim Aufruf immer automatisch in einen double-Wert umgewandelt werden würde bevor er der Methode bekannt gegeben wird.
Polymorphie
Polymorphie bedeutet allgemein für Aufrufe, daß die Auswahl der aufgerufenen Methode nicht nur vom im Aufruf angegebenen Namen der Methode abhängt, sondern auch von den Typen der Argumente abhängen kann.
Die oben beschriebene Überladung ist eine Form von Polymorphie, da aus der Kombination eines Methodennamens und des Typs der Argumente die zu verwendende Methode bestimmt wird.
Beispielsweise enthält der Aufruf »global::System.Math.Abs« den Methodennamen »global::System.Math.Abs« und den Argumentausdruck »2.0«. Durch die Kombination des Methodennamens mit dem Typ des Arguments, also durch die Aufrufsignatur »global::System.Math.Abs(double)« wird dann festgelegt, daß die Implementation (Methode) »global::System.Math.Abs(double)« aufgerufen wird.
Beispiele zu Typwandlungen
Einige Typwandlungen lassen sich als Methodenaufrufe mit einem Argument schreiben.
Obwohl es viele verschiedene Möglichkeiten solcher Aufrufe gibt, reicht es, sich für sie den Begriff »To« zu merken.
- Ein Begriff für Wandlungen
To
Durch Anfügen eines der vier Typnamen »Decimal«, »Double«, »Decimal«, »Decimal« ergeben sich so vier Verben (also Bezeichner für Methoden), die jeweils ausdrücken in welchen Typ gewandelt werden soll.
- Vier Verben für Wandlungen
ToDecimal
ToDouble
ToInt32
ToString
Vier Methoden mit diesen Verben finden sich in der Klasse »global::System.Convert«.
- Die Namen der verwendeten Wandlungsmethoden
global::System.Convert.ToDecimal
global::System.Convert.ToDouble
global::System.Convert.ToInt32
global::System.Convert.ToString
Zu jedem einzelnen jener Umwandlungsverben gehören dann diverse Überladung mit Umwandlungsmethoden für verschiedene Argumenttypen.
Beispielsweise ist der Typ eines Aufrufs des Verbes »ToDecimal« stets »decimal«. Mögliche Argumenttypen umfassen aber »int«, »double«, »decimal« und »string«.
Der Aufruf »global::System.Convert.ToDecimal(4)« hat beispielsweise den Typ »decimal« und ruft die Überladung (Methode) »global::System.Convert.ToDecimal(int)« auf, das Ergebnis ist die Umwandlung des Wertes »4« in den Datentyp »decimal«, also in den Wert dieses Typs, der den Zahlenwert »4« repräsentiert, das heißt in die Zahl Vier vom Typ »decimal«.
- Siehe auch
https://msdn.microsoft.com/en-us/library/system.convert_methods.aspx
Wandlung von »double«
Wandlung von »double« nach »int«
Das folgende Programm enthält eine Wandlung eines double-Wertes in einen int-Wert.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( global::System.Convert.ToInt32( 5.3 )); }}global::System.Console.Out
5
Wandlung von »double« nach »decimal«
Das folgende Programm enthält eine Wandlung eines double-Wertes in einen decimal-Wert.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 1/global::System.Convert.ToDecimal( 3.0 )); }}global::System.Console.Out
0.3333333333333333333333333333
Wandlung von »double« in eine Zeichenfolge
Das folgende Programm enthält eine Wandlung eines double-Wertes in eine Zeichenfolge.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 1 + global::System.Convert.ToString( 3.0 )); }}global::System.Console.Out
13
Wandlung von »int«
Wandlung von »int« nach »double«
Das folgende Programm enthält eine Wandlung eines int-Wertes in einen double-Wert.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 1/global::System.Convert.ToDouble( 3 )); }}global::System.Console.Out
0.333333333333333
Wandlung von »int« nach »decimal«
Das folgende Programm enthält eine Wandlung eines int-Wertes in einen decimal-Wert.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 1/global::System.Convert.ToDecimal( 3 )); }}global::System.Console.Out
0.3333333333333333333333333333
Wandlung von »int« in eine Zeichenfolge
Das folgende Programm enthält eine Wandlung eines int-Wertes in eine Zeichenfolge.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 1 + global::System.Convert.ToString( 3 )); }}global::System.Console.Out
13
Wandlung von »decimal«
Wandlung von »decimal« nach »int«
Das folgende Programm enthält eine Wandlung eines decimal-Wertes in einen int-Wert.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 1/global::System.Convert.ToInt32( 3m )); }}global::System.Console.Out
0.333333333333333
Wandlung von »decimal« nach »double«
Das folgende Programm enthält eine Wandlung eines decimal-Wertes in einen double-Wert.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 1/global::System.Convert.ToDouble( 3m )); }}global::System.Console.Out
0.333333333333333
Wandlung von »decimal« in eine Zeichenfolge
Das folgende Programm enthält eine Wandlung eines decimal-Wertes in eine Zeichenfolge.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 1 + global::System.Convert.ToString( 3m )); }}global::System.Console.Out
13
Wandlung von Zeichenfolgen
Wandlung einer Zeichenfolge nach »int«
Das folgende Programm enthält eine Wandlung einer Zeichenfolge in einen int-Wert.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 1 + global::System.Convert.ToInt32( "2" + "3" )); }}global::System.Console.Out
24
Wandlung einer Zeichenfolge nach »double«
Das folgende Programm enthält eine Wandlung einer Zeichenfolge in einen double-Wert.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 1/global::System.Convert.ToDouble( "1" + "0" )); }}global::System.Console.Out
0.1
Wandlung einer Zeichenfolge nach »decimal«
Das folgende Programm enthält eine Wandlung einer Zeichenfolge in einen decimal-Wert.
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 1/global::System.Convert.ToDecimal( "3" )); }}global::System.Console.Out
0.3333333333333333333333333333
Übungsfragen
? Überladungen
Wie viele Überladungen hat das Verb »Parse« im Typ »int« (Struktur »global::System.Int32«)?
? Aufrufsignaturen _
Welche Aufrufsignatur hat der Aufruf »sin( 2.0 )«?
Welche Aufrufsignatur hat der Aufruf »sin( 2 )«?
? Methoden und Signaturen _
Welche Methodensignatur hat die Methode »Log« der Klasse »global::System.Math«?
? Aufrufe und Signaturen ⃗
Was trifft für »Abs( 2 )« zu?
- Es ist ein Aufruf.
- Es ist eine Signatur.
- Es kann beides sein.
? Aufrufe und Signaturen (1) ⃗
Was trifft für »Abs(int)« zu?
- Es ist ein Aufruf.
- Es ist eine Signatur.
- Es kann beides sein.
? Aufrufe und Signaturen (2) ⃗
Was trifft für »random()« zu?
- Es ist ein Aufruf.
- Es ist eine Signatur.
- Es kann beides sein.
? Aufrufe und Signaturen (3) ⃗
- / Welche der folgenden einfachen Aufrufe haben die gleiche Signatur?
- »f( 23 )«
- »f( 12 )«
- »g( 2. )«
- »f( 2. )«
? Datentypen ⃗
Welchen Datentyp haben die folgenden Aufrufe jeweils?
- »global::System.Math.Max( 2, 3 )«
- »global::System.Math.Max( 2.0, 3.0 )«
- »global::System.Math.Max( 2, 3.0 )«
Diskussion der Typwandung mit Hilfe von »+« ⃗
Gelegentlich wird die Wandlung in eine Zeichenfolge mit »+ ""« als „unverständlich“ kritisiert. Es wird behauptet, daß ein Leser eines Programms an »+ ""« die Absicht des Programmierers, etwas in eine Zeichenfolge zu wandeln, nicht erkennen könne. Diese Kritik ist allerdings nicht berechtigt. Falls ein C♯ -Programmierer die Bedeutung von »+ ""« nicht kennt, dann liegt dies daran, daß er seine Ausbildung noch nicht beendet hat – aber man kann schlecht verlangen, Programme für Leser schreiben, welche die Programmiersprache nicht kennen. Falls ein C♯ -Programmierer die Bedeutung von »+ ""« aber kennt, dann ist es ihm auch klar, daß damit eine Umwandlung in eine Zeichenfolge beabsichtigt ist, denn warum sollte jemand »+ ""« sonst verwenden?
Die Kritiker würden die Verwendung von »global::System.Convert.ToString« bevorzugen, vermutlich weil dort Englische Wörter vorkommen, die – mehr oder weniger – „erklären“, was gemeint ist, während »+ ""« keine solchen Wörter enthält.
? Ausgabe vorhersagen ⃗
Das folgende Programm gibt sieben Zeichen aus. Sagen Sie alle sieben Zeichen einzeln voraus!
Program.cs
public static class Program
{ public static void Main()
{ global::System.Console.WriteLine
( 2E3 + ", " + global::System.Math.Pow( 2, 3 ) ); }}- Protokoll
2000, 8
Übungsaufgaben ⃗
/ Übungsaufgabe ⃗
Schreiben Sie ein Programm, in dem zunächst die Methode »Max(double,double)« aus der Klasse »global::System.Math« und dann die Methode »Max(int,int)« aus derselben Klasse aufgerufen wird.