[C# Básico] Métodos con parámetros variables

Publicado 2/5/2012 12:07 por Eduard Tomàs i Avellana

¡Hey! Dos entradas de la serie C# Básico en menos de un mes… ¿Señal de algo? Quien sabe… :P

Antes que nada el aviso típico de esta serie: En esos posts exploramos elementos, digamos, básicos del lenguaje. No es un tutorial ni un libro ni nada. Cada post es independiente del resto y pueden ser leídos en el orden en que prefiráis… Dicho esto, al tajo.

Bueno, en este post veremos como funcionan los métodos con parámetros variables en C#. Cuando digo parámetros variables me refiero a que el número de parámetros es variable (o sea le puedes pasar 1 parámetro, ó 10 ó 100).

Primera opción: sobrecarga

Cuando aprendemos C# por norma general nos dicen que al definir un método debemos indicar que parámetros acepta, y que todos esos parámetros deben pasarse para invocar el método. Tranquilo, no te han engañado, es cierto. Todos sabemos que si tengo un método:

class Foo

{

    public void DoBar(int baz, string bazbar) { }

}

Si quiero invocar el método DoBar, debo pasarle los dos parámetros: un int y una cadena. No hay otra. O se hace o el código no compila.

Entonces… ¿como puedo hacer métodos con parámetros variables? Pues, la primera opción, muy usada, para proporcionar la ilusión de un número variable de parámetros (pues en este caso se trata de una ilusión) es aprovechar el mecanismo de sobrecarga. Sobrecargar un método significa que hay dos métodos métodos independientes pero que tienen el mismo nombre. El compilador tan solo pedirá una cosa: que la lista de parámetros sea distinta, o bien en número, o bien en el tipo de parámetros. Luego, cuando se llama el método, el compilador invocará al método que toque según el tipo y número de parámetros que se le pasen. Si alguien viene de C (pero no de C++) igual le sorprende esto, pero es realmente útil. Recuerdas toda aquella pléyade de funciones itoa, ltoa, ultoa y similares? Todas hacían lo mismo, convertir un número a cadena, pero claro la primera convertía un int, la segunda un long y la tercera un unsigned long. Esto mismo en C# se puede conseguir declarando tres funciones, pero todas ellas llamadas igual, p.ej. numberToString y teniendo cada una un tipo de parámetros variables. Así tenemos que recordar un solo nombre de método en lugar de n-mil.

El framework está lleno de métodos sobrecargados. ¿Un ejemplo? El método ToInt32 de la clase Convert:

image

Aquí lo veis, tiene 19 sobrecargas! Eso significa que hay 19 métodos llamados ToInt32 en la clase Convert. Insisto en que se trata de métodos independientes, si se quisiera el tipo de retorno podría ser distinto en cada caso y por supuesto podrían hacer cosas completamente distintas  (aunque hay que estar un poco mal de la cabeza para poner dos métodos que se llamen igual pero hagan cosas distintas :p).

Sobrecargando un método puedo dar la ilusión de que el número de parámetros es variable:

class Foo

{

    public void DoBar(int baz, string bazbar) { }

    public void DoBar(int baz) { }

    public void DoBar(string bazbar) { }

}

class Program

{

    static void Main(string[] args)

    {

        var foo = new Foo();

        foo.DoBar(10);

        foo.DoBar("edu");

        foo.DoBar(20, "edu");

    }

Bueno, esta técnica puede usarse para casos con pocos parámetros, pero en el fondo no tenemos realmente un “número de parámetros variable”, ya que no le puedo pasar 8 parámetros a DoBar.

Segunda opción: params

Antes que nada, a alguien se le ocurre un método en C# en el que se le pueda pasar un número arbitrario de parámetros (uno, diez o cien)? Pues hay varios, pero el más conocido es string.Format.

Seguro que todos habéis usado string.Format. Y sabéis que en función del primer parámetro, se le pasan luego tantos parámetros adicionales como sea necesario. La verdad es que string.Format tiene varias sobrecargas, pero te lo aseguro, no tiene todas las posibles. Entonces, ¿como lo hace? Pues usando la palabra clave params en C#:

class Foo

{

    public void DoBar(int baz, params string[] bazbar) { }

}

class Program

{

    static void Main(string[] args)

    {

        var foo = new Foo();

        foo.DoBar(10, "hola", "adios", "bye");

        foo.DoBar(20);

        foo.DoBar(10, "hi!");

    }

}

Este código compila y es perfectamente válido. El método DoBar tiene realmente dos parámetros: un int y un array de cadenas. Pero el uso de la palabra clave params permite pasar todas las cadenas del array no como un array sino como n parámetros separados por comas. Pero lo que DoBar recibe es un array de cadenas. Así el siguiente programa:

    public void DoBar(int baz, params string[] bazbar)

    {

        Console.WriteLine("# cadenas: " + bazbar.Length);

        for (int i = 0; i < bazbar.Length; i++)

        {

            Console.Write(bazbar[i] + ",");

        }

        Console.WriteLine();

    }

}

class Program

{

    static void Main(string[] args)

    {

        var foo = new Foo();

        foo.DoBar(10, "hola", "adios", "bye");

        foo.DoBar(20);

        foo.DoBar(10, "hi!");

    }

}

Genera la siguiente salida:

image

Así como podéis ver, realmente el método DoBar tiene dos parámetros: un entero que debo pasar siempre y un array de cadenas que el compilador me deja pasar como si fuesen N parámetros, pero que realmente es un array de N cadenas. De hecho, incluso eso es válido:

foo.DoBar(10, new string[] { "hola", "adios" });

En resumen, el uso de la palabra clave params me permite pasar un array como si fuesen N parámetros, pero no me obliga a ello (puedo seguir pasando el array como un array). Pero recordad: es un truco del compilador ;-)

¡Ah, s¡! Y tened presente que:

  1. Tan solo puedo tener un parámetro params en un método…
  2. …Y debe ser el último

Por supuesto, si queréis que los “parámetros variables” puedan ser de cualquier tipo, podéis declarar que vuestro método recibe un params[] object:

class Foo

{

    public void DoBar(int baz, params object[] bazbar) { }

}

class Program

{

    static void Main(string[] args)

    {

        var foo = new Foo();

        foo.DoBar(10, "veinte", 20, new ClaseMia());

    }

}

En este caso, dentro de DoBar, bazbar es un array de objects con dos objetos (una cadena “veinte” y un objeto ClaseMia). Por supuesto que el método DoBar sepa de que tipo son los objetos que están dentro de bazbar es otra historia… ;-)

Tercera opción (aunque no reconoceré haberlo dicho): __arglist

Aunque params por si solo ya es suficientemente interesante, vamos a hablar de una de esos pequeños aspectos de C# que son en general, desconocidos… Me refiero a __arglist (sí, con DOS subrayados delante, lo cual ya indica algo). El uso de esa palabra clave (pues es una palabra clave) significa “y a partir de aquí más argumentos”. En efecto, podemos declarar nuestro método DoBar:

class Foo

{

    public void DoBar(int baz, __arglist) { }

}

class Program

{

    static void Main(string[] args)

    {

        var foo = new Foo();

        foo.DoBar(10, __arglist("veinte", 20, 30, "bye"));

    }

Bueno, antes de continuar un par de cosas:

  1. Clarísimamente (como diría Cruyff) __arglist está pensada para que no la usemos. Es decir se trata de una palabra clave no documentada. La verdad es que funciona desde, al menos VS2005 y supongo que funcionará en todas las versiones de C#. Pero insisto: no está documentada, no es “oficial” (de ahí el “no reconoceré haberlo dicho” del título).
  2. Fijaos que debo invocar el método pasándole los parámetros opcionales usando también la palabra clave __arglist.

Ahora nos queda la cuestión final: como accede DoBar a esos parámetros opcionales. Eso no es como params que tenemos un array. No. Aquí si que tenemos un número arbitrario de parámetros de verdad. Pues nada, vamos a echar mano de la estructura ArgIterator:

public void DoBar(int baz, __arglist)

{

    var args = new ArgIterator(__arglist);

    while (args.GetRemainingCount() > 0)

    {

        Console.WriteLine("Param {0} valor {1}",

            Type.GetTypeFromHandle(args.GetNextArgType()).Name,

            TypedReference.ToObject(args.GetNextArg()).ToString());

    }

}

El uso de ArgIterator nos permite iterar sobre la lista de argumentos. Por cada argumento básicamente obtenemos:

  • Su valor, pero a través de un objeto TypedReference que se puede convertir a un object usando el método estático ToObject de la clase TypedReference.
  • Su tipo, pero a través de un objeto RuntimeTypeHandle que se puede convertir a un Type a través del método estático GetTypeFromHandle de la clase Type.

Probablemente estés pensando que no vale la pena usar __arglist, ArgIterator y todo este coñazo de TypedReference y RuntimeTypeHandle y tendrás razón. Por algo la palabra clave __arglist no está documentada. El hecho de que exista tiene que ver más con P/Invoke que no con una necesidad propiamente dicha del lenguaje.

Y bueno… eso es todo! :P Espero que os haya sido interesante… ;-)

Saludos!

Archivado en: ,
Comparte este post:

Comentarios

# re: [C# Básico] Métodos con parámetros variables

Wednesday, May 2, 2012 4:58 PM by Darío Cerredelo

No conocía la existencia de "__arglist", interesante.

¿Qué opinas de los parámetros opcionales como "cuarta opción"?

msdn.microsoft.com/.../dd264739.aspx

Un saludo!

# re: [C# Básico] Métodos con parámetros variables

Wednesday, May 2, 2012 5:24 PM by Pedro Hurtado

Eduard,

Tu sabes lo que pienso de esto, que has cogido una naranja,  se la has dado a quien nació el mismo día que yo. Y como no te has quedado satisfecho,  la has vuelto a meter a la licuadora:)

Muy bueno,  Master:)

# re: [C# Básico] Métodos con parámetros variables

Wednesday, May 2, 2012 9:05 PM by Eduard Tomàs i Avellana

@Darío

Son una opción sin duda... :) Otra forma de "ilusión" al mismo nivel que N sobrecargas. La razón principal por no mencionarlos es porque creo que tienen suficiente entidad como para mercerse un post por si mismos ;-)

@Pedro

LOL!

Gracias por comentar!

# re: [C# Básico] Métodos con parámetros variables

Thursday, May 3, 2012 4:25 PM by Ernesto

A proposito de esto de los parametros opcionales... no estaba seguro, pero por lo que veo a diferencia de los parametros variables, solo se estable una unica "firma" del metodo, siendo el compilador el responsable de pasar los parametros por defecto, corrijanme si me equivoco, pero en los casos indicados en este post se establece una "firma" (sobrecarga) por cada implementacion diferente.

No estaba seguro de eso hasta que hice la siguiente prueba:

Tengo una clase que implementa este metodo:

public static DocumentType GetXId(int idDocument, UserCurrentType user = null, Guid? guiIdentifyMaster = null)

Luego por reflection tengo que implementar algo como esto:

Type objectType2;

//.... carga de objectType

var theMethod = objectType2.GetMethod("GetXId", new[] { typeof(Int32) })

Y, claro theMethod sale null, pero cuando a la clase mencionada agrego esta sobrecarga, todo vuelve a funcionar:

       public static DocumentType GetXId(int idDocument)

       {

           return GetXId(idDocument, null, null);

       }

Ahora toca replantear como jugar con estas opciones...

# re: [C# Básico] Métodos con parámetros variables

Friday, May 18, 2012 10:47 AM by Eduard Tomàs i Avellana

@ecardenas

A los parámetros opcionales les dedicaremos un día un post enterito que tienen sus cosillas también... ;-)

Gracias por comentar!!! xD