Concepto de método

    Un método es un conjunto de instrucciones a las que se les da un determinado nombre de tal manera que sea posible ejecutarlas en cualquier momento sin tenerlas que rescribir sino usando sólo su nombre. A estas instrucciones se les denomina cuerpo del método, y a su ejecución a través de su nombre se le denomina llamada al método.

    La ejecución de las instrucciones de un método puede producir como resultado un objeto de cualquier tipo. A este objeto se le llama valor de retorno del método y es completamente opcional, pudiéndose escribir métodos que no devuelvan ninguno.

    La ejecución de las instrucciones de un método puede depender del valor de unas variables especiales denominadas parámetros del método, de manera que en función del valor que se dé a estas variables en cada llamada la ejecución del método  se pueda realizar de una u otra forma y podrá producir uno u otro valor de retorno.

    Al conjunto formado por el nombre de un método y el número y tipo de sus parámetros se le conoce como signatura del método. La signatura de un método es lo que verdaderamente lo identifica, de modo que es posible definir en un mismo tipo varios métodos con idéntico nombre siempre y cuando tengan distintos parámetros. Cuando esto ocurre se dice que el método que tiene ese nombre está sobrecargado.

Definición de métodos

    Para definir un método hay que indicar tanto cuáles son las instrucciones que forman su cuerpo como cuál es el nombre que se le dará, cuál es el tipo de objeto que puede devolver y cuáles son los parámetros que puede tomar. Esto se indica definiéndolo así:


<tipoRetorno> <nombreMétodo>(<parámetros>)
{
 <cuerpo>
}

    En <tipoRetorno> se indica cuál es el tipo de dato del objeto que el método devuelve, y si no devuelve ninguno se ha de escribir void en su lugar.

    Como nombre del método se puede poner en <nombreMétodo> cualquier identificador válido. Como se verá más adelante en el Tema 15: Interfaces, también es posible incluir en <nombreMétodo> información de explicitación de implementación de interfaz, pero por ahora podemos considerar que siempre será un identificador.

    Aunque es posible escribir métodos que no tomen parámetros, si un método los toma se ha de indicar en <parámetros> cuál es el nombre y tipo de cada uno, separándolos con comas si son más de uno y siguiendo la sintaxis que más adelante se explica.

    El <cuerpo> del método también es opcional, pero si el método retorna algún tipo de objeto entonces ha de incluir al menos una instrucción return que indique cuál objeto.

    La sintaxis anteriormente vista no es la que se usa para definir métodos abstractos. Como ya se vio en el Tema 5: Clases, en esos casos lo que se hace es sustituir el cuerpo del método y las llaves que lo encierran por un simple punto y coma (;) Más adelante en este tema veremos que eso es también lo que se hace para definir métodos externos.

    A continuación se muestra un ejemplo de cómo definir un método de nombre Saluda cuyo cuerpo consista en escribir en la consola el mensaje “Hola Mundo” y que devuelva  un objeto int de valor 1:


int Saluda()
{
 Console.WriteLine("Hola Mundo");
 return 1;
}

Llamada a métodos

    La forma en que se puede llamar a un método depende del tipo de método del que se trate. Si es un método de objeto (método no estático) se ha de usar la notación:


<objeto>.<nombreMétodo>(<valoresParámetros>)

    El <objeto> indicado puede ser directamente una variable del tipo de datos al que pertenezca el método o puede ser una expresión que produzca como resultado una variable de ese tipo (recordemos que, debido a la herencia, el tipo del <objeto> puede ser un subtipo del tipo donde realmente se haya definido el método); pero si desde código de algún método de un objeto se desea llamar a otro método de ese mismo objeto, entonces  se ha de dar el valor this a <objeto>.

    En caso de que sea un método de tipo (método estático), entones se ha de usar:


<tipo>.<nombreMétodo>(<valoresParámetros>)

    Ahora en <tipo> ha de indicarse el tipo donde se haya definido el método o algún subtipo suyo. Sin embargo, si el método pertenece al mismo tipo que el código que lo llama entonces se puede usar la notación abreviada:


<nombreMétodo>(<valoresParámetros>)

    El formato en que se pasen los valores a cada parámetro en <valoresParámetros> a aquellos métodos que tomen parámetros depende del tipo de parámetro que sea. Esto se explica en el siguiente apartado.

Tipos de parámetros. Sintaxis de definición

    La forma en que se define cada parámetro de un método depende del tipo de parámetro del que se trate. En C# se admiten cuatro tipos de parámetros: parámetros de entrada,  parámetros de salida, parámetros por referencia y parámetros de número indefinido.

Parámetros de entrada

    Un parámetro de entrada recibe una copia del valor que almacenaría una variable del tipo del objeto que se le pase. Por tanto, si el objeto es de un tipo valor se le pasará una copia del objeto y cualquier modificación que se haga al parámetro dentro del cuerpo del método no afectará al objeto original sino a su copia; mientras que si el objeto es de un tipo referencia entonces se le pasará una copia de la referencia al mismo y cualquier  modificación que se haga al parámetro dentro del método también afectará al objeto original ya que en realidad el parámetro referencia a ese mismo objeto original.

    Para definir un parámetro de entrada basta indicar cuál el nombre que se le desea dar y el cuál es tipo de dato que podrá almacenar. Para ello se sigue la siguiente sintaxis:


<tipoParámetro> <nombreParámetro>

    Por ejemplo, el siguiente código define un método llamado Suma que toma dos parámetros de entrada de tipo int llamados par1 y par2 y devuelve un int con su suma:


int Suma(int par1, int par2)
{
 return par1+par2;
}

    Como se ve, se usa la instrucción return para indicar cuál es el valor que ha de devolver el método. Este valor es el resultado de ejecutar la expresión par1+par2; es decir, es la suma de los valores pasados a sus parámetros par1 y par2 al llamarlo.

     En las llamadas a métodos se expresan los valores que se deseen dar a este tipo de parámetros indicando simplemente el valor deseado. Por ejemplo, para llamar al método anterior con los valores 2 y 5 se haría <objeto>.Suma(2,5), lo que devolvería el valor 7.

    Todo esto se resume con el siguiente ejemplo:


using System;
class ParámetrosEntrada
{
 public int a = 1;
 public static void F(ParametrosEntrada p)
 {
  p.a++;
 }
 public static void G(int p)
 {
  p++;
 }
 public static void Main()
 {
  int obj1 = 0;
  ParámetrosEntrada obj2 = new ParámetrosEntrada();
  G(obj1);
  F(obj2);
  Console.WriteLine("{0}, {1}", obj1, obj2.a);
 }
}

    Este programa muestra la siguiente salida por pantalla:


 0, 2

    Como se ve, la llamada al método G() no modifica el valor que tenía obj1 antes de llamarlo ya que obj1 es de un tipo valor (int) Sin embargo, como obj2 es de un tipo referencia (ParámetrosLlamadas) los cambios que se le hacen dentro de F() al pasárselo como parámetro sí que le afectan.

Parámetros de salida

    Un parámetro de salida se diferencia de uno de entrada en que todo cambio que se le  realice en el código del método al que pertenece afectará al objeto que se le pase al llamar dicho método tanto si éste es de un tipo por valor como si es de un tipo referencia. Esto se debe a que lo que a estos parámetros se les pasa es siempre una referencia al valor que almacenaría  una variable del tipo del objeto que se les pase.

    Cualquier parámetro de salida de un método siempre ha de modificarse dentro del cuerpo del método y además dicha modificación ha de hacerse antes que cualquier lectura de su valor. Si esto no se hiciese así el compilador lo detectaría e informaría de ello con un error. Por esta razón es posible pasar parámetros de salida que sean variables no inicializadas, pues se garantiza que en el método se inicializarán antes de leerlas. Además, tras la llamada a un método se considera que las variables que se le pasaron como parámetros de salida ya estarán inicializadas, pues dentro del método seguro que se las inicializa.

    Nótese que este tipo de parámetros permiten diseñar métodos que devuelvan múltiples objetos: un objeto se devolvería como valor de retorno y los demás se devolverían escribiéndolos en los parámetros de salida.

    Los parámetros de salida se definen de forma parecida a los parámetros de entrada pero se les ha de añadir la palabra reservada out. O sea, se definen así:


