Sollten Sie Methoden mit Überladungen oder optionalen Parametern in C # 4.0 deklarieren?

93

Ich habe Anders 'Vortrag über C # 4.0 und eine Vorschau auf C # 5.0 gesehen und darüber nachgedacht, wann optionale Parameter in C # verfügbar sind. Was ist die empfohlene Methode, um Methoden zu deklarieren, für die nicht alle Parameter angegeben werden müssen?

Zum Beispiel hat so etwas wie die FileStreamKlasse ungefähr fünfzehn verschiedene Konstruktoren, die in logische 'Familien' unterteilt werden können, z. B. die folgenden aus einer Zeichenfolge, die aus einer IntPtrund die aus einer SafeFileHandle.

FileStream(string,FileMode);
FileStream(string,FileMode,FileAccess);
FileStream(string,FileMode,FileAccess,FileShare);
FileStream(string,FileMode,FileAccess,FileShare,int);
FileStream(string,FileMode,FileAccess,FileShare,int,bool);

Es scheint mir, dass diese Art von Muster vereinfacht werden könnte, indem stattdessen drei Konstruktoren verwendet werden und optionale Parameter für diejenigen verwendet werden, die standardmäßig verwendet werden können, wodurch die verschiedenen Konstruktorfamilien deutlicher werden würden [Anmerkung: Ich weiß, dass diese Änderung nicht möglich ist gemacht in der BCL, ich spreche hypothetisch für diese Art von Situation].

Was denken Sie? Wird es ab C # 4.0 sinnvoller sein, eng verwandte Gruppen von Konstruktoren und Methoden zu einer einzigen Methode mit optionalen Parametern zu machen, oder gibt es einen guten Grund, sich an den traditionellen Mechanismus mit vielen Überladungen zu halten?

Greg Beech
quelle

Antworten:

120

Ich würde Folgendes in Betracht ziehen:

  • Muss Ihr Code aus Sprachen verwendet werden, die keine optionalen Parameter unterstützen? Wenn ja, ziehen Sie die Überlastungen in Betracht.
  • Haben Sie Mitglieder in Ihrem Team, die sich gewaltsam gegen optionale Parameter aussprechen? (Manchmal ist es einfacher, mit einer Entscheidung zu leben, die Sie nicht mögen, als den Fall zu argumentieren.)
  • Sind Sie sicher, dass sich Ihre Standardeinstellungen zwischen den Builds Ihres Codes nicht ändern, oder sind Ihre Anrufer damit einverstanden?

Ich habe nicht überprüft, wie die Standardeinstellungen funktionieren, aber ich würde davon ausgehen, dass die Standardwerte in den aufrufenden Code eingebrannt werden, ähnlich wie Verweise auf constFelder. Das ist normalerweise in Ordnung - Änderungen an einem Standardwert sind sowieso ziemlich bedeutend - aber das sind die Dinge, die berücksichtigt werden müssen.

Jon Skeet
quelle
20
+1 für die Weisheit über Pragmatismus: Manchmal ist es einfacher, mit einer Entscheidung zu leben, die Sie nicht mögen, als den Fall zu argumentieren.
Legends2k
13
@romkyns: Nein, der Effekt von Überladungen ist nicht der gleiche wie bei Punkt 3. Bei Überlastungen, die die Standardeinstellungen bereitstellen, befinden sich die Standardeinstellungen im Bibliothekscode. Wenn Sie also die Standardeinstellungen ändern und eine neue Version der Bibliothek bereitstellen, werden die Anrufer dies tun Siehe die neue Standardeinstellung ohne Neukompilierung. Bei optionalen Parametern müssen Sie neu kompilieren, um die neuen Standardeinstellungen zu "sehen". Meistens ist es keine wichtige Unterscheidung, aber es ist eine Unterscheidung.
Jon Skeet
hi @JonSkeet, ich würde gerne wissen, ob wir beide verwenden, dh Funktion mit optionalem Parameter und andere mit Überladung, welche Methode wird aufgerufen? zB Add (int a, int b) und Add (int a, int b, int c = 0) und Funktionsaufruf sagen: Add (5,10); Welche Methode wird als überladene Funktion oder optionale Parameterfunktion bezeichnet? danke :)
SHEKHAR SHETE
@ Shekshar: Hast du es versucht? Lesen Sie die Spezifikation für Details, aber im Grunde gewinnt in einem Tie-Breaker eine Methode, bei der der Compiler keine optionalen Parameter eingeben musste.
Jon Skeet
@ JonSkeet gerade jetzt habe ich mit oben versucht ... die Funktion Überladung gewinnt über optionale Parameter :)
SHEKHAR SHETE
19

