Home > .NET allgemein > Benannte und optionale Parameter

Benannte und optionale Parameter

Manchmal stolpert man bei C# über Leistungsmerkmale, von denen man sich wünscht, schon eher davon gehört zu haben. Ein gutes Beispiel hierfür sind benannte und optionale Parameter, die seit C# 4.0 bereitgestellt werden. Doch es lauern auch Gefahren, die man kennen sollte.

Optionale und benannte Parameter werden sehr häufig zusammen erwähnt, sind aber zwei verschiedene Leistungsmerkmale. Optionale Parameter sind, wie der Name schon sagt, optional. Diese können bei einem Methodenaufruf weggelassen werden. Durch benannte Parameter ist die Reihenfolge der Parameter bei einem Methodenaufruf beliebig.

Beide Techniken lassen sich auf Methoden, Konstruktoren und Indexer anwenden.

Optionale Parameter

Um dem Nutzer einer Klasse den maximalen Komfort zu bieten, werden Methoden mit vielen Parametern sehr häufig überladen. Somit steht die gleiche Methode in unterschiedlichen Varianten zur Verfügung. Ein recht gutes Beispiel ist die statische Methode Show() der Klasse MessageBox:

public class MessageBox
{
    public static DialogResult Show(string text);
    public static DialogResult Show(string text, string caption);
    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons);
    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon);
    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton);
    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options);
    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, bool displayHelpButton);
    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath);
    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, HelpNavigator navigator);
    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, string keyword);
    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, HelpNavigator navigator, object param);

    public static DialogResult Show(IWin32Window owner, string text);
    public static DialogResult Show(IWin32Window owner, string text, string caption);
    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons);
    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon);
    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton);
    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options);
    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath);
    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, HelpNavigator navigator);
    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, string keyword);
    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, HelpNavigator navigator, object param);
}

Seit C# 4.0 kann bei der Definition einer Methode angegeben werden, ob ein Parameter beim Aufruf erforderlich ist oder nicht. Hierzu wird hinter den gewünschten Parametern ein Standardwert angegeben. Der Standardwert muss immer eine Konstante sein. Wird bei dem Methodenaufruf kein Wert an den Parameter übergeben, so wird der Standardwert verwendet. Optionale Parameter müssen immer am Ende der Parameterliste, nach allen obligatorischen Parametern, stehen.

public void TestMethod(int x, int y = 123, int z = 456)
{
    Console.WriteLine("x: {0}  y: {1}  z: {2}", x, y, z);
}

Die Methode lässt sich auf verschiedene Arten aufrufen. Allerdings dürfen optionale Parametern bei dem Aufruf nicht ‘übersprungen’ werden.

TestMethod(1);
TestMethod(1, 2);
TestMethod(1, 2, 3);
TestMethod(1, , 3);  // Fehler

Es ist möglich, Methoden zu überladen, die optionale Parameter besitzen.

public void TestMethod(int x, int y = 123, int z = 456)
{
    Console.WriteLine("x: {0}  y: {1}  z: {2}", x, y, z);
}
public void TestMethod(int x)
{
    Console.WriteLine("x: {0}", x);
}

TestMethod(1);

Die Verwendung von TestMethod(1) hat zur Folge, dass die Methode TestMethod(int x) aufgerufen wird. Es wird also die Methode bevorzugt, die keine optionalen Parameter hat.

Da der Standardwert immer eine Konstante sein muss, gibt der Compiler bei folgendem Beispiel einen Fehler aus:

// nicht erlaubt
public void TestMethod(int x, int y = 123, Foo z = new Foo())
{
    Console.WriteLine("x: {0}  y: {1}  z: {2}", x, y, z);
}

Anders sieht das bei Enums und Strukturen aus. Da dieses Wertetypen sind, kann der new-Operator benutzt werden. allerdings nur in Verbindung mit dem Standardkonstruktor.

public void TestMethod(int x, int y = 123, StructFoo z = new StructFoo())
{
    Console.WriteLine("x: {0}  y: {1}  z: {2}", x, y, z);
}

Das Schlüsselwort default(T) kann ebenfalls benutzt werden:

public void TestMethod(int x, int y = 123, Foo z = default(Foo))
{
    Console.WriteLine("x: {0}  y: {1}  z: {2}", x, y, z);
}

Die Variable z erhält den Wert null, wenn beim Aufruf kein Wert angegeben wurde.

IntelliSense zeigt optionale Parameter in eckigen Klammern an:

Picture01

Die Attribute Optional und DefaultParameterValue

