¿Debería declarar métodos usando sobrecargas o parámetros opcionales en C # 4.0?

94

Estaba viendo la charla de Anders sobre C # 4.0 y la vista previa de C # 5.0 , y me hizo pensar cuando los parámetros opcionales están disponibles en C #, ¿cuál será la forma recomendada de declarar métodos que no necesitan todos los parámetros especificados?

Por ejemplo, algo así como la FileStreamclase tiene alrededor de quince constructores diferentes que se pueden dividir en 'familias' lógicas, por ejemplo, los de abajo de una cadena, los de an IntPtry los de a 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);

Me parece que este tipo de patrón podría simplificarse teniendo tres constructores en su lugar, y usando parámetros opcionales para los que pueden estar predeterminados, lo que haría que las diferentes familias de constructores sean más distintas [nota: sé que este cambio no será hecho en la BCL, estoy hablando hipotéticamente para este tipo de situación].

¿Qué piensas? A partir de C # 4.0, ¿tendrá más sentido convertir los grupos de constructores y métodos estrechamente relacionados en un método único con parámetros opcionales, o hay una buena razón para seguir con el mecanismo tradicional de sobrecarga múltiple?

Greg Beech
fuente

Respuestas:

122

Consideraría lo siguiente:

  • ¿Necesita que su código se utilice en idiomas que no admiten parámetros opcionales? Si es así, considere incluir las sobrecargas.
  • ¿Tiene algún miembro en su equipo que se oponga violentamente a los parámetros opcionales? (A veces es más fácil vivir con una decisión que no te gusta que argumentar el caso).
  • ¿Está seguro de que sus valores predeterminados no cambiarán entre las compilaciones de su código o, si lo hacen, las personas que llaman estarán de acuerdo con eso?

No he comprobado cómo van a funcionar los valores predeterminados, pero supongo que los valores predeterminados se incluirán en el código de llamada, al igual que las referencias a los constcampos. Por lo general, eso está bien, los cambios en un valor predeterminado son bastante significativos de todos modos, pero esas son las cosas a considerar.

Jon Skeet
fuente
21
+1 por la sabiduría sobre el pragmatismo: a veces es más fácil vivir con una decisión que no te gusta que argumentar el caso.
legends2k
13
@romkyns: No, el efecto de las sobrecargas no es lo mismo con el punto 3. Con las sobrecargas que proporcionan los valores predeterminados, los valores predeterminados están dentro del código de la biblioteca , por lo que si cambia el valor predeterminado y proporciona una nueva versión de la biblioteca, las personas que llaman vea el nuevo valor predeterminado sin recompilación. Mientras que con los parámetros opcionales, necesitaría volver a compilar para "ver" los nuevos valores predeterminados. Muchas veces no es una distinción importante, pero es una distinción.
Jon Skeet
hola @JonSkeet, me gustaría saber si usamos ambas, es decir, la función con parámetro opcional y otra con sobrecarga, ¿qué método se llamará? por ejemplo, Add (int a, int b) y Add (int a, int b, int c = 0) y la llamada a la función dice: Add (5,10); ¿Qué método se llamará función sobrecargada o función de parámetro opcional? gracias :)
SHEKHAR SHETE
@Shekshar: ¿Lo has probado? Lea las especificaciones para obtener más detalles, pero básicamente en un desempate gana un método en el que el compilador no ha tenido que completar ningún parámetro opcional.
Jon Skeet
@JonSkeet acaba de probar con lo anterior ... la función de sobrecarga gana sobre el parámetro opcional :)
SHEKHAR SHETE
19

Cuando una sobrecarga de método normalmente realiza lo mismo con un número diferente de argumentos, se utilizarán los valores predeterminados.

Cuando una sobrecarga de método realiza una función de manera diferente en función de sus parámetros, se seguirá utilizando la sobrecarga.

Usé opcional en mis días de VB6 y desde entonces lo he perdido, reducirá una gran cantidad de duplicación de comentarios XML en C #.

cfeduke
fuente
11

He estado usando Delphi con parámetros opcionales desde siempre. En su lugar, cambié a usar sobrecargas.

Porque cuando va a crear más sobrecargas, invariablemente entrará en conflicto con un formulario de parámetro opcional, y luego tendrá que convertirlos a no opcionales de todos modos.

Y me gusta la idea de que generalmente hay un súper método, y el resto son envoltorios más simples sobre ese.

Ian Boyd
fuente
1
Estoy muy de acuerdo con esto, sin embargo, existe la advertencia de que cuando tienes un método que toma múltiples (3+) parámetros, que son por naturaleza todos "opcionales" (podrían ser sustituidos por uno predeterminado) podrías terminar con muchas permutaciones de la firma del método sin más beneficio. Considere Foo(A, B, C)requiere Foo(A), Foo(B), Foo(C), Foo(A, B), Foo(A, C), Foo(B, C).
Dan Lugg
7

Definitivamente usaré la función de parámetros opcionales de 4.0. Se deshace de lo ridículo ...

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
}

... y coloca los valores justo donde la persona que llama pueda verlos ...

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

Mucho más simple y mucho menos propenso a errores. De hecho, he visto esto como un error en el caso de sobrecarga ...

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

Todavía no he jugado con el complier 4.0, pero no me sorprendería saber que el complier simplemente emite las sobrecargas por ti.

JP Alioto
fuente
6

Los parámetros opcionales son esencialmente una pieza de metadatos que dirige a un compilador que está procesando una llamada a un método para que inserte los valores predeterminados apropiados en el sitio de la llamada. Por el contrario, las sobrecargas proporcionan un medio por el cual un compilador puede seleccionar uno de varios métodos, algunos de los cuales pueden proporcionar valores predeterminados por sí mismos. Tenga en cuenta que si uno intenta llamar a un método que especifica parámetros opcionales a partir de código escrito en un lenguaje que no los admite, el compilador requerirá que se especifiquen los parámetros "opcionales", pero dado que llamar a un método sin especificar un parámetro opcional es equivalente a llamarlo con un parámetro igual al valor predeterminado, no hay ningún obstáculo para que tales lenguajes llamen a tales métodos.

Una consecuencia importante de la vinculación de parámetros opcionales en el sitio de llamada es que se les asignarán valores basados ​​en la versión del código de destino que está disponible para el compilador. Si un ensamblado Footiene un método Boo(int)con un valor predeterminado de 5, y el ensamblado Barcontiene una llamada a Foo.Boo(), el compilador lo procesará como Foo.Boo(5). Si el valor predeterminado se cambia a 6 y Foose vuelve a compilar el ensamblado , Barcontinuará llamando a Foo.Boo(5)menos o hasta que se vuelva a compilar con esa nueva versión de Foo. Por lo tanto, se debe evitar el uso de parámetros opcionales para cosas que puedan cambiar.

Super gato
fuente
Re: "Por lo tanto, se debe evitar el uso de parámetros opcionales para cosas que podrían cambiar". Estoy de acuerdo en que esto puede ser problemático si el código del cliente pasa desapercibido. Sin embargo, el mismo problema existe cuando el valor por defecto está oculta dentro de una sobrecarga del método: void Foo(int value) … void Foo() { Foo(42); }. Desde el exterior, la persona que llama no sabe qué valor predeterminado se utiliza, ni cuándo podría cambiar; uno tendría que monitorear la documentación escrita para eso. Los valores predeterminados para los parámetros opcionales pueden verse simplemente como eso: documentación en código cuál es el valor predeterminado.
stakx - ya no contribuye el
@stakx: Si una sobrecarga sin parámetros se encadena a una sobrecarga con un parámetro, cambiar el valor "predeterminado" de ese parámetro y volver a compilar la definición de la sobrecarga cambiará el valor que usa incluso si el código de llamada no se vuelve a compilar .
supercat
Es cierto, pero eso no lo hace más problemático que la alternativa. En un caso (sobrecarga del método), el código de llamada no tiene voz en el valor predeterminado. Esto puede ser apropiado si al código de llamada realmente no le importa en absoluto el parámetro opcional y lo que significa. En el otro caso (parámetro opcional con valor predeterminado), el código de llamada compilado previamente no se verá afectado cuando cambie el valor predeterminado. Esto también puede ser apropiado cuando el código de llamada realmente se preocupa por el parámetro; omitirlo en la fuente es como decir, "el valor predeterminado sugerido actualmente está bien para mí".
stakx - ya no contribuye el
El punto que estoy tratando de señalar aquí es que, si bien hay consecuencias para cualquiera de los enfoques (como usted señaló), no son inherentemente ventajosas o desventajosas. Eso depende también de las necesidades y objetivos del código de llamada. Desde ese punto de vista, encontré el veredicto en la última oración de su respuesta algo demasiado rígido.
stakx - ya no contribuye el
@stakx: Dije "evitar usar" en lugar de "nunca usar". Si cambiar X significará que la próxima recompilación de Y cambiará el comportamiento de Y, eso requerirá que se configure un sistema de compilación para que cada recompilación de X también recompile Y (ralentizando las cosas), o creará el riesgo de que un programador cambie X de una manera que romperá Y la próxima vez que se compile, y solo descubrirá dicha ruptura en un momento posterior cuando Y se cambie por alguna razón totalmente ajena. Los parámetros predeterminados solo deben usarse cuando sus ventajas superen dichos costos.
supercat
4

Se puede argumentar si deben usarse o no argumentos opcionales o sobrecargas, pero lo más importante es que cada uno tiene su propia área donde son insustituibles.

Los argumentos opcionales, cuando se usan en combinación con argumentos con nombre, son extremadamente útiles cuando se combinan con algunas listas de argumentos largos con todos los opcionales de llamadas COM.

Las sobrecargas son extremadamente útiles cuando el método puede operar en muchos tipos de argumentos diferentes (solo uno de los ejemplos) y realiza conversiones internamente, por ejemplo; simplemente lo alimenta con cualquier tipo de datos que tenga sentido (que sea aceptado por alguna sobrecarga existente). No se puede superar eso con argumentos opcionales.

mr.b
fuente
3

Espero con interés los parámetros opcionales porque mantiene los valores predeterminados más cercanos al método. Entonces, en lugar de docenas de líneas para las sobrecargas que simplemente llaman al método "expandido", simplemente defina el método una vez y podrá ver lo que los parámetros opcionales tienen por defecto en la firma del método. Prefiero mirar:

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

En lugar de esto:

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

Obviamente, este ejemplo es realmente simple, pero en el caso del OP con 5 sobrecargas, las cosas pueden llenarse muy rápido.

Mark A. Nicolosi
fuente
7
Escuché que los parámetros opcionales deberían ser los últimos, ¿no?
Ilya Ryzhenkov
Depende de tu diseño. Quizás el argumento de 'inicio' es normalmente importante, excepto cuando no lo es. Quizás tenga la misma firma en otro lugar, lo que significa algo diferente. Para un ejemplo artificial, public Rectangle (int width, int height, Point innerSquareStart, Point innerSquareEnd) {}
Robert P
13
Por lo que dijeron en la charla, los parámetros opcionales deben estar después de los parámetros requeridos.
Greg Beech
3

Uno de mis aspectos favoritos de los parámetros opcionales es que ves lo que sucede con tus parámetros si no los proporcionas, incluso sin ir a la definición del método. Visual Studio simplemente le mostrará el valor predeterminado para el parámetro cuando escriba el nombre del método. Con un método de sobrecarga, no puede leer la documentación (si está disponible) o navegar directamente a la definición del método (si está disponible) y al método que envuelve la sobrecarga.

En particular: el esfuerzo de documentación puede aumentar rápidamente con la cantidad de sobrecargas, y probablemente terminará copiando comentarios ya existentes de las sobrecargas existentes. Esto es bastante molesto, ya que no produce ningún valor y rompe el principio DRY ). Por otro lado, con un parámetro opcional, hay exactamente un lugar donde todos los parámetros están documentados y puede ver su significado, así como sus valores predeterminados mientras escribe.

Por último, pero no menos importante, si usted es el consumidor de una API, es posible que ni siquiera tenga la opción de inspeccionar los detalles de implementación (si no tiene el código fuente) y, por lo tanto, no tendrá la oportunidad de ver a qué súper método están sobrecargados. están envolviendo. Por lo tanto, está atascado leyendo el documento y esperando que todos los valores predeterminados aparezcan allí, pero este no es siempre el caso.

Por supuesto, esta no es una respuesta que maneje todos los aspectos, pero creo que agrega una que no se ha cubierto hasta ahora.

HimBromBeere
fuente
1

Si bien son (¿supuestamente?) Dos formas conceptualmente equivalentes disponibles para modelar su API desde cero, desafortunadamente tienen una diferencia sutil cuando necesita considerar la compatibilidad con versiones anteriores del tiempo de ejecución para sus antiguos clientes en la naturaleza. Mi colega (¡gracias Brent!) Me señaló esto maravillosa publicación: Problemas de versiones con argumentos opcionales . Algunos lo citan:

La razón por la que se introdujeron parámetros opcionales en C # 4 en primer lugar fue para admitir la interoperabilidad COM. Eso es. Y ahora, estamos aprendiendo todas las implicaciones de este hecho. Si tiene un método con parámetros opcionales, nunca puede agregar una sobrecarga con parámetros opcionales adicionales por temor a causar un cambio de ruptura en tiempo de compilación. Y nunca se puede eliminar una sobrecarga existente, ya que siempre ha sido un cambio decisivo en el tiempo de ejecución. Necesitas tratarlo como una interfaz. Su único recurso en este caso es escribir un nuevo método con un nuevo nombre. Así que tenga esto en cuenta si planea utilizar argumentos opcionales en sus API.

RayLuo
fuente
1

Una advertencia de los parámetros opcionales es el control de versiones, donde una refactorización tiene consecuencias no deseadas. Un ejemplo:

Código inicial

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

Suponga que esta es una de las muchas personas que llaman del método anterior:

HandleError("Disk is full", false);

Aquí el evento no es silencioso y se trata como crítico.

Ahora digamos que después de una refactorización encontramos que todos los errores solicitan al usuario de todos modos, por lo que ya no necesitamos la bandera silenciosa. Entonces lo quitamos.

Después de refactorizar

La llamada anterior aún se compila, y digamos que se desliza por el refactor sin cambios:

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

...

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

Ahora falsetendrá un efecto no deseado, el evento ya no se tratará como crítico.

Esto podría resultar en un defecto sutil, ya que no habrá error de compilación o tiempo de ejecución (a diferencia de otras advertencias de opcionales, como este o este ).

Tenga en cuenta que existen muchas formas de este mismo problema. Aquí se describe otra forma .

Nótese también que estrictamente el uso de parámetros con nombre al llamar al método evitará la emisión, como la siguiente: HandleError("Disk is full", silent:false). Sin embargo, puede que no sea práctico asumir que todos los demás desarrolladores (o usuarios de una API pública) lo harán.

Por estas razones, evitaría el uso de parámetros opcionales en una API pública (o incluso un método público si pudiera usarse ampliamente) a menos que haya otras consideraciones convincentes.

Zach
fuente
0

Tanto el parámetro opcional como la sobrecarga del método tienen su propia ventaja o desventaja. Depende de su preferencia para elegir entre ellos.

Parámetro opcional: disponible solo en .Net 4.0. El parámetro opcional reduce el tamaño de su código. No se puede definir el parámetro out y ref

métodos sobrecargados: puede definir parámetros de salida y de referencia. El tamaño del código aumentará, pero los métodos sobrecargados son fáciles de entender.

sushil pandey
fuente
0

En muchos casos, se utilizan parámetros opcionales para cambiar la ejecución. Por ejemplo:

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

    decimal basePrice = CalculateBasePrice(productName);

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

El parámetro de descuento aquí se usa para alimentar la declaración if-then-else. Existe el polimorfismo que no se reconoció y luego se implementó como una declaración if-then-else. En tales casos, es mucho mejor dividir los dos flujos de control en dos métodos independientes:

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 esta manera, incluso hemos protegido a la clase para que no reciba una llamada sin descuento. Esa llamada significaría que la persona que llama piensa que existe el descuento, pero de hecho no hay ningún descuento. Tal malentendido puede causar fácilmente un error.

En casos como este, prefiero no tener parámetros opcionales, sino obligar al llamador a seleccionar explícitamente el escenario de ejecución que se adapte a su situación actual.

La situación es muy similar a tener parámetros que pueden ser nulos. Esa es igualmente mala idea cuando la implementación se reduce a declaraciones como if (x == null).

Puede encontrar un análisis detallado en estos enlaces: Evitar parámetros opcionales y Evitar parámetros nulos

Zoran Horvat
fuente
0

Para agregar una obviedad cuando usar una sobrecarga en lugar de opcionales:

Siempre que tenga una serie de parámetros que solo tienen sentido juntos, no introduzca opcionales en ellos.

O de manera más general, siempre que las firmas de su método habiliten patrones de uso que no tienen sentido, restrinja el número de permutaciones de posibles llamadas. Por ejemplo, mediante el uso de sobrecargas en lugar de opcionales (esta regla también se cumple cuando tiene varios parámetros del mismo tipo de datos, por cierto; aquí, dispositivos como métodos de fábrica o tipos de datos personalizados pueden ayudar).

Ejemplo:

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);
Sebastián Mach
fuente