Wenn eine Methodenüberladung normalerweise dasselbe mit einer anderen Anzahl von Argumenten ausführt, werden Standardwerte verwendet.

Wenn eine Methodenüberladung eine Funktion basierend auf ihren Parametern unterschiedlich ausführt, wird die Überladung weiterhin verwendet.

Ich habe in meinen VB6-Tagen optional verwendet und habe es seitdem verpasst. Dadurch wird die Duplizierung von XML-Kommentaren in C # erheblich reduziert.

cfeduke
quelle
11

Ich habe Delphi schon immer mit optionalen Parametern verwendet. Ich habe stattdessen auf Überlastungen umgestellt.

Wenn Sie mehr Überladungen erstellen, treten ausnahmslos Konflikte mit einem optionalen Parameterformular auf, und Sie müssen diese ohnehin in nicht optionale konvertieren.

Und ich mag die Vorstellung, dass es im Allgemeinen eine Super- Methode gibt, und der Rest sind einfachere Wrapper um diese.

Ian Boyd
quelle
1
Ich stimme dem sehr zu, aber es gibt die Einschränkung, dass bei einer Methode mit mehreren (3+) Parametern, die von Natur aus alle "optional" sind (durch eine Standardeinstellung ersetzt werden könnten), viele Permutationen auftreten können der Methodensignatur für keinen Nutzen mehr. Betrachten Sie Foo(A, B, C)erfordert Foo(A), Foo(B), Foo(C), Foo(A, B), Foo(A, C), Foo(B, C).
Dan Lugg
7

Ich werde auf jeden Fall die optionale Parameterfunktion von 4.0 verwenden. Es wird das Lächerliche los ...

public void M1( string foo, string bar )
{
   // do that thang
}

public void M1( string foo )
{
  M1( foo, "bar default" ); // I have always hated this line of code specifically
}

... und setzt die Werte genau dort, wo der Anrufer sie sehen kann ...

public void M1( string foo, string bar = "bar default" )
{
   // do that thang
}

Viel einfacher und viel weniger fehleranfällig. Ich habe dies tatsächlich als Fehler im Überlastungsfall gesehen ...

public void M1( string foo )
{
   M2( foo, "bar default" );  // oops!  I meant M1!
}

Ich habe noch nicht mit dem 4.0-Complier gespielt, aber ich wäre nicht schockiert zu erfahren, dass der Complier einfach die Überlastungen für Sie ausgibt.

JP Alioto
quelle
6

Optionale Parameter sind im Wesentlichen Metadaten, die einen Compiler, der einen Methodenaufruf verarbeitet, anweisen, entsprechende Standardeinstellungen an der Aufrufstelle einzufügen. Im Gegensatz dazu bieten Überladungen ein Mittel, mit dem ein Compiler eine von mehreren Methoden auswählen kann, von denen einige möglicherweise selbst Standardwerte liefern. Beachten Sie, dass, wenn man versucht, eine Methode aufzurufen, die optionale Parameter aus Code angibt, der in einer Sprache geschrieben ist, die sie nicht unterstützt, der Compiler die Angabe der "optionalen" Parameter verlangt, aber da das Aufrufen einer Methode ohne Angabe eines optionalen Parameters ist Entspricht dem Aufruf mit einem Parameter, der dem Standardwert entspricht, gibt es kein Hindernis für solche Sprachen, die solche Methoden aufrufen.

