Devez-vous déclarer des méthodes utilisant des surcharges ou des paramètres facultatifs en C # 4.0?

StackOverflow https://stackoverflow.com/questions/251868

Question

Je regardais le discours d'Anders sur C # 4.0 et un aperçu de C # 5.0 , et cela m’a fait réfléchir sur le moment où les paramètres facultatifs sont disponibles en C #, quelle est la méthode recommandée pour déclarer des méthodes n’ayant pas besoin de tous les paramètres spécifiés?

Par exemple, quelque chose comme la classe FileStream a environ quinze constructeurs différents qui peuvent être divisés en "familles" logiques, par exemple. ceux ci-dessous d'une chaîne, ceux d'un IntPtr et ceux d'un 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);

Il me semble que ce type de modèle pourrait être simplifié en utilisant plutôt trois constructeurs et en utilisant des paramètres facultatifs pour ceux qui peuvent être configurés par défaut, ce qui rendrait les différentes familles de constructeurs plus distincts [note: je connais ce changement. ne sera pas faite en BCL, je parle hypothétiquement de ce genre de situation].

Qu'en penses-tu? À partir de C # 4.0, est-il plus logique de regrouper des groupes de constructeurs et de méthodes étroitement liés en une seule méthode avec des paramètres facultatifs, ou existe-t-il une bonne raison de s'en tenir au mécanisme traditionnel de multi-surcharge?

Était-ce utile?

La solution

Je considérerais ce qui suit:

  • Votre code doit-il être utilisé dans des langues ne prenant pas en charge les paramètres facultatifs? Si tel est le cas, envisagez d’inclure les surcharges.
  • Avez-vous des membres de votre équipe qui s’opposent violemment aux paramètres facultatifs? (Parfois, il est plus facile de vivre avec une décision que vous n'aimez pas que de défendre votre cause.)
  • Êtes-vous confiant que vos valeurs par défaut ne changeront pas entre les versions de votre code, ou si elles le pouvaient, vos appelants seront-ils d'accord avec cela?

Je n'ai pas vérifié comment les valeurs par défaut vont fonctionner, mais je suppose que les valeurs par défaut seront intégrées au code appelant, ce qui revient au même que les références aux champs const . C’est d’habitude correct: les modifications apportées à une valeur par défaut sont quand même assez importantes, mais ce sont les éléments à prendre en compte.

Autres conseils

Quand une surcharge de méthode effectue normalement la même chose avec un nombre différent d'arguments, les valeurs par défaut sont utilisées.

Quand une surcharge de méthode exécute une fonction différemment en fonction de ses paramètres, la surcharge continue à être utilisée.

J'ai utilisé les options en retour dans mes jours VB6 et je l’ai manqué depuis. Cela réduira beaucoup la duplication de commentaires XML en C #.

J'utilise Delphi avec des paramètres facultatifs depuis toujours. Je suis passé à l’utilisation de surcharges.

En effet, lorsque vous créez davantage de surcharges, vous vous en prendrez toujours à une forme de paramètre facultative. et vous devrez alors les convertir en non-facultatif de toute façon.

Et j'aime l'idée qu'il existe généralement une seule et même méthode super , les autres étant plus simples.

J'utiliserai certainement la fonctionnalité de paramètres optionnels de 4.0. Il se débarrasse du ridicule ...

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
}

... et place les valeurs là où l'appelant peut les voir ...

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

Beaucoup plus simple et beaucoup moins sujet aux erreurs. En fait, j’ai vu cela comme un bug dans le cas de surcharge ...

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

Je n'ai pas encore joué avec le compatible 4.0, mais je ne serais pas choqué d'apprendre que ce dernier émet simplement les surcharges pour vous.

Les paramètres facultatifs sont essentiellement des métadonnées qui orientent un compilateur qui traite un appel de méthode pour insérer les paramètres par défaut appropriés sur le site de l'appel. En revanche, les surcharges permettent au compilateur de sélectionner l'une des nombreuses méthodes, dont certaines peuvent fournir elles-mêmes des valeurs par défaut. Notez que si vous essayez d’appeler une méthode qui spécifie des paramètres facultatifs à partir de code écrit dans un langage qui ne les prend pas en charge, le compilateur exige que le paramètre "facultatif" les paramètres doivent être spécifiés, mais comme appeler une méthode sans spécifier de paramètre facultatif revient à l'appeler avec un paramètre égal à la valeur par défaut, il n'y a aucun obstacle à ce que ces langages appellent de telles méthodes.

