TOC

This article is currently in the process of being translated into Spanish (~97% done).

Clases:

Métodos con parámetros

En el artículo anterior, hablamos de métodos y tuvimos una breve introducción al concepto de parámetros de métodos/funciones. En este artículo, profundizaremos en este tema en todas sus variaciones. Como ya se ha mencionado, los métodos pueden funcionar sin parámetros, pero normalmente tendrán uno o más parámetros, que ayudarán al método a cumplir su tarea.

Ya vimos un caso de uso muy simple para los parámetros en el artículo anterior: Nuestro método AddNumbers(), que toma dos números como parámetros y devuelve la suma de estos dos números:

public int AddNumbers(int number1, int number2)
{
	return number1 + number2;
}

Esto demuestra en gran medida lo ingenioso que es el concepto de los métodos, porque con los métodos se puede encapsular la funcionalidad en el método, pero se puede modificar el resultado cuando se llama a este método a través de los parámetros:

AddNumbers(2, 3);

Result: 5

AddNumbers(38, 4);

Result: 42

Este es el tipo básico de parámetros, pero hablemos más sobre los diversos modificadores y opciones que se pueden usar para cambiar el comportamiento de los parámetros.

Tenga en cuenta que en este artículo, vamos a profundizar en todos los tipos de parámetros y en cómo pueden ayudarle, pero si acaba de empezar con C# y sólo quiere ver algunos resultados, lo siguiente podría ser un poco demasiado complejo y técnico por ahora. Siéntase libre de saltarse el resto del artículo y volver más tarde cuando esté listo.

Parámetros opcionales

Por defecto, cuando se llama a un método con uno o varios parámetros, estás obligado a suministrar valores para todos estos parámetros. Sin embargo, en algunas situaciones, es posible que tengas que hacer que uno o varios parámetros sean opcionales. En algunos lenguajes de programación, se le permitiría hacerlo simplemente marcando el parámetro como opcional, pero en C#, un parámetro se hace opcional suministrando un valor por defecto para él en la declaración del método. En realidad, ésta es una buena solución, porque te ahorra tener que escribir código adicional para hacer frente a situaciones en las que el parámetro no es suministrado por quien llama al método.

A continuación, un ejemplo de un método con un parámetro opcional:

public int AddNumbers(int number1, int number2, int number3 = 0)
{
	return number1 + number2 + number3;
}

El último parámetro (number3) es ahora opcional, porque hemos proporcionado un valor por defecto para él (0). Al llamarlo, ahora puede proporcionar dos o tres valores, de esta forma:

public void Main(string[] args)
{
	AddNumbers(38, 4);
	AddNumbers(36, 4, 2);
}

Puede hacer que más de un parámetro sea opcional - de hecho, su método puede consistir sólo en parámetros opcionales si es necesario. Sólo recuerde que los parámetros opcionales deben ser los últimos en la declaración del método - no los primeros o entre parámetros no opcionales.

El modificador params

Como alternativa a una serie de parámetros opcionales, se puede utilizar el modificador de params para permitir un número arbitrario de parámetros. Podría ser parecido a esto:

public void GreetPersons(params string[] names) { }

La llamada entonces podría quedar así:

GreetPersons("John", "Jane", "Tarzan");

Otra ventaja de usar el enfoque con params, es que también se le permite pasar cero parámetros al método. Los métodos con params también pueden aceptar parámetros normales, siempre y cuando el parámetro con el modificador params sea el último. Por otra parte, sólo se puede utilizar un parámetro con la palabra clave params por método. Éste es un ejemplo completo en el que usamos el modificador params para imprimir un número variable de nombres con nuestro método GreetPersons():

public void Main(string[] args)
{
	GreetPersons("John", "Jane", "Tarzan");
}

public void GreetPersons(params string[] names)
{
	foreach(string name in names)
		Console.WriteLine("Hello " + name);
}

Tipos de parámetros: por valor o por referencia

C#, al igual que otros lenguajes de programación, diferencia entre dos tipos de parámetros: "por valor" y "por referencia". El valor por defecto en C# es "por valor", lo que básicamente significa que cuando se pasa una variable en una llamada a un método, en realidad se está enviando una copia del objeto, en lugar de una referencia a él. Esto también significa que puedes hacer cambios en el parámetro desde dentro del método, sin afectar al objeto original que pasaste como parámetro.

Podemos demostrar fácilmente este comportamiento con un ejemplo:

public void Main(string[] args)
{
	int number = 20;
	AddFive(number);
	Console.WriteLine(number);
}

public void AddFive(int number)
{
	number = number + 5;
}

Tenemos un método muy básico llamado AddFive() el cual adicionará 5 al número que le pasemos. Así en nuestro método Main(), creamos un variable llamada number con el valor de 20 y entonces llamamos al método AddFive(). Ahora en la siguiente línea, cuando escribamos la variable number, uno esperaría que el valor es ahora 25, pero en vez de eso, permanece como 20. ¿Porqué? Por que por defecto, el parámetro fue pasado como una copia de objeto original (por valor), así cuando nuestro método AddFive() trabajó en el parámetro, estaba trabajando en una copia y así nunca afecto la variable original.

Actualmente hay dos formas de cambiar este comportamiento , así a nuestro método AddFive() le es permitido modificar el valor original: Podemos usar el modificador ref or los modificadores in/out

El modificador ref

El modificador ref es la abreviación de "reference" [referencia] y básacamente sólo cambia el comportamiento del parámetro de un comportamiento por defecto de "por valor" a "por referencia", lo que significa que ahora estamos pasando una referencia a la variable original en vez de una copia de su valor. Aquí un ejemplo modificado:

public void Main(string[] args)
{
	int number = 20;
	AddFive(ref number);
	Console.WriteLine(number);
}

public void AddFive(ref int number)
{
	number = number + 5;
}

Notará que he agregado la palabra clave "ref" en dos lugares: En la declaración del método y cuando paso el parámetro en la llamada al método. Con este cambio, ahora obtenemos el comportamiento que esperabamos originalmente - el resultado es ahora 25 en vez de 20, por que el modificador ref permite al método trabajar en el valor pasado realmente del parámetro y no en una copia.

El modificador out

Al igual que el modificador ref, el modificador out asegura que el parámetro es pasado como referencia en vez de como valor, peroi hay una diferencia mayor: Cuando usa el modificador "ref", usted pasa un valor inicializado el cual puede decidir modificar dentro del método, o dejarlo como está. Por otro lado, cuando usa el modificador out, usted forza a inicializar el parámetro dentro del método. Esto significa que puede pasar un valor no inicializado cuando usa el modificador "out" - el compilador se quejará si trata de terminar un método con un parámetro out sin asignarle un valor a él.

En C#, un método puede retornar solamente un valor, pero si usa el modificador out, es capaz de rodear esto pasando varios parámetros con el modificador out - cuando el método ha sido llamado, los parámetros out habrán sido asignados. Aquí está un ejemplo, donde pasamos dos números y luego, usando modificadores out, retornamos ambos una suma y una resta usando estos números.

public void Main(string[] args)
{
	int addedValue, subtractedValue;
	DoMath(10, 5, out addedValue, out subtractedValue);
	Console.WriteLine(addedValue);
	Console.WriteLine(subtractedValue);
}

public void DoMath(int number1, int number2, out int addedValue, out int subtractedValue)
{
	addedValue = number1 + number2;
	subtractedValue = number1 - number2;
}
Output:

15
5

Como puede var en el inicio del ejemplo, declaro las dos variables (addedValue y subtractedValue) antes de pasarlas como parámetros out. Esto era un requerimiento en versiones previas del lenguaje C#, pero desde la version 6 de C#, puede declararlas directamente en la llamada al método, así:

DoMath(10, 5, out int addedValue, out int subtractedValue);
Console.WriteLine(addedValue);
Console.WriteLine(subtractedValue);

El modificador in

Así como el modificador out, el modificador in asegura que el parámetro sea pasado como una referencia en vez de como una copia del valor, pero a diferencia del modificador out, el modificador in le prevendrá de hacer cualquier cambio a la variable dentro del método.

Su primer pensamiento puede ser: Si no puedo cambiar el valor del parámetro, entonces quizá también puedo pasarlo como un parámetro regular, donde los cambios no afectarán a la variable original. Tiene razón, el resultado aparecerá exáctamente igual, pero aún hab´ra una razón muy válida para usar el modificador "in": Al pasarlo como una referencia en vez de un valor, está ahorrando recursos por que el ambiente no tiene que invertir tiempo creando una copia del objto cuando lo pasa al método como un parámetro regular.

En la mayoria de los casos, esto no hará mucha diferencia, por que la mayoría de los parámetros con cadenas simples o enteros, pero en situaciones donde llame repetidamente el mismo método muchas veces en un ciclo o donde use parámetros con valores largos, por ejemplo una cadena muy larga o estructuras, esto puede darle una buena mejora del desempeño.

Aquí está un ejemplo sonde usamos el modificador in:

public void Main(string[] args)
{
	string aVeryLargeString = "Lots of text...";
	InMethod(aVeryLargeString);
}

public void InMethod(in string largeString)
{
	Console.WriteLine(largeString);
}

En nuestro método, InMethod(), el parámetro largeString es ahora una referencia de solo-lectura a la variable original (aVeryLargeString), así podemos usarla, pero no modificarla. Si lo intentamos, el compilador se quejará:

public void InMethod(in string largeString)
{
	largeString = "We can't do this...";
}

Error: Cannot assign to variable 'in string' because it is a readonly variable

Parámetros nombrados

Como lo ha visto en todos los ejemplos de arriba, cada parámetro tiene un identificador único en la declaración del método. Esto le permite referenciar al parámetro dentro del método. Sin embargo, cuando llama al método, usted no usa estos nombres - usted solamente provee los valores en el mismo orden en el que fueron declarados. Esto no es un problema para métodos simples que toman 2 o 3 parámetros, pero algunos métodos son más complejos y requieren más parámetros. En esas situaciones, puede ser difícil darse cuenta de a que se refieren los varios valores en la llamada al método, como en este ejemplo:

PrintUserDetails(1, "John", 42, null);

No es muy claro que significan los varios parámetros en esta llamada al método, pero si mira en la declaración del método, puede ver esto:

public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines = null)
{
	// Print details...
}

Pero es molesto si tiene que buscar constantemente la declaración del método para entender lo que hacen los parámetros, así para métodos más complejos, puede suministrar los nombres de los parámetros directamente en la llamada del método. Esto le permite suministrar los nombres de los parámetros en cualquier orden, en vez de ser forzado a usar el orden de la declaración. Aquí está un ejemplo:

PrintUserDetails(name: "John Doe", userId: 1);

Como un bono extra, esto le permite suministrar un valor para cualquiera de sus parámetros opcionales, sin tener que proveer valores para todos los parámetros opcionales previos en la declaración. En otras palabras, si quiere proveer un valor para el parámetro addressLines en este ejemplo también tendría que proveer un valor para el parámetro age, por que viene primero en la declaración del método. Sin embargo, si usa parámetros nombrados, el orden ya deja de importar y puede suministrar solamente los valores para los parámetros requeridos así como uno o varios de los parámetros opcionales, por ejemplo:

PrintUserDetails(addressLines: new List<string>() { }, name: "Jane Doe", userId: 2);

Aquí está el ejemplo completo usando parámetros nombrados:

public void Main(string[] args)
{
	PrintUserDetails(1, "John", 42, null);
	PrintUserDetails(name: "John Doe", userId: 1);
	PrintUserDetails(addressLines: new List<string>() { }, name: "Jane Doe", userId: 2);
}

public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines = null)
{
	// Print details...
	Console.WriteLine(name + " is " + age + " years old...");
}

Resumen

As you can see from this article, parameters come in many forms and types. Fortunately, you'll come along way with plain, old regular parameters, but once you start to dig deeper into the C# language, you will benefit from knowing all the types and modifiers as explained in this article.