Eine wesentliche Folge der Bindung optionaler Parameter am Aufrufstandort ist, dass ihnen Werte zugewiesen werden, die auf der Version des Zielcodes basieren, die dem Compiler zur Verfügung steht. Wenn eine Assembly Fooeine Methode Boo(int)mit dem Standardwert 5 hat und die Assembly Bareinen Aufruf von enthält Foo.Boo(), verarbeitet der Compiler diese als Foo.Boo(5). Wenn der Standardwert auf 6 geändert wird und die Assembly Fooneu kompiliert wird, Barwird der Aufruf fortgesetzt, es Foo.Boo(5)sei denn oder bis sie mit dieser neuen Version von neu kompiliert wird Foo. Man sollte daher vermeiden, optionale Parameter für Dinge zu verwenden, die sich ändern könnten.

Superkatze
quelle
Betreff: "Man sollte daher vermeiden, optionale Parameter für Dinge zu verwenden, die sich ändern könnten." Ich bin damit einverstanden, dass dies problematisch sein kann, wenn die Änderung vom Client-Code nicht bemerkt wird. Das gleiche Problem besteht jedoch, wenn der Standardwert in einer Methodenüberladung verborgen ist : void Foo(int value) … void Foo() { Foo(42); }. Von außen weiß der Anrufer nicht, welcher Standardwert verwendet wird und wann er sich ändern könnte. dafür müsste man die schriftliche Dokumentation überwachen. Standardwerte für optionale Parameter können als genau das angesehen werden: Dokumentation im Code, wie hoch der Standardwert ist.
stakx - nicht mehr
@stakx: Wenn eine parameterlose Überladung mit einem Parameter zu einer Überladung verkettet wird, wird durch Ändern des "Standard" -Werts dieses Parameters und erneutes Kompilieren der Definition der Überladung der verwendete Wert geändert, auch wenn der aufrufende Code nicht neu kompiliert wird .
Supercat
Stimmt, aber das macht es nicht problematischer als die Alternative. In einem Fall (Methodenüberladung) hat der aufrufende Code im Standardwert kein Mitspracherecht. Dies kann angebracht sein, wenn der aufrufende Code den optionalen Parameter und dessen Bedeutung überhaupt nicht interessiert. Im anderen Fall (optionaler Parameter mit Standardwert) wird der zuvor kompilierte Aufrufcode nicht beeinflusst, wenn sich der Standardwert ändert. Dies kann auch dann sinnvoll sein, wenn sich der aufrufende Code tatsächlich um den Parameter kümmert. Wenn Sie es in der Quelle weglassen, sagen Sie: "Der derzeit vorgeschlagene Standardwert ist für mich in Ordnung."
stakx - trägt nicht mehr
Der Punkt, den ich hier ansprechen möchte, ist, dass beide Ansätze zwar Konsequenzen haben (wie Sie bereits betont haben), aber nicht von Natur aus vorteilhaft oder nachteilig sind. Das hängt auch von den Bedürfnissen und Zielen des aufrufenden Codes ab. Aus dieser Sicht fand ich das Urteil im letzten Satz Ihrer Antwort etwas zu starr.
stakx - nicht mehr
@stakx: Ich sagte "Vermeiden Sie die Verwendung" anstatt "nie verwenden". Wenn das Ändern von X bedeutet, dass die nächste Neukompilierung von Y das Verhalten von Y ändert, muss entweder ein Build-System so konfiguriert werden, dass bei jeder Neukompilierung von X auch Y neu kompiliert wird (was die Geschwindigkeit verlangsamt), oder es besteht das Risiko, dass sich ein Programmierer ändert X auf eine Weise, die Y beim nächsten Kompilieren bricht und erst zu einem späteren Zeitpunkt entdeckt wird, wenn Y aus einem völlig anderen Grund geändert wird. Standardparameter sollten nur verwendet werden, wenn ihre Vorteile diese Kosten überwiegen.
Supercat
4

