it-swarm.com.de

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

Ich schaute Anders 'Vortrag über C # 4.0 und eine Vorschau auf C # 5.0 und brachte mich zum Nachdenken darüber, wann optionale Parameter in C # verfügbar sind. Auf welche Weise werden Methoden empfohlen, die nicht alle Parameter erfordern spezifizierten?

Beispielsweise hat etwas wie die FileStream-Klasse etwa fünfzehn verschiedene Konstruktoren, die in logische "Familien" unterteilt werden können, z. die unten stehenden aus einer Zeichenfolge, die aus einer IntPtr und 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 als Standard festgelegt werden können, wodurch die verschiedenen Familien der Konstruktoren deutlicher werden würden in der BCL gemacht, ich spreche hypothetisch für diese Art von Situation].

Was denkst du? Ist es ab C # 4.0 sinnvoller, eng verwandte Konstruktor- und Methodengruppen zu einer einzigen Methode mit optionalen Parametern zu machen, oder gibt es einen guten Grund, bei dem traditionellen Mechanismus mit vielen Überladungen zu bleiben?

89
Greg Beech

Ich würde folgendes überlegen:

  • Möchten Sie, dass Ihr Code aus Sprachen verwendet wird, die optionale Parameter nicht unterstützen? Wenn ja, ziehen Sie die Überlastungen in Betracht.
  • Hast du irgendwelche Mitglieder in deinem Team, die freiwillige Parameter gewaltsam ablehnen? (Manchmal ist es einfacher, mit einer Entscheidung zu leben, die Sie nicht mögen, als den Fall zu diskutieren.)
  • Sind Sie zuversichtlich, dass sich Ihre Standardwerte nicht zwischen Builds Ihres Codes ändern, oder wenn dies der Fall ist, werden Ihre Anrufer damit einverstanden sein?

Ich habe nicht überprüft, wie die Standardeinstellungen funktionieren, aber ich würde davon ausgehen, dass die Standardwerte in den aufrufenden Code eingebettet werden, ähnlich wie Verweise auf const-Felder. Das ist normalerweise in Ordnung - Änderungen an einem Standardwert sind sowieso ziemlich wichtig - aber das sind die Dinge, die zu beachten sind.

111
Jon Skeet

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

Wenn eine Methodenüberladung eine Funktion abhängig von ihren Parametern unterschiedlich ausführt, wird die Überladung weiterhin verwendet.

Ich habe optional in meinen VB6-Tagen verwendet und habe es seitdem verpasst. Dadurch werden viele XML-Kommentar-Duplikate in C # reduziert.

17
cfeduke

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

Denn wenn Sie weitere Überladungen erstellen, müssen Sie immer ein optionales Parameterformular verwenden. und Sie müssen sie dann trotzdem in nicht-optional konvertieren.

Und ich mag die Vorstellung, dass es in der Regel eine super - Methode gibt, und der Rest ist einfachere Hülle um diese.

11
Ian Boyd

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 bringt die Werte so, dass der Anrufer sie sehen kann ...

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

Viel einfacher und weniger fehleranfällig. Ich habe das tatsächlich als Fehler in der Überlastungssituation 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 die Überlastungen einfach für Sie aussendet.

7
JP Alioto

Optionale Parameter sind im Wesentlichen Metadaten, die einen Compiler, der einen Methodenaufruf verarbeitet, anweisen, entsprechende Standardwerte an der Aufrufstelle einzufügen. Im Gegensatz dazu stellen Überladungen ein Mittel dar, mit dem ein Compiler eine von mehreren Methoden auswählen kann, von denen einige möglicherweise selbst Standardwerte liefern. Wenn Sie versuchen, eine Methode aufzurufen, die optionale Parameter aus Code angibt, der in einer Sprache geschrieben wurde, die sie nicht unterstützt, muss der Compiler die "optionalen" Parameter angeben. Der Aufruf einer Methode ist jedoch ohne Angabe eines optionalen Parameters erforderlich Äquivalent zum Aufrufen mit einem Parameter, der dem Standardwert entspricht, gibt es für solche Sprachen kein Hindernis, wenn sie solche Methoden aufrufen.

Eine wesentliche Konsequenz der Bindung optionaler Parameter an der Aufrufstelle besteht darin, dass ihnen Werte zugewiesen werden, die auf der Version des Zielcodes basieren, der dem Compiler zur Verfügung steht. Wenn eine Assembly Foo eine Methode Boo(int) mit einem Standardwert von 5 hat und Assembly Bar einen Aufruf an Foo.Boo() enthält, verarbeitet der Compiler dies als Foo.Boo(5). Wenn der Standardwert auf 6 geändert wird und Assembly Foo erneut kompiliert wird, wird Bar den Aufruf von Foo.Boo(5) fortsetzen, es sei denn, oder es wird mit dieser neuen Version von Foo erneut kompiliert. Es sollte daher vermieden werden, optionale Parameter für Dinge zu verwenden, die sich ändern könnten.

6
supercat

Einer meiner Lieblingsaspekte von optionalen Parametern ist, dass Sie sehen, was mit Ihren Parametern geschieht, wenn Sie sie nicht angeben, auch wenn Sie nicht zur Methodendefinition 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 (sofern verfügbar) oder direkt zur Definition der Methode navigieren (falls verfügbar) und zu der Methode, die von der Überladung umschlossen wird. 

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