out <tipoParámetro> <nombreParámetro>

    Al llamar a un método que tome parámetros de este tipo también se ha preceder el valor especificado para estos parámetros del modificador out. Una utilidad de esto es facilitar la legibilidad de las llamadas a métodos. Por ejemplo, dada una llamada de la forma:


a.f(x, out z)

    Es fácil determinar que lo que se hace es llamar al método f() del objeto a pasándole x como parámetro de entrada y z como parámetro de salida. Además, también se puede deducir que el valor de z cambiará tras la llamada.

    Sin embargo, la verdadera utilidad de forzar a explicitar en las llamadas el tipo de paso de cada parámetro es que permite evitar errores derivados de que un programador pase una variable a un método y no sepa que el método la puede modificar. Teniéndola que explicitar se asegura que el programador sea consciente de lo que hace.

Parámetros por referencia

    Un parámetro por referencia es similar a un parámetro de salida sólo que no es obligatorio modificarlo dentro del método al que pertenece, por lo que será obligatorio pasarle una variable inicializada ya que no se garantiza su inicialización en el método.

    Los parámetros por referencia se definen igual que los parámetros de salida pero sustituyendo el modificador out por el modificador ref. Del mismo modo, al pasar valores a parámetros por referencia también hay que precederlos del ref.

Parámetros de número indefinido

    C# permite diseñar métodos que puedan tomar cualquier número de parámetros. Para ello hay que indicar como último parámetro del método un parámetro de algún tipo de tabla unidimensional o dentada precedido de la palabra reservada params. Por ejemplo:


static void F(int x, params object[] extras)
{}

    Todos los parámetros de número indefinido que se pasan al método al llamarlo han de ser del mismo tipo que la tabla. Nótese que en el ejemplo ese tipo es la clase primigenia object, con lo que se consigue que gracias al polimorfismo el método pueda tomar cualquier número de parámetros de cualquier tipo. Ejemplos de llamadas válidas serían:


F(4);// Pueden pasarse 0 parámetros indefinidos
F(3,2);
F(1, 2, "Hola", 3.0, new Persona());
F(1, new object[] {2,"Hola", 3.0, new Persona});

    El primer ejemplo demuestra que el número de parámetros indefinidos que se pasen también puede ser 0. Por su parte, los dos últimos ejemplos son totalmente equivalentes, pues precisamente la utilidad de palabra reservada params es indicar que se desea que la creación de la tabla object[] se haga implícitamente.

    Es importante señalar que la prioridad de un método que incluya el params es inferior a la de cualquier otra sobrecarga del mismo. Es decir, si se hubiese definido una sobrecarga del método anterior como la siguiente:


static void F(int x, int y)
{}

    Cuando se hiciese una llamada como F(3,2) se llamaría a esta última versión del método, ya que aunque la del params es también aplicable, se considera que es menos prioritaria.

Sobrecarga de tipos de parámetros

    En realidad los modificadores ref y out de los parámetros de un método también forman parte de lo que se conoce como signatura del método, por lo que esta clase es válida:


class Sobrecarga
{
 public void f(int x)
 {}
 public void f(out int x)
 {}
}

    Nótese que esta clase es correcta porque cada uno de sus métodos tiene una signatura distinta: el parámetro es de entrada en el primero y de salida en el segundo.

    Sin embargo, hay una restricción: no puede ocurrir que la única diferencia entre la signatura de dos métodos sea que en uno un determinado parámetro lleve el modificador ref y en el otro lleve el modificador out. Por ejemplo, no es válido:


class SobrecargaInválida
{
 public void f(ref int x)
 {}
 public void f(out int x)
 {}
}

 

Métodos
José Antonio González Seco

José Antonio es experto en tecnologias Microsoft. Imparte cursos y conferencias en congresos sobre C# y .NET en Universidades de toda España (Sevilla, Barcelona, San Sebastián, Valencia, Oviedo, etc.) en representación de grandes empresas como Microsoft.
Fecha de alta:11/10/2006
Última actualizacion:11/10/2006
Visitas totales:86885
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com