Es kann argumentiert werden, ob optionale Argumente oder Überladungen verwendet werden sollten oder nicht, aber am wichtigsten ist, dass jedes seinen eigenen Bereich hat, in dem sie unersetzbar sind.

Optionale Argumente sind in Kombination mit benannten Argumenten äußerst nützlich, wenn sie mit einigen Listen mit langen Argumenten und allen Optionen von COM-Aufrufen kombiniert werden.

Überladungen sind äußerst nützlich, wenn die Methode in der Lage ist, viele verschiedene Argumenttypen zu verarbeiten (nur eines von Beispielen) und Castings beispielsweise intern ausführt. Sie füttern es einfach mit einem beliebigen Datentyp, der sinnvoll ist (der von einer vorhandenen Überlastung akzeptiert wird). Kann das mit optionalen Argumenten nicht übertreffen.

mr.b.
quelle
3

Ich freue mich auf optionale Parameter, da dadurch die Standardeinstellungen näher an der Methode bleiben. Anstelle von Dutzenden von Zeilen für die Überladungen, die nur die "erweiterte" Methode aufrufen, definieren Sie die Methode nur einmal und sehen, was die optionalen Parameter standardmäßig in der Methodensignatur sind. Ich würde lieber schauen:

public Rectangle (Point start = Point.Zero, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

An Stelle von:

public Rectangle (Point start, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

public Rectangle (int width, int height) :
    this (Point.Zero, width, height)
{
}

Natürlich ist dieses Beispiel wirklich einfach, aber im Fall von 5 Überlastungen kann es sehr schnell zu einer Überfüllung kommen.

Mark A. Nicolosi
quelle
7
Ich habe gehört, optionale Parameter sollten zuletzt sein, nicht wahr?
Ilya Ryzhenkov
Kommt auf dein Design an. Vielleicht ist das Argument 'start' normalerweise wichtig, außer wenn dies nicht der Fall ist. Vielleicht haben Sie die gleiche Signatur woanders, was etwas anderes bedeutet. Für ein erfundenes Beispiel, öffentliches Rechteck (int width, int height, Punkt innerSquareStart, Punkt innerSquareEnd) {}
Robert P
13
Nach dem, was sie im Vortrag gesagt haben, müssen optionale Parameter nach den erforderlichen Parametern liegen.
Greg Beech
3

Einer meiner Lieblingsaspekte bei optionalen Parametern ist, dass Sie sehen, was mit Ihren Parametern passiert, wenn Sie sie nicht angeben, auch ohne zur Methodendefinition zu gehen. Visual Studio zeigt Ihnen einfach den Standardwert für den Parameter an, wenn Sie den Methodennamen eingeben. Bei einer Überladungsmethode müssen Sie entweder die Dokumentation lesen (falls überhaupt verfügbar) oder direkt zur Definition der Methode (falls verfügbar) und zu der Methode navigieren, die die Überladung umschließt.

Insbesondere: Der Dokumentationsaufwand kann mit der Anzahl der Überladungen schnell zunehmen, und Sie werden wahrscheinlich bereits vorhandene Kommentare aus den vorhandenen Überladungen kopieren. Dies ist ziemlich ärgerlich, da es keinen Wert erzeugt und das DRY-Prinzip bricht . Andererseits gibt es mit einem optionalen Parameter genau eine Stelle, an der alle Parameter dokumentiert sind, und Sie sehen ihre Bedeutung sowie ihre Standardwerte während der Eingabe.

Last but not least haben Sie als Konsument einer API möglicherweise nicht einmal die Möglichkeit, die Implementierungsdetails zu überprüfen (wenn Sie nicht über den Quellcode verfügen), und haben daher keine Chance zu sehen, zu welcher Supermethode die überladenen gehören wickeln. Sie müssen also das Dokument nicht lesen und hoffen, dass dort alle Standardwerte aufgelistet sind. Dies ist jedoch nicht immer der Fall.

Natürlich ist dies keine Antwort, die alle Aspekte behandelt, aber ich denke, sie fügt eine hinzu, die bisher nicht behandelt wurde.

HimBromBeere
quelle
1

Obwohl dies (angeblich?) Zwei konzeptionell äquivalente Möglichkeiten sind, Ihre API von Grund auf neu zu modellieren, weisen sie leider einen subtilen Unterschied auf, wenn Sie die Abwärtskompatibilität der Laufzeit für Ihre alten Clients in freier Wildbahn berücksichtigen müssen. Mein Kollege (danke Brent!) Hat mich auf diesen wunderbaren Beitrag hingewiesen : Versionierungsprobleme mit optionalen Argumenten . Einige Zitate daraus:

Der Grund, warum optionale Parameter in C # 4 eingeführt wurden, war die Unterstützung von COM-Interop. Das ist es. Und jetzt lernen wir die vollständigen Auswirkungen dieser Tatsache kennen. Wenn Sie eine Methode mit optionalen Parametern haben, können Sie niemals eine Überladung mit zusätzlichen optionalen Parametern hinzufügen, aus Angst, eine Änderung der Kompilierungszeit zu verursachen. Und Sie können eine vorhandene Überlastung niemals entfernen, da dies immer eine Änderung zur Laufzeit war. Sie müssen es so ziemlich wie eine Schnittstelle behandeln. In diesem Fall können Sie nur eine neue Methode mit einem neuen Namen schreiben. Beachten Sie dies, wenn Sie optionale Argumente in Ihren APIs verwenden möchten.

RayLuo
quelle
1

Eine Einschränkung optionaler Parameter ist die Versionierung, bei der ein Refactor unbeabsichtigte Konsequenzen hat. Ein Beispiel:

Anfangscode

public string HandleError(string message, bool silent=true, bool isCritical=true)
{
  ...
}

Angenommen, dies ist einer von vielen Aufrufern der oben genannten Methode:

HandleError("Disk is full", false);

Hier ist das Ereignis nicht still und wird als kritisch behandelt.

Nehmen wir nun an, nach einem Refactor stellen wir fest, dass alle Fehler den Benutzer trotzdem auffordern, sodass wir das stille Flag nicht mehr benötigen. Also entfernen wir es.

Nach dem Refactor

Der frühere Aufruf wird immer noch kompiliert, und sagen wir, er rutscht unverändert durch den Refactor:

public string HandleError(string message, /*bool silent=true,*/ bool isCritical=true)
{
  ...
}

...

// Some other distant code file:
HandleError("Disk is full", false);

Nun falsewird eine unbeabsichtigte Wirkung hat, wird das Ereignis nicht mehr so kritisch behandelt werden.

Dies kann zu einem geringfügigen Fehler führen, da kein Kompilierungs- oder Laufzeitfehler auftritt (im Gegensatz zu einigen anderen Einschränkungen von Optionen wie diesem oder diesem ).

Beachten Sie, dass es viele Formen desselben Problems gibt. Eine andere Form wird hier beschrieben .

Beachten Sie auch, dass die strikte Verwendung benannter Parameter beim Aufrufen der Methode das folgende Problem vermeidet : HandleError("Disk is full", silent:false). Es ist jedoch möglicherweise nicht praktikabel anzunehmen, dass alle anderen Entwickler (oder Benutzer einer öffentlichen API) dies tun.

Aus diesen Gründen würde ich die Verwendung optionaler Parameter in einer öffentlichen API (oder sogar einer öffentlichen Methode, wenn diese weit verbreitet sein könnte) vermeiden, es sei denn, es gibt andere zwingende Überlegungen.

Zach
quelle
0

Beide optionalen Parameter, Methodenüberladung, haben ihren eigenen Vor- oder Nachteil. Es hängt von Ihrer Präferenz ab, zwischen ihnen zu wählen.

Optionaler Parameter: Nur in .Net 4.0 verfügbar. Optionaler Parameter Reduzieren Sie Ihre Codegröße. Sie können keine Parameter definieren und ref

überladene Methoden: Sie können Out- und Ref-Parameter definieren. Die Codegröße nimmt zu, aber überladene Methoden sind leicht zu verstehen.

Sushil Pandey
quelle
0

In vielen Fällen werden optionale Parameter verwendet, um die Ausführung zu wechseln. Beispielsweise:

decimal GetPrice(string productName, decimal discountPercentage = 0)
{

    decimal basePrice = CalculateBasePrice(productName);

    if (discountPercentage > 0)
        return basePrice * (1 - discountPercentage / 100);
    else
        return basePrice;
}

Der Discount-Parameter wird hier verwendet, um die if-then-else-Anweisung einzugeben. Es gibt den Polymorphismus, der nicht erkannt wurde, und der dann als Wenn-Dann-Sonst-Anweisung implementiert wurde. In solchen Fällen ist es viel besser, die beiden Kontrollflüsse in zwei unabhängige Methoden aufzuteilen:

decimal GetPrice(string productName)
{
    decimal basePrice = CalculateBasePrice(productName);
    return basePrice;
}

decimal GetPrice(string productName, decimal discountPercentage)
{

    if (discountPercentage <= 0)
        throw new ArgumentException();

    decimal basePrice = GetPrice(productName);

    decimal discountedPrice = basePrice * (1 - discountPercentage / 100);

    return discountedPrice;

}

Auf diese Weise haben wir die Klasse sogar davor geschützt, einen Anruf ohne Rabatt zu erhalten. Dieser Anruf würde bedeuten, dass der Anrufer denkt, dass es den Rabatt gibt, aber tatsächlich gibt es überhaupt keinen Rabatt. Ein solches Missverständnis kann leicht einen Fehler verursachen.

In solchen Fällen möchte ich keine optionalen Parameter haben, sondern den Aufrufer zwingen, das Ausführungsszenario explizit auszuwählen, das seiner aktuellen Situation entspricht.

Die Situation ist sehr ähnlich zu Parametern, die null sein können. Das ist eine ebenso schlechte Idee, wenn die Implementierung auf Aussagen wie z if (x == null).

Eine detaillierte Analyse finden Sie unter folgenden Links: Vermeiden optionaler Parameter und Vermeiden von Nullparametern

Zoran Horvat
quelle
0

So fügen Sie ein Kinderspiel hinzu, wenn eine Überladung anstelle von Optionen verwendet werden soll:

Wenn Sie eine Reihe von Parametern haben, die nur zusammen Sinn machen, führen Sie keine Optionen ein.

Oder allgemeiner: Wenn Ihre Methodensignaturen Verwendungsmuster aktivieren, die keinen Sinn ergeben, beschränken Sie die Anzahl der Permutationen möglicher Aufrufe. Zum Beispiel durch die Verwendung von Überladungen anstelle von Optionen (diese Regel gilt übrigens auch, wenn Sie mehrere Parameter desselben Datentyps haben; hier können Geräte wie Factory-Methoden oder benutzerdefinierte Datentypen hilfreich sein).

Beispiel:

enum Match {
    Regex,
    Wildcard,
    ContainsString,
}

// Don't: This way, Enumerate() can be called in a way
//         which does not make sense:
IEnumerable<string> Enumerate(string searchPattern = null,
                              Match match = Match.Regex,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);

// Better: Provide only overloads which cannot be mis-used:
IEnumerable<string> Enumerate(SearchOption searchOption = SearchOption.TopDirectoryOnly);
IEnumerable<string> Enumerate(string searchPattern, Match match,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);
Sebastian Mach
quelle