Parámetros opcionales: úsense con precaución

Los parámetros opcionales son una interesante ayuda que hace tiempo que está presente en otros lenguajes como Visual Basic .NET, y ha sido introducida en la versión 4.0 de C#, para alegría de muchos.

A grandes rasgos, esta característica nos permite especificar valores por defecto para los parámetros de nuestros métodos, ahorrándonos tiempo de codificación:

class Program
{
    public static void Main(string[] args)
    {
        Muestra();            // Imprime 1,1
        Muestra(8);           // Imprime 8,1
        Muestra(3,4);         // Imprime 3,4
        Console.ReadKey();
    }
 
    static void Muestra(int a=1, int b=1)
    {
        Console.WriteLine(a + "," + b);
    }
}

Desde siempre, ignorante de mí, había pensado que esto no era más que una triquiñuela del compilador, un azucarillo sintáctico destinado a facilitarnos la creación de sobrecargas de forma rápida, pero, ahora que lo he estudiado algo más a fondo, resulta que no es así. De hecho, los parámetros opcionales están soportados a nivel de plataforma, y funcionan de forma algo extraña (o al menos diferente a lo que podía esperarse), por lo que es conveniente conocerlos bien para no cometer determinados errores.

En primer lugar, me ha llamado la atención que la detección de la ausencia de parámetros en la llamada y la asignación de los valores por defecto no la realiza el método en el que se han definido. Es decir, sobre el ejemplo anterior, no es el método Muestra() el que comprueba si se han suministrado valores para los parámetros a y b, ni el que le asigna los valores por defecto en caso contrario. Esta «comprobación» se realiza en tiempo de compilación (!).

Esto lo demostramos muy fácilmente si descompilamos esta misma aplicación con ayuda del imprescindible Reflector, que nos mostrará el siguiente código:

class Program
{
    public static void Main(string[] args)
    {
        Muestra(1, 1);
        Muestra(8, 1);
        Muestra(3, 4);
        Console.ReadKey();
    }
 
    public static void Muestra([Optional, DefaultParameterValue(1)] int a, 
     [Optional, DefaultParameterValue(1)] int b)
    {
        Console.WriteLine(a + ", " + b);
    }
}

Como se puede observar, se ha generado un método Muestra() cuyos parámetros incluyen atributos que indican su opcionalidad y el valor por defecto en cada caso.

Pero lo curioso está en el método Main(), desde donde se hacen las llamadas, el que podemos observar que las invocaciones a Muestra() incluyen valores para todos los parámetros, como si se tratara de constantes especificadas directamente por el desarrollador.

Por tanto, no hay nada mágico en los métodos con parámetros opcionales, ni sobrecargas, ni código de comprobación o asignación insertado de forma automática. Es el propio compilador el que, en el momento de generar el código IL, extrae los valores por defecto de los parámetros no especificados en la llamada examinando los atributos presentes en la signatura y los introduce en la invocación.

Y es aquí justo donde hay que tener cuidado al utilizar los parámetros opcionales. Dado que el valor de los parámetros se determina en tiempo de compilación y se incluyen como constantes en el código IL generado, pueden producirse efectos no deseados si trabajamos con distintos ensamblados.

Veámoslo con un ejemplo, que, aunque poco real, creo que ilustrará un escenario donde los parámetros opcionales podrían jugarnos una mala pasada.

En el siguiente código, perteneciente al ensamblado LogicaNegocio.dll, vemos un método CalculaImporteconIva(), que retorna un importe aplicándole el impuesto (IVA) correspondiente:

public class LogicaNegocio
{
    public double CalculaImporteConIva(double importe, double iva = 0.16)
    {
        return importe + importe*iva;
    }
}

Así, podemos tener un ensamblado externo, pongamos ERP.exe, que haga uso de este método de la siguiente forma:

public void muestraDesglose(double importe)
{
    double importeTotal = logica.CalculaImporteConIVA(importe)
    // Mostrarlo...
}

En el momento de compilación de ERP.exe, la llamada anterior quedará en el ensamblado resultante igual que si hubiéramos hecho esta llamada:

 
    double importeTotal = logica.CalculaImporteConIVA(importe, 0.16)