Une conséquence importante de la liaison de paramètres facultatifs sur le site d’appel est que des valeurs leur seront attribuées en fonction de la version du code cible disponible pour le compilateur. Si un assembly Foo a une méthode Boo (int) avec une valeur par défaut de 5, et que l'assembly Bar contient un appel à Foo .Boo () , le compilateur traitera cela comme un Foo.Boo (5) . Si la valeur par défaut est modifiée à 6 et que l'assembly Foo est recompilé, Bar continuera d'appeler Foo.Boo (5) sauf si ou jusqu'à ce que est recompilé avec cette nouvelle version de Foo . Il faut donc éviter d’utiliser des paramètres optionnels pour les choses qui pourraient changer.

J'attends avec impatience les paramètres facultatifs, car ils conservent les valeurs par défaut plus proches de la méthode. Ainsi, au lieu de dizaines de lignes pour les surcharges, il suffit d'appeler le mot-clé "développé". méthode, il vous suffit de définir la méthode une fois et vous pouvez voir quels paramètres facultatifs sont définis par défaut dans la signature de la méthode. Je préfère regarder:

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

Au lieu de ceci:

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)
{
}

Évidemment, cet exemple est très simple, mais dans le cas de l'OP avec 5 surcharges, les choses peuvent devenir très encombrées très rapidement.

On peut se demander si des arguments optionnels ou des surcharges doivent être utilisés ou non, mais surtout, chacun a son propre domaine où ils sont irremplaçables.

Les arguments facultatifs, lorsqu'ils sont utilisés en combinaison avec des arguments nommés, sont extrêmement utiles lorsqu'ils sont combinés avec des listes d'appels COM à longs arguments, avec de longues listes d'arguments.

Les surcharges sont extrêmement utiles lorsque la méthode est capable de fonctionner sur de nombreux types d'arguments différents (un exemple parmi d'autres) et qu'elle effectue des conversions en interne, par exemple; vous venez de le nourrir avec n'importe quel type de données qui a du sens (qui est accepté par une surcharge existante). Ne peut pas battre cela avec des arguments optionnels.

L’un de mes aspects préférés des paramètres facultatifs est que vous voyez ce qu’il advient de vos paramètres si vous ne les fournissez pas, même sans passer à la définition de la méthode. Visual Studio affiche simplement la valeur par défaut du paramètre lorsque vous tapez le nom de la méthode. Avec une méthode de surcharge, vous êtes obligé soit de lire la documentation (si elle est disponible), soit de naviguer directement vers la définition de la méthode (si disponible) et vers la méthode encapsulée par la surcharge.

En particulier: l'effort de documentation peut augmenter rapidement avec le nombre de surcharges, et vous finirez probablement par copier les commentaires existants à partir des surcharges existantes. Ceci est assez gênant, car il ne produit aucune valeur et casse le principe DRY . ). D'autre part, avec un paramètre facultatif, il existe exactement un emplacement où tous les paramètres sont documentés et vous voyez leur signification ainsi que leurs valeurs par défaut lors de la frappe.

Enfin, si vous êtes le consommateur d'une API, vous n'aurez peut-être même pas la possibilité d'inspecter les détails de la mise en oeuvre (si vous n'avez pas le code source) et donc de ne pas savoir à quelle super-méthode les surchargés enveloppent. Vous êtes donc coincé avec la lecture du document et espérez que toutes les valeurs par défaut y sont répertoriées, mais ce n’est pas toujours le cas.

Bien sûr, ce n’est pas une réponse qui traite tous les aspects, mais je pense qu’elle en ajoute une qui n’a pas encore été abordée.

L'un des inconvénients des paramètres facultatifs est le contrôle de version, dans lequel un refactor a des conséquences inattendues. Un exemple:

Code initial

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

Supposons qu'il s'agit d'un des nombreux appelants de la méthode ci-dessus:

HandleError("Disk is full", false);

Ici, l'événement n'est pas silencieux et est traité comme critique.

Supposons maintenant qu’après un refactor, toutes les erreurs incitent l’utilisateur à ne plus avoir besoin de l’indicateur silencieux. Alors on l'enlève.

Après le refactor

Le premier appel est toujours compilé, et disons qu'il passe le refactor sans changement:

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

...

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

Maintenant, false aura un effet inattendu, l'événement ne sera plus traité comme étant critique.

Cela pourrait entraîner un défaut subtil, car il n'y aura pas d'erreur de compilation ou d'exécution (contrairement à d'autres mises en garde des options, telles que this ou this ).

Notez qu'il existe plusieurs formes de ce même problème. Un autre formulaire est décrit ici .

Notez également que l'utilisation stricte de paramètres nommés lors de l'appel de la méthode évitera le problème, comme suit: HandleError ("Le disque est plein", silent: false) . Cependant, il peut ne pas être pratique de supposer que tous les autres développeurs (ou utilisateurs d'une API publique) le feront.

Pour ces raisons, j’éviterais d’utiliser des paramètres facultatifs dans une API publique (ou même dans le cas d’une méthode publique si elle pouvait être utilisée à grande échelle), à ??moins d’autres considérations convaincantes.

Le paramètre Facultatif, surcharge de méthode, présente un avantage ou un inconvénient. Il dépend de votre préférence pour choisir entre eux.

Paramètre facultatif: disponible uniquement en .Net 4.0. paramètre facultatif réduire la taille de votre code. Vous ne pouvez pas définir les paramètres out et ref

méthodes surchargées: Vous pouvez définir les paramètres Out et Ref. La taille du code augmentera, mais les méthodes surchargées sont faciles à comprendre.

Dans de nombreux cas, des paramètres facultatifs sont utilisés pour commuter l'exécution. Par exemple:

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

    decimal basePrice = CalculateBasePrice(productName);

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

Le paramètre discount sert ici à alimenter l'instruction if-then-else. Il y a le polymorphisme qui n'a pas été reconnu, puis il a été implémenté en tant qu'instruction if-then-else. Dans ce cas, il est préférable de scinder les deux flux de contrôle en deux méthodes indépendantes:

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;

}

De cette manière, nous avons même protégé la classe des appels sans remise. Cet appel signifierait que l'appelant pense qu'il y a un rabais, mais en réalité, il n'y a pas de rabais du tout. Un tel malentendu peut facilement causer un bogue.

Dans de tels cas, je préfère ne pas avoir de paramètres facultatifs, mais forcer l'appelant à sélectionner explicitement le scénario d'exécution qui convient à sa situation actuelle.

La situation est très similaire à celle d'avoir des paramètres pouvant être nuls. C'est également une mauvaise idée lorsque l'implémentation se résume à des instructions telles que if (x == null) .

Vous trouverez une analyse détaillée sur ces liens: Éviter les paramètres facultatifs et Éviter les paramètres nuls

Bien qu'ils soient (supposés?) comme deux méthodes équivalentes sur le plan conceptuel pour modéliser votre API, ils présentent malheureusement une différence subtile lorsque vous devez prendre en compte la compatibilité en amont de l'exécution pour vos anciens clients. Mon collègue (merci Brent!) M'a signalé ce point: post merveilleux: problèmes de gestion des versions avec des arguments facultatifs . Quelques citations:

  

La raison pour laquelle des paramètres facultatifs ont été introduits dans C # 4 dans   La première place était de soutenir COM Interop. C'est tout. Et maintenant, nous sommes   en apprendre davantage sur toutes les implications de ce fait. Si tu as un   méthode avec des paramètres facultatifs, vous ne pouvez jamais ajouter une surcharge avec   paramètres optionnels supplémentaires par peur de causer un temps de compilation   briser le changement. Et vous ne pouvez jamais supprimer une surcharge existante, comme   cela a toujours été un changement radical à l'exécution. Tu as bien besoin   le traiter comme une interface. Votre seul recours dans ce cas est de   écrire une nouvelle méthode avec un nouveau nom. Soyez donc conscient de cela si vous envisagez de   utilisez des arguments optionnels dans vos API.

Pour ajouter une évidence quand utiliser une surcharge au lieu des options:

Chaque fois que vous avez un certain nombre de paramètres qui n'ont de sens que tous ensemble, n'introduisez pas de paramètres en option.

Ou plus généralement, chaque fois que les signatures de votre méthode permettent des modèles d'utilisation qui n'ont pas de sens, limitez le nombre de permutations d'appels possibles. Par exemple, en utilisant des surcharges au lieu des options (cette règle est également vraie si vous avez plusieurs paramètres du même type de données, soit dit en passant; ici, les périphériques tels que les méthodes d'usine ou les types de données personnalisés peuvent vous aider).

Exemple:

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);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top