Das Attribute Optional kann ebenfalls benutzt werden, um einen Parameter als optional zu kennzeichnen. Wird bei dem Aufruf der Methode dem Parameter kein Wert zugewiesen, so wird der Standardwert des jeweiligen Datentyps benutzt.

public void TestMethod(int x, [Optional]int y, [Optional]int z)
{
    Console.WriteLine("x: {0}  y: {1}  z: {2}", x, y, z);
}

Ergänzt werden kann das Attribut Optional durch das Attribut DefaultParameterValue.

public void TestMethod(int x, [Optional, DefaultParameterValue(123)]int y,
                              [Optional, DefaultParameterValue(456)]int z)
{
    Console.WriteLine("x: {0}  y: {1}  z: {2}", x, y, z);
}

Somit ist die Kombination aus beiden Attributen gleichzusetzen mit den obigen Beispielen. Es gelten auch die gleichen Randbedingungen.

Gefahrenquelle

Besondere Vorsicht ist geboten, wenn die Methode und der Aufrufer der Methode in unterschiedlichen Projekten abgelegt sind. Das folgende Beispiel enthält zwei Klassen in getrennten Assemblies.

class Program
{
    static void Main(string[] args)
    {
        new Program().Run();
    }
    public void Run()
    {
        ExternalClass externalClass = new ExternalClass();
        externalClass.TestMethod(1);
        externalClass.TestMethod(1, 2);
        externalClass.TestMethod(1, 2, 3);
    }
}

class ExternalClass
{
    public void TestMethod(int x, int y = 123, int z = 456)
    {
        Console.WriteLine("x: {0}  y: {1}  z: {2}", x, y, z);
    }
}

Was zu beachten ist, wird deutlich bei der Analyse des IL-Codes vom Aufrufer. Der Compiler kopiert die Werte in die Methodenaufrufe.

Picture02

Das kann sehr unangenehm werden, wenn bei der Methode TestMethod() die Standardwerte geändert werden, der Aufrufer aber nicht neu compiliert wird. Die Methode wird weiterhin mit den alten Standardwerten aufgerufen.

Meiner Meinung nach sollten optionale Parameter (mit Standardwert) nur innerhalb einer Assembly angewendet werden. Gegen das Attribut Optional (ohne Standardwert) ist nichts einzuwenden, da das oben gezeigte Problem nicht auftreten kann.

Benannte Parameter

Benannte Parameter können bei jedem Methodenaufruf verwendet werden. Hierbei wird vor dem Wert auch der Name des Parameters angegeben. Dadurch können die Parameter in beliebiger Reihenfolge angegeben werden. Als Beispiel soll folgende Methode dienen:

public void TestMethod(int xPosition, int yPosition, int size)
{
    Console.WriteLine("x: {0}  y: {1}  z: {2}", xPosition, yPosition, size);
}

Folgende Aufrufe sind u.a. möglich:

TestMethod(1, 2, 3);
TestMethod(xPosition: 1, yPosition: 2, size: 3);
TestMethod(size: 3, yPosition: 2, xPosition: 1);

Der Compiler sorgt dafür, dass die Methode immer vollständig mit allen Parametern aufgerufen wird.

Picture03

Benannte Parameter müssen immer am Ende des Methodenaufrufs stehen.

TestMethod(1, yPosition: 2, size: 3);
TestMethod(yPosition: 2, xPosition: 1, 3);  // Fehler
TestMethod(xPosition: 1, 3, size: 3);       // Fehler

Neben der Tatsache, dass die Reihenfolge geändert werden kann, erhöht sich auch die Lesbarkeit des Quellcodes. Allerdings nur dann, wenn die Parameter einer Methode auch aussagekräftige Namen haben.

TestMethod(1, 2, 3);

TestMethod(xPosition: 1, yPosition: 2, size: 3);  // lesbarer

Gefahrenquelle

Die Auswertung der Parameter geschieht in der gleichen Reihenfolge, in der sie auch genannt werden.

int position = 1;
TestMethod(yPosition: position++, xPosition: position, size: 3);

Der Aufruf liefert für xPosition den Wert 2 und für yPosition den Wert 1.

Picture04

Der IL-Code zeigt sehr deutlich, wie die Parameter in der Reihenfolge, in der sie genannt werden, auch ausgewertet werden.

Liegen Methode und Aufrufer in unterschiedlichen Assemblies, so kann durchaus der Name eines Parameters geändert werden, ohne dass der Aufrufer neu compiliert werden muss. Das liegt ganz einfach daran, dass der Name der Variablen im IL-Code nicht mehr auftaucht. Probleme treten erst dann auf, wenn der Aufrufer neu compiliert wird.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: