Pregunta

Estaba viendo charla de Anders sobre C # 4.0 y vista previa de C # 5.0 , y Me puse a pensar cuándo estarán disponibles los parámetros opcionales en C #. ¿Cuál será la forma recomendada de declarar métodos que no necesitan todos los parámetros especificados?

Por ejemplo, algo como la clase FileStream tiene aproximadamente quince constructores diferentes que se pueden dividir en 'familias' lógicas, por ejemplo. los de abajo de una cadena, los de IntPtr y los de 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 al tener tres constructores en su lugar, y el uso de parámetros opcionales para los que pueden predeterminarse, lo que haría que las diferentes familias de constructores sean más distintas [nota: sé que este cambio no se hará en el BCL, estoy hablando hipotéticamente para este tipo de situación].

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

¿Fue útil?

Solución

Consideraría lo siguiente:

  • ¿Necesita que su código se use en idiomas que no admiten parámetros opcionales? Si es así, considera incluir las sobrecargas.
  • ¿Tiene algún miembro de 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 le gusta que discutir el caso).
  • ¿Confía en que sus valores predeterminados no cambiarán entre las compilaciones de su código, o si es posible, sus llamadores estarán de acuerdo con eso?

No he comprobado cómo funcionarán los valores predeterminados, pero asumo que los valores predeterminados se incorporarán al código de llamada, de manera muy similar a las referencias a los campos const . Por lo general, eso está bien. Los cambios a un valor predeterminado son bastante significativos de todos modos, pero esas son las cosas a considerar.

Otros consejos

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

Cuando una sobrecarga del método realiza una función diferente según sus parámetros, se seguirá utilizando la sobrecarga.

Usé el respaldo opcional en mis días de VB6 y desde entonces lo perdí, se reducirá la duplicación de comentarios XML en C #.

He estado usando Delphi, con parámetros opcionales, para siempre. He cambiado a utilizar sobrecargas en su lugar.

Porque cuando va a crear más sobrecargas, invariablemente se encontrará con un formulario de parámetros opcional; y luego tendrás que convertirlos a no opcionales.

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

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 puede 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 compilador 4.0, pero no me sorprendería saber que el complaciente simplemente emite las sobrecargas para usted.

Los parámetros opcionales son esencialmente una pieza de metadatos que dirigen a un compilador que procesa una llamada de método para insertar 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 podrían proporcionar los valores predeterminados por sí mismos. Tenga en cuenta que si se intenta llamar a un método que especifica parámetros opcionales a partir del código escrito en un idioma que no los admite, el compilador requerirá que el " opcional " se especifiquen los parámetros, pero dado que llamar a un método sin especificar un parámetro opcional equivale a llamarlo con un parámetro igual al valor predeterminado, no existe ningún obstáculo para que dichos idiomas llamen a estos métodos.

Una consecuencia significativa de la vinculación de parámetros opcionales en el sitio de la 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 conjunto Foo tiene un método Boo (int) con un valor predeterminado de 5, y el conjunto Bar contiene una llamada a Foo .Boo () , el compilador lo procesará como Foo.Boo (5) . Si el valor predeterminado se cambia a 6 y el ensamblaje Foo se vuelve a compilar, Bar continuará llamando a Foo.Boo (5) a menos que o se recompila con esa nueva versión de Foo . Por lo tanto, se debe evitar el uso de parámetros opcionales para cosas que podrían cambiar.

Estoy esperando parámetros opcionales porque mantiene lo que los valores predeterminados están más cerca del método. Así que en lugar de docenas de líneas para las sobrecargas que simplemente llaman a " expandido " En este método, solo define el método una vez y puede ver los parámetros opcionales predeterminados 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 se pueden llenar rápidamente.

Se puede argumentar si los argumentos opcionales o las sobrecargas deben usarse o no, 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 nombrados, son extremadamente útiles cuando se combinan con algunas listas largas de argumentos con todas las opciones 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 hace castings internamente, por ejemplo; simplemente lo alimenta con cualquier tipo de datos que tenga sentido (esto es aceptado por alguna sobrecarga existente). No se puede superar eso con argumentos opcionales.

Uno de mis aspectos favoritos de los parámetros opcionales es que usted ve qué sucede con sus parámetros si no los proporciona, incluso sin tener que 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, se queda atascado con la lectura de la documentación (si está disponible) o con la navegación directa a la definición del método (si está disponible) y al método que la sobrecarga ajusta.

En particular: el esfuerzo de documentación puede aumentar rápidamente con la cantidad de sobrecargas, y probablemente terminará copiando los 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 se documentan todos los parámetros y usted ve su significado, así como sus valores predeterminados mientras escribe.

Por último, pero no menos importante, si eres el consumidor de una API, es posible que ni siquiera tengas la opción de inspeccionar los detalles de la implementación (si no tienes el código fuente) y, por lo tanto, no tienes la oportunidad de ver a qué súper método Los sobrecargados se están envolviendo. Por lo tanto, no puede leer el documento y espera que todos los valores predeterminados aparezcan allí, pero no siempre es así.

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

Una advertencia de los parámetros opcionales es el control de versiones, donde un refactor tiene consecuencias no deseadas. Un ejemplo:

Código inicial

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

Suponga que este es uno de los muchos llamadores 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 un refactor, encontramos que todos los errores incitan al usuario de todos modos, por lo que ya no necesitamos la marca de silencio. Así que lo eliminamos.

Después de refactorizar

La llamada anterior aún se compila, y digamos que se desliza a través del 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 false tendrá un efecto no deseado, el evento ya no se considerará 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 opciones, como this o this ).

Tenga en cuenta que hay muchas formas de este mismo problema. Otra forma se describe aquí .

Tenga en cuenta también que el uso estricto de parámetros con nombre al llamar al método evitará el problema, como por ejemplo: HandleError (" El disco está lleno " ;, silent: false) . Sin embargo, puede que no sea práctico suponer que todos los demás desarrolladores (o usuarios de una API pública) lo harán.

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

Ambos parámetros opcionales, 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 y refinar el parámetro

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

En muchos casos, los parámetros opcionales se utilizan para cambiar de 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 instrucción if-then-else. Existe un polimorfismo que no se reconoció, y luego se implementó como una sentencia 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 de recibir una llamada con un descuento de cero. Esa llamada significaría que la persona que llama cree que existe el descuento, pero en realidad no hay ningún descuento. Tal malentendido puede causar fácilmente un error.

En casos como este, prefiero no tener parámetros opcionales, sino forzar a la persona que llama 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 una idea igualmente mala cuando la implementación se reduce a declaraciones como if (x == null) .

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

Si bien están (¿supuestamente?) dos formas conceptualmente equivalentes disponibles para que usted pueda modelar su API desde cero, desafortunadamente tienen una diferencia sutil cuando necesita considerar la compatibilidad con versiones anteriores en tiempo de ejecución para sus antiguos clientes. Mi colega (gracias Brent!) Me señaló esto post maravilloso: problemas de versiones con argumentos opcionales . Algunas citas de ella:

  

La razón por la que los parámetros opcionales se introdujeron en C # 4 en el   El primer lugar fue para apoyar la interoperabilidad COM. Eso es todo. Y ahora, estamos   aprendiendo sobre las implicaciones completas de este hecho. Si tienes un   Método con parámetros opcionales, nunca se puede agregar una sobrecarga con   Parámetros opcionales adicionales por temor a causar un tiempo de compilación   rompiendo el cambio. Y nunca se puede eliminar una sobrecarga existente, como   Esto siempre ha sido un cambio de ruptura en tiempo de ejecución. Tu casi necesitas   Para tratarlo como una interfaz. Su único recurso en este caso es   Escribe un nuevo método con un nuevo nombre. Así que ten cuidado con esto si planeas   usa argumentos opcionales en tus APIs.

Para agregar una obviedad sobre cuándo usar una sobrecarga en lugar de opcionales:

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

O más generalmente, siempre que las firmas de su método permitan patrones de uso que no tienen sentido, restrinja el número de permutaciones de posibles llamadas. Por ejemplo, al usar sobrecargas en lugar de opcionales (esta regla también se aplica cuando tiene varios parámetros del mismo tipo de datos, por cierto; aquí, los dispositivos como los métodos de fábrica o los 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);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top