Si ahora se produce una subida de IVA (como lamentablemente va a ocurrir en breve), acudiríamos a modificar el valor por defecto del parámetro iva en el método CalculaImporteConIva() y recompilaríamos LogicaNegocio.dll:

public class LogicaNegocio
{
    public double CalculaImporteConIva(double importe, double iva = 0.18)
    {
        return importe + importe*iva;
    }
}

Sin embargo, si no recompilamos ERP.EXE desde éste seguiríamos enviándole el valor anterior (0.16, recordad que este valor aparece como constante en el ensamblado), lo que podía provocar algún problema. Es decir, si queremos mantener la coherencia del sistema, nos veríamos obligados a recompilar todos los ensamblados que referencien LogicaNegocio.dll.

Conclusión: en métodos públicos, y especialmente en aquellos que serán consumidos desde ensamblados externos, es conveniente utilizar parámetros opcionales sólo cuando los valores constantes sean “verdades universales”, como las constantes matemáticas o datos que con toda seguridad no van a cambiar.  No es buena idea utilizarlos para reflejar valores variables o constantes de lógica de negocio, con posibilidades de cambio aunque sean remotas.

Por último, comentar que aunque este post está centrado en C#, todas estas precauciones son igualmente válidas para Visual Basic .NET.

Publicado en: Variable not found

6 comentarios sobre “Parámetros opcionales: úsense con precaución”

  1. Interesante, me hiciste investigar un poco.

    Primero que nada, permítete resaltar la importancia de usar referencias con versiones y firmas digitales, para que el consumidor no acepte ensamblados que no corresponden.

    Luego, dejo el link de un enlace (en inglés) para un estudio un poco más acabado de los parámetros opcionales en c#4 en donde resaltan otros problemas, como que es lo que sucede cuando se implementa un parámetro opcional en una interfaz y luego se sobre-escribe en una implementación (incluye pruebas unitarias).
    http://elegantcode.com/2010/01/28/c-4-0-optional-parameters-exploration/

    En general, si hacen cargas dinámicas de ensamblados, llamaría a programar contra interfaces (pero ya lo hacen de todas formas, cierto?) y evitar incorporar parámetros por defecto en estas interfaces para no generar este tipo de problema.

    Pero por sobre todo, no los usen para cosas importantes como el cálculo del IVA que se sabe de antemano que va a seguir subiendo (pero nunca bajar) 😛

  2. Muy buen post! Lo leí hace algunos días cuando lo publicaste en variablenotfound, pero estaba esperando a que lo pusieras en geeks para decírtelo… 🙂

    En mi opinión el CLR debería de encargarse de eso, y no el compilador. Es decir, en tu ejemplo modificar LogicaNegocio.dll con el nuevo valor del IVA debería afectar a ERP.exe sin necesidad de recompilarlo. Además que el CLR tendría la capacidad para hacerlo puesto que el valor por defecto de los parámetros se guarda en el assembly (creo que con [DefaultValue]).
    Así que el CLR podría hacer esto… la razón de que lo haga el compilador, la desconozco.

    En fin, que le vamos a hacer…

  3. Holas,

    @David, gracias por el aporte! Efectivamente, el IVA no sería precisamente un caso de «constante de negocio», jajaja.

    @Eduard, estás en lo cierto. El valor por defecto de los parámetros opcionales se guarda en el ensamblado con el atributo DefaultValue (de hecho, de ahí se toman al compilar los ensamblados dependientes).

    Supongo que debe haber una buena razón para haber implementado así esta característica, pero la verdad es que no se me ocurre ninguna…

    Gracias a todos por comentar 🙂

  4. hey!! muy buen ejemplo.
    La verdad sabia muy poco de este tema, pero gracias a tus informe, se hace un poco mas claro todo.
    Muchas gracias!!!

    Ces!

  5. hey!! muy buen ejemplo.
    La verdad sabia muy poco de este tema, pero gracias a tus informe, se hace un poco mas claro todo.
    Muchas gracias!!!

    Ces!

Responder a cesmunoz Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *