opcionales - optional parameter c# string
¿Debería declarar métodos que usan sobrecargas o parámetros opcionales en C#4.0? (13)
Estaba viendo la charla de Anders sobre C # 4.0 y la vista previa de C # 5.0 , y me hizo pensar en cuándo hay disponibles 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 así como la clase FileStream
tiene aproximadamente quince constructores diferentes que se pueden dividir en ''familias'' lógicas, por ejemplo, las de una cadena, las de un IntPtr
y las de 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);
Me parece que este tipo de patrón podría simplificarse al tener tres constructores en su lugar, y usar parámetros opcionales para los que pueden ser predeterminados, lo que haría que las diferentes familias de constructores sean más distintas [nota: sé que este cambio no será hecho en el BCL, estoy hablando hipotéticamente para este tipo de situación].
¿Qué piensas? Desde C # 4.0, ¿tiene más sentido hacer que los grupos de constructores y métodos estrechamente relacionados sean un único método con parámetros opcionales, o existe una buena razón para seguir con el mecanismo tradicional de muchas sobrecargas?
Ambos parámetros opcionales, Método de sobrecarga tienen su propia ventaja o desventaja. Depende de su preferencia para elegir entre ellos.
Parámetro opcional: disponible solo en .Net 4.0. parámetro opcional reduce el tamaño de tu código. No puede definir el parámetro out y ref
métodos sobrecargados: puede definir parámetros de salida y ref. El tamaño del código aumentará, pero los métodos sobrecargados son fáciles de entender.
Consideraría lo siguiente:
- ¿Necesita que su código se use en idiomas que no admiten parámetros opcionales? Si es así, considere 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 te gusta que discutir el caso).
- ¿Estás seguro de que tus valores predeterminados no cambiarán entre las compilaciones de tu código, o si lo hicieran, las personas que llaman estarían 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 campos const
. 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.
Cuando una sobrecarga de método normalmente realiza lo mismo con una cantidad diferente de argumentos, entonces se usará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 continuará utilizando la sobrecarga.
Utilicé la opción opcional en mis días VB6 y desde entonces la he perdido, reducirá una gran cantidad de duplicación de comentarios XML en C #.
Definitivamente usaré la función de parámetros opcionales de 4.0. Se deshace del 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 pone 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 jugué con el compilador 4.0, pero no me sorprendería saber que el compilador simplemente emite las sobrecargas para usted.
En muchos casos, los parámetros opcionales se utilizan 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 fue reconocido, 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 de recibir una llamada con cero descuento. Esa llamada significa 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 forzar al 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 hierve en declaraciones como if (x == null)
.
Puede encontrar un análisis detallado de estos enlaces: cómo evitar los parámetros opcionales y cómo evitar los parámetros nulos
Espero con interés los parámetros opcionales porque mantiene lo que los valores predeterminados están más cerca del método. Entonces, en lugar de docenas de líneas para las sobrecargas que simplemente llaman al método "expandido", usted simplemente define el método una vez y puede ver a qué parámetros predeterminados opta 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 el caso en el OP con 5 sobrecargas, las cosas se pueden llenar muy rápido.
He estado usando Delphi, con parámetros opcionales, para siempre. he cambiado a usar sobrecargas en su lugar.
Porque cuando vas a crear más sobrecargas, invariablemente estarás en conflicto con una forma de parámetro opcional; y luego tendrá que convertirlos a no opcional de todos modos.
Y me gusta la noción de que generalmente hay un supermétodo , y el resto son envolturas más simples alrededor de ese.
Los parámetros opcionales son esencialmente una porción de metadatos que dirige a un compilador que está procesando una llamada de método para insertar los valores predeterminados adecuados en el sitio de llamadas. 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. Tenga en cuenta que si uno intenta llamar a un método que especifica parámetros opcionales a partir de un código escrito en un idioma 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 dichos lenguajes invoquen dichos métodos.
Una consecuencia importante del enlace 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 conjunto Foo
tiene un método Boo(int)
con un valor predeterminado de 5, y la Bar
ensamblaje contiene una llamada a Foo.Boo()
, el compilador lo procesará como un Foo.Boo(5)
. Si el valor predeterminado se cambia a 6 y el conjunto Foo
se vuelve a compilar, Bar
continuará llamando a Foo.Boo(5)
menos que o hasta que se vuelva a compilar con esa nueva versión de Foo
. Por lo tanto, uno debe evitar el uso de parámetros opcionales para cosas que podrían cambiar.
Para agregar un pan comido cuando usar una sobrecarga en lugar de opcionales:
Siempre que tenga una serie de parámetros que solo tienen sentido juntos, no introduzca los opcionales.
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 es válida 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);
Se puede argumentar si se deben usar o no argumentos opcionales o sobrecargas, pero lo más importante es que cada uno tiene su propia área donde son irremplazables.
Los argumentos opcionales, cuando se usan en combinación con argumentos nombrados, son extremadamente útiles cuando se combinan con algunas listas de argumento largo con todas las opciones de llamadas COM.
Las sobrecargas son extremadamente útiles cuando el método es capaz de operar en muchos tipos de argumentos diferentes (solo uno de los ejemplos), y lo hace internamente, por ejemplo; usted solo 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.
Si bien son (¿supuestamente?) Dos formas conceptualmente equivalentes disponibles para que usted pueda modelar su API desde el principio, desafortunadamente tienen una diferencia sutil cuando necesita considerar la compatibilidad hacia atrás en tiempo de ejecución para sus antiguos clientes en la naturaleza. Mi colega (¡gracias Brent!) Me señaló esta maravillosa publicación: Problemas de control de versiones con argumentos opcionales . Algunos citan de esto:
La razón por la que los parámetros opcionales se introdujeron en C # 4 en primer lugar fue para soportar la interoperabilidad COM. Eso es. Y ahora, estamos aprendiendo sobre las implicaciones completas 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 rotura en tiempo de compilación. Y nunca puede eliminar una sobrecarga existente, ya que esto siempre ha sido un cambio en el tiempo de ejecución. Bastante necesitas tratarlo como una interfaz. Su único recurso en este caso es escribir un nuevo método con un nuevo nombre. Así que tenga en cuenta esto si planea usar argumentos opcionales en sus API.
Una advertencia de los parámetros opcionales es el control de versiones, donde un refactor tiene consecuencias imprevistas. Un ejemplo:
Código inicial
public string HandleError(string message, bool silent=true, bool isCritical=true)
{
...
}
Supongamos que esta es una de las muchas personas que llaman al método anterior:
HandleError("Disk is full", false);
Aquí el evento no es silencioso y se trata como crítico.
Ahora, digamos después de un refactor, encontramos que todos los errores le preguntan al usuario de todos modos, por lo que ya no necesitamos el indicador silencioso. Entonces lo eliminamos.
Después de refactor
La primera llamada 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 involuntario, el evento ya no se tratará como crítico.
Esto podría dar como resultado un defecto sutil, ya que no habrá compilación o error de tiempo de ejecución (a diferencia de algunas otras advertencias de opcionales, como this o this ).
Tenga en cuenta que hay muchas formas de este mismo problema. Otra forma se describe here .
Tenga en cuenta también que el uso estricto de parámetros con nombre al llamar al método evitará el problema, como este: HandleError("Disk is full", silent:false)
. Sin embargo, puede no ser práctico suponer 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 en un método público si se utilizara ampliamente) a menos que haya otras consideraciones convincentes.
Uno de mis aspectos favoritos de los parámetros opcionales es que ve lo que sucede con sus parámetros si no los proporciona, 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, tiene que 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 ajusta 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 DRY-principle ). Por otro lado, con un parámetro opcional, hay exactamente un lugar donde se documentan todos los parámetros y se ve su significado, así como sus valores predeterminados, mientras se escribe.
Por último, si usted es el consumidor de una API, es posible que ni siquiera tenga la opción de inspeccionar los detalles de la implementación (si no tiene el código fuente) y, por lo tanto, no tiene posibilidad de ver a qué método super los sobrecargados están envolviendo. Por lo tanto, tiene que leer el documento y esperar 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.