Optionale Parameter in .NET
Einleitung
Als optionale Parameter werden Prozedurparameter bezeichnet, die beim Prozeduraufruf in der Parameterliste nicht angeführt werden müssen. Optionale Parameter werden üblicherweise mit der Übergabe von Parameterstandardwerten für die nicht explizit aufgeführten und mit einem Wert versehenen Parameter verbunden. Die Standardwerte der Parameter werden als Teil der Prozedurspezifikation dokumentiert und sind somit Teil der Schnittstelle. Optionale Parameter sowie Parameterstandardwerte sind aus Visual Basic 6.0 und der Programmiersprache C bekannt. Die Programmiersprache Visual Basic .NET unterstützt ebenfalls optionale Parameter.
In Bezug auf .NET sind optionale Parameter und Parameterstandardwerte und ihre Umsetzung in Visual Basic Ausgangspunkt kontroverser Diskussionen. Ziel dieses Artikels ist es, die vorgebrachten Argumente gegen die Unterstützung und Verwendung optionale Parameter hinsichtlich ihrer Stichhaltigkeit zu untersuchen. Ferner wird die Implementierung optionaler Parameter in Visual Basic beleuchtet. Auf Basis einer Betrachtung der Versionierungsproblematik werden Leitlinien zur Nutzung optionaler Parameter vorgeschlagen.
Optionale Parameter in .NET
In der Common Intermediate Language (CIL) steht zum Markieren von Parametern als optional das Parameterattribut opt
zur Verfügung. Der Visual-Basic-Compiler emittiert dieses Attribut für optionale Parameter. Die zu wählende Implementierung für optionale Parameter wird in der Common Language Specification (CLS) jedoch nicht vorgegeben. Obwohl Definition und Aufruf von Methoden mit optionalen Parametern nicht von allen .NET-Programmiersprachen gleichermaßen unterstützt werden, ist das Attribut opt
CLS-konform ([1], Teil II Metadata Definition and Semantics, Kap. 15.4 Defining methods, S. 73).
Über die .param
-Anweisung ([1], Teil II Metadata Definition and Semantics, Kap. 15.4.1.4 The .param
type directive, S. 75) können konstante Werte mit Methodenparametern verknüpft werden. Die Spezifikation läßt die Semantik der angegebenen Werte offen, führt jedoch in einer Anmerkung Parameterstandardwerte als möglichen Nutzungszweck an. Der Visual-Basic-Compiler nutzt die .param
-Anweisung, um die Parameterstandardwerte im ausgegebenen IL-Code zu hinterlegen. Außerdem ermittelt der Compiler daraus die bei Aufrufen von Methoden mit optionalen Parametern für weggelassene Parameter eingefügten Werte.
Folgendes Listing zeigt die Implementierung einer Methode Test
in Visual Basic, die einen optionalen Parameter i
des Datentyps Integer
mit Standardwert 12 enthält. Der optionale Parameter wird mittels des Schlüsselwortes Optional
gekennzeichnet und über den Operator =
der Standardwert zugewiesen:
Da C# optionale Parameter nicht direkt in der Syntax unterstützt, erfolgen das Auszeichnen eines Parameters als optional und die Angabe seines Standardwertes über Attribute:
Die Compiler für Visual Basic und C# emittieren für die beiden gezeigten Implementierungen der Methode Test
identischen IL-Code. Der Parameter i
wird dabei mit dem Modifizierer opt
versehen und der Standardwert 12 des Parameters im ersten Element der .param
-Anweisung eingetragen. Der IL-Code im IL-Assembler-Format sieht wie folgt aus:
Optionale Parameter vs. Überladung
In Diskussionen zu optionalen Parametern wird oftmals das Ersetzen einer Methode mit optionalen Parametern durch ein Bündel an überladenen Methoden unterschiedlicher Signatur empfohlen. Während dies in jenen Fällen, in denen es sich beim Standardwert um ein nicht feststehendes Implementierungsdetail handelt, eine sinnvolle Lösung darstellt, ist es problematisch, wenn dabei wichtige Vertragsbestandteile versteckt werden. Sollen beispielsweise beim Schließen eines Objekts durchgeführte Änderungen übernommen werden, bietet es sich an, der Methode einen optionalen Parameter des Datentyps Boolean
zu übergeben, der gesetzt werden kann, um die Daten zu speichern:
Sowohl die Implementierung von Close
mit optionalem Parameter als auch die Lösung mittels Überladung erlaubt den Aufruf der Funktion ohne Angabe von Parametern und unter Übergabe des Wertes False
für den Parameter PersistChanges
. Trotzdem sind die beiden Lösungen nicht gleichwertig, da im Falle der Überladungen beim Aufruf der parameterlosen Methode nicht hervorgeht, welcher Wert für PersistChanges
übergeben wird. Damit verliert der Quellcode seinen selbstdokumentierenden Charakter. Die genannte Lösung mit Überladung sieht folgendermaßen aus:
Bei einer größeren Anzahl an optionalen Parametern kann eine Umsetzung mittels Überladung zur Überladungshölle führen, die dadurch gekennzeichnet ist, daß eine hohe Anzahl verschiedener Überladungen mit geringen Unterschieden und stark ähnlicher Semantik existieren. Ein Beispiel für eine derartige Methode stellt MessageBox.Show
mit seinen 21 Überladungen dar. Die in Visual Basic bekannte Funktion MsgBox
bietet zwar etwas weniger Optionen, leidet jedoch nicht unter dieser Unübersichtlichkeit, da optionale Parameter eingesetzt werden. MsgBox
hat zudem den Vorteil, daß die übergebenen Standardwerte einfach ermittelt werden können.
Kritik an optionalen Parametern
Eric Gunnerson, der an der Entwicklung von C# mitarbeitet, begründet im Artikel Does C# have default parameters? die Entscheidung dafür, den früh gebundenen Aufruf von Methoden mit Standardparametern unter Übergabe ihrer Standardwerte im C#-Compiler nicht zu unterstützen (diese Gründe wurden auch von Anders Hejlsberg in einem Interview auf der Konferenz Tech·Ed 2004 genannt). Er führt folgende Gründe an, die seiner Ansicht nach gegen die Unterstützung optionaler Parameter sprechen:
Da die Standardwerte optionaler Parameter vom Compiler in Aufrufen der Methoden eingefügt werden, besteht keine Möglichkeit, den Standardwert des optionalen Parameters zu ändern, ohne aufrufende Bibliotheken neu zu kompilieren. Durch Einsatz von Überladung bleiben die Standardwerte einer Veränderung zugänglich, da sie nicht in Methodenaufrufe kopiert werden, sondern in der Bibliothek verbleiben, in der die Methode enthalten ist.
Das Erstellen von Überladungen auf Basis der Methode mit optionalen Parametern bei deren Kompilierung ist mit zwei Problemen behaftet: Der Zusammenhang zwischen dem vom Benutzer geschriebenen Code und den vom Compiler ausgegebenen Methoden ist weniger offensichtlich. Außerdem wären für XML-Kommentare und IntelliSense eigene Regeln erforderlich, um Kommentare für die Überladungen zu erstellen und die Überladungen in IntelliSense zu einer einzigen Methode zusammenzufügen.
Die angeführten Gründe halten jedoch einer eingehenden Prüfung nicht stand. Der erste Kritikpunkt ist rein theoretischer Natur, da optionale Parameter mit Standardwerten in Szenarien nicht geeignet sind, in denen der Standardwert nicht unumstößlich feststeht. Über compilerseitige Versionierungsmechanismen wie der Möglichkeit, eine Bibliothek anzugeben, zu der das erstellte Kompilat kompatibel sein soll, kann das problematische Ändern des Parameterstandardwertes (wie auch andere inkompatible Änderungen) erkannt und verhindert werden.
Der zweite Kritikpunkt richtet sich nicht direkt gegen optionale Parameter, sondern eine bestimmte Form der Implementierung, die von anderen .NET-Programmiersprachen wie Visual Basic nicht gewählt wurde. Wie Gunnerson zurecht anmerkt, können Methoden mit optionalen Parametern nicht in allen Fällen durch das Erstellen von Überladungen durch den Compiler ersetzt werden. Dabei kann es nämlich zu mehreren Überladungen mit gleicher Signatur kommen, sofern zwei oder mehr optionale Parameter, die hintereinander in der Parameterliste stehen, den selben Datentyp besitzen:
Die im vorigen Listing gezeigte Methode Test
kann folgendermaßen aufgerufen werden:
Beim Ersatz der Methode Test
mit ihren beiden optionalen Parametern durch Überladung ist es nicht möglich, den Aufruf unter Auslassung des ersten optionalen Parameters nachzubilden, da die Methoden Test(Param1)
und Test(Param2)
aufgrund der Gleichheit des Parameterdatentyps die gleiche Signatur aufweisen würden. Lediglich die im nachstehenden Listing angeführten Methodendefinitionen sind möglich:
Kunstgriffe wären notwendig, um derartige Szenarien mittels Überladung nachzubilden, etwa die für den Benutzer transparente Vergabe unterschiedlicher Methodennamen oder implizites und für den Entwickler transparentes Hinzufügen zusätzlicher Methodenparameter durch den Compiler. Dadurch würde jedoch die intuitive Nutzung der Methoden im vorgesehenen Sinn in Programmiersprachen, welche diese Implementierung optionaler Parameter nicht unterstützen, behindert.
Standardwerte als Teil der Schnittstelle
Da Standardwerte nicht in allen Fällen veränderliche Implementierungsdetails darstellen, die zurecht für den Benutzer transparent sein sollten, ist das Verstecken der Standardwerte in Überladungen nicht immer sinnvoll. Einige Klassen des .NET Frameworks nutzen Überladung, um vermeintliche „Details“ zu verstecken, obwohl die Kenntnis dieser Details für Auswahl und Verwendung der Methoden von Bedeutung ist. Die Folge sind Fehler im die Methode nutzenden Code.
Beispielsweise ist der Konstruktor der Klasse System.IO.FileStream
überladen. Nicht alle Überladungen des Konstruktors besitzen einen Parameter des Typs FileShare
, mittels dessen der gemeinsame Zugriff auf die Datei durch mehrere Prozesse eingestellt werden kann. Bei den Überladungen ohne FileShare
-Parameter wird, so die Dokumentation, implizit FileShare.Read
übergeben. Dabei ist das Wissen um den genutzten FileShare
-Wert wichtig, um die Auswirkungen des Methodenaufrufs auf andere Programme zu erkennen und sie in der Fehlerbehandlung im eigenen Projekt berücksichtigen zu können.
Ein weiteres Beispiel stellen die Überladungen des Konstruktors der Klasse System.IO.StreamReader
dar, in denen die beim Lesen der Daten genutzte Codierung implizit auf Encoding.UTF8
festgelegt wird, obwohl zur Zeit in der Praxis die meisten Dateien Windows-ANSI-codiert sind und damit das Lesen zu unerwarteten Ergebnissen führt. In diesem Fall wäre eine Lösung mit optionalen Parametern nicht möglich, da es sich bei Encoding
um einen Verweistyp handelt und kein konstanter Parameterstandardwert angegeben werden kann. Trotzdem ist die vorzufindende Situation unbefriedigend und verlangt nach einer weiterreichenden Lösung.
Versionierung von Methoden mit optionalen Parametern
Die Werte optionaler Parameter sind genauso als Teil der Objektschnittstelle anzusehen wie Methodensignaturen. Compiler, die den Aufruf von Methoden mit optionalen Parametern unter Übergabe ihrer Standardwerte unterstützen, ermitteln bei der Kompilierung den Wert des Standardparameters und fügen ihn im emittierten Methodenaufruf in der Argumentliste ein. Daher können Standardwerte optionaler Parameter nicht nach Belieben geändert werden, sondern ihre Veränderung darf nur über eine geordnete Versionierung erfolgen.
Gehen wir davon aus, daß der optionale Parameter i
einer Methode Test
den Standardwert 12 besitzt. In einer neuen Version der Bibliothek ist nun eine Änderung des Standardwertes auf den Wert 100 erforderlich. (Hierbei ist anzumerken, daß eine derartige Änderung immer ein Hinweis auf einen schlechten Entwurf der ursprünglichen Version der Methode darstellt und gänzlich vermeidbar ist.) Die Signatur der Methode vor der Änderung sieht folgendermaßen aus:
Das intuitive Ändern des Standardwertes von Parameter i
in der Signatur von 12 auf 100 ist zwar möglich, jedoch nicht zulässig. Einerseits würde es das spezifizierte Verhalten der Methode verändern und deren Implementierung damit nicht mehr spezifikationskonform sein, andererseits kann es in Szenarien, in denen Bibliotheken zum Einsatz gelangen, die gegen unterschiedliche Versionen der Methode kompiliert wurden, zu inkonsistentem Verhalten führen. Die folgende Änderung darf deshalb nicht durchgeführt werden:
Stattdessen kann die bestehende Methode durch Markieren mit dem Attribut Obsolete
für überholt erklärt und das geänderte Verhalten in einer neuen Methode bereitgestellt werden. Durch Übergabe des Wertes True
für die Eigenschaft IsError
des Attributs Obsolete
wird beim Kompilieren ein Kompilierungsfehler ausgelöst, wenn die als überholt gekennzeichnete Methode aufgerufen wird. Ist der Einsatz der bestehenden Methode weiterhin sinnvoll, kann das Markieren als überholt unterbleiben. Der Quellcode in der neuen Version der Bibliothek könnte folgendermaßen aussehen:
Die Versionierung von Methoden mit optionalen Parametern gestaltet sich nicht komplizierter als jene mehrerer Überladungen, da optionale Parameter ohnehin nicht eingesetzt werden sollten, wenn der Standardparameterwert keinen festen Teil der Objektschnittstelle darstellt. Die Angabe eines Standardsteuersatzes in einem Parameter des Datentyps Double
wäre nicht ratsam, wenn der Steuersatz im Laufe der Zeit einer Änderung unterworfen ist und dadurch der angegebene Parameterstandardwert nicht mehr dem Standardsteuersatz entsprechen würde. In diesem Fall ist eine Lösung mit Überladung vorzuziehen:
Optionale Parameter in C#
Die Programmiersprache C# bietet in ihrer Syntax weder spezifische Unterstützung für die Definition noch den Aufruf von Methoden mit optionalen Parametern. Dennoch können zumindest Methoden mit optionalen Parametern problemlos über Attribute an den Parametern definiert werden. Der Aufruf ist zwar möglich, jedoch weitaus weniger komfortabel als in Visual Basic, da weder der Compiler Unterstützung bietet noch durch die Entwicklungsumgebung die Nutzung mittels IntelliSense und Hinweistexten vereinfacht wird.
Methoden mit optionalen Parametern und Parameterstandardwerten können in C# definiert werden. Der C#-Compiler emittiert für mit dem Attribut Optional
gekennzeichnete und über das Attribut DefaultParameterValue
mit einem Standardwert versehene Parameter die gleichen IL-Anweisungen wie der Visual-Basic-Compiler. Folgendes Listing zeigt die Definition einer Methode mit einem optionalen Parameter:
Beim Aufruf von Methoden mit optionalen Parametern muß nach Nutzungsszenario unterschieden werden: Für optionale Parameter des Typs Object
in COM Interop kann der Wert Type.Missing
übergeben werden, um anzuzeigen, daß ihr Wert nicht angegeben wird. An die aufgerufene Methode wird dann im Parameter ein VARIANT
des Typs VT_ERROR
mit dem Wert DISP_E_PARAMNOTFOUND
übergeben. Innerhalb von Visual Basic 6.0 könnte dann der Variant
-Parameter mittels der Funktion IsMissing
daraufhin geprüft werden, ob für ihn ein Wert übergeben wurde.
In anderen Fällen, etwa bei in Visual Basic implementierten Methoden mit optionalen Parametern, müssen ebenfalls alle Parameter im Aufruf aufscheinen; ihre Standardwerte können entweder vom Benutzer händisch im Code eingetragen oder zur Laufzeit über Reflection ermittelt werden. Erstere Vorgehensweise bietet sich an, da sie den Quellcode lesbarer hält. Das Ermitteln des Parameterwertes zur Laufzeit ist langsamer, bewirkt hingegen, daß selbst bei Änderung des Standardwertes immer der aktuelle Wert übergeben wird. Folgendes Listing zeigt den Aufruf der zuvor vorgestellten Methode Test
unter Übergabe des Standardwertes für den optionalen Parameter:
Schlußwort
Optionale Methodenparameter sind ein ausdrucksstarkes Mittel, das durch Überladung nicht vollständig ersetzt werden kann. Richtig eingesetzt stellen optionale Parameter keine Quelle für Versionierungsprobleme dar, da ihre Standardwerte Teil des durch die veröffentlichte Schnittstelle definierten Vertrages darstellen. In der Praxis ist besonders der Aufruf von Methoden mit optionalen Parametern wichtig, um Komponenten nutzen zu können, die mit verbreiteten .NET-Programmiersprachen erstellt wurden, welche die Definition von Methoden mit optionalen Parametern zulassen. Deshalb wäre es sinnvoll, in C# zumindest den Aufruf von Methoden mit optionalen Parametern direkt durch den Compiler zu unterstützen.
Literaturverzeichnis
- [1]
- Microsoft Corporation: Standard ECMA-335 – Common Language Infrastructure (CLI), 4th Edition, ECMA, Juni 2006.