Wenn Sie ein API-Benutzer sind, haben Sie möglicherweise nicht einmal die Möglichkeit, die Implementierungsdetails zu überprüfen (wenn Sie nicht über den Quellcode verfügen), und haben daher keine Möglichkeit, die überladene Supermethode festzustellen wickeln So bleiben Sie beim Lesen des Dokuments und hoffen, dass dort alle Standardwerte aufgeführt sind. Dies ist jedoch nicht immer der Fall.

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

3
HimBromBeere

Es kann argumentiert werden, ob optionale Argumente oder Überladungen verwendet werden sollen oder nicht, aber am wichtigsten ist, dass jeder 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 Argumenten für lange Argumente mit allen Optionen von COM-Aufrufen kombiniert werden.

Überladungen sind äußerst nützlich, wenn method mit vielen verschiedenen Argumenttypen arbeiten kann (nur eines von Beispielen) und Castings beispielsweise intern ausführt. Sie füttern es einfach mit einem beliebigen Datentyp, der Sinn macht (der von einer vorhandenen Überladung akzeptiert wird). Kann das nicht mit optionalen Argumenten schlagen.

3
mr.b

Ich freue mich auf optionale Parameter, weil dadurch die Standardwerte näher an der Methode bleiben. Anstelle von Dutzenden Zeilen für die Überladungen, die nur die "expandierte" Methode aufrufen, definieren Sie die Methode einfach einmal und Sie können sehen, was die optionalen Parameter standardmäßig in der Methodensignatur verwenden. 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 sehr einfach, aber im OP mit 5 Überladungen können die Dinge sehr schnell überfüllt werden.

3

Ein Nachteil der optionalen Parameter ist die Versionierung, bei der ein Refaktor unbeabsichtigte Folgen hat. Ein Beispiel:

Anfangscode

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

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

HandleError("Disk is full", false);

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

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

Nach Refactor

Der erste Aufruf wird immer noch kompiliert, und wir sagen, er läuft unverändert durch den Refaktor:

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

...

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

Jetzt hat false eine unbeabsichtigte Wirkung, das Ereignis wird nicht mehr als kritisch behandelt. 

Dies kann zu einem geringfügigen Defekt führen, da es keinen Kompilierungs- oder Laufzeitfehler gibt (im Gegensatz zu einigen anderen Einschränkungen von Optionals, wie this oder this ).

Beachten Sie, dass es viele Formen dieses Problems gibt. Eine andere Form ist hier .

Beachten Sie auch, dass das strikte Verwenden von benannten Parametern beim Aufrufen der Methode das Problem vermeidet, z. B. wie folgt: HandleError("Disk is full", silent:false). Es kann jedoch nicht praktikabel sein, anzunehmen, dass alle anderen Entwickler (oder Benutzer einer öffentlichen API) dies tun werden.

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

1
Zach

Die beiden optionalen Parameter Method Überladung haben ihren eigenen Vorteil oder Nachteil. Es hängt von Ihrer Präferenz ab, zwischen ihnen zu wählen.

Optionale Parameter: Nur in .Net 4.0 . Verfügbar. Optionale Parameter reduzieren die Codegröße Sie können keine out- und ref-Parameter definieren 

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

0
sushil pandey

So fügen Sie eine unkomplizierte Option hinzu, wenn Sie anstelle von Optionals eine Überladung verwenden:

Wenn Sie eine Reihe von Parametern haben, die nur zusammen sinnvoll sind, führen Sie keine Optionals ein. 

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

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);
0
Sebastian Mach

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

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 zu füttern. Es gibt den Polymorphismus, der nicht erkannt wurde, und dann wurde er als if-then-else-Anweisung implementiert. 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 vor dem Erhalt eines Anrufs ohne Rabatt geschützt. Dieser Anruf würde bedeuten, dass der Anrufer der Meinung ist, dass es einen Rabatt gibt, tatsächlich aber keinen Rabatt. Ein solches Missverständnis kann leicht zu einem Fehler führen.

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

Die Situation ist sehr ähnlich, Parameter zu haben, die Null sein können. Das ist gleichermaßen eine schlechte Idee, wenn die Implementierung auf Aussagen wie if (x == null) hinausläuft.

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

0
Zoran Horvat

Es gibt (vermutlich?) Zwei konzeptionell äquivalente Möglichkeiten, mit denen Sie Ihre API von Grund auf neu modellieren können. Leider haben sie einen geringfügigen Unterschied, wenn Sie die Laufzeitabwärtskompatibilität Ihrer alten Clients in der Wildnis in Betracht ziehen müssen. Mein Kollege (danke Brent!) Wies mich auf diesen wunderbaren Beitrag: Versionierungsprobleme mit optionalen Argumenten . Einige Zitate daraus:

Der Grund dafür, dass optionale Parameter in C # 4 in .__ eingeführt wurden. An erster Stelle stand die Unterstützung von COM-Interop. Das ist es. Und jetzt sind wir Lernen Sie die vollen Auswirkungen dieser Tatsache kennen. Wenn Sie eine .__ haben. Mit optionalen Parametern können Sie niemals eine Überladung mit .__ hinzufügen. zusätzliche optionale Parameter, aus Angst, eine Kompilierzeit zu verursachen brechen ändern Und Sie können eine vorhandene Überladung niemals als .__ entfernen. Dies war schon immer eine bahnbrechende Änderung. Sie brauchen ziemlich um es wie eine Schnittstelle zu behandeln. Ihr einziger Rückgriff in diesem Fall ist auf Schreiben Sie eine neue Methode mit einem neuen Namen. Seien Sie sich also dessen bewusst, wenn Sie vorhaben, Verwenden Sie optionale Argumente in Ihren APIs.

0
RayLuo