Parámetros de métodos (Referencia de C#)
En C#, los argumentos se pueden pasar a parámetros por valor o por referencia. Recuerde que los tipos de C# pueden ser tipos de referencia (class
) o tipos de valor (struct
):
- Pasar por valor significa pasar una copia de la variable al método.
- Pasar por referencia significa pasar el acceso a la variable al método.
- Una variable de un tipo de referencia contiene una referencia a sus datos.
- Una variable de un tipo de valor contiene sus datos directamente.
Como un struct es un tipo de valor, cuando pasa un struct mediante valor a un método, el método recibe y funciona en una copia del argumento struct. El método no tiene acceso al struct original en el método de llamada y, por lo tanto, no puede cambiarlo de ninguna manera. El método solo puede cambiar la copia.
Una instancia de clase es un tipo de referencia, no un tipo de valor. Cuando un tipo de referencia se pasa mediante valor a un método, el método recibe una copia de la referencia a la instancia de clase. Es decir, el método al que se llama recibe una copia de la dirección de la instancia y el método de llamada conserva la dirección original de la instancia. La instancia de clase en el método de llamada tiene una dirección, el parámetro en el método que se ha llamado tiene una copia de la dirección, y ambas direcciones hacen referencia al mismo objeto. Como el parámetro solo contiene una copia de la dirección, el método al que se ha llamado no puede cambiar la dirección de la instancia de clase en el método de llamada. En cambio, el método al que se ha llamado puede usar la copia de la dirección para acceder a los miembros de clase a los que hacen referencia la dirección original y la copia de la dirección. Si el método al que se ha llamado cambia un miembro de clase, la instancia de clase original en el método de llamada también cambia.
En el resultado del ejemplo siguiente se ilustra la diferencia. El valor del campo willIChange
de la instancia de clase se cambia mediante la llamada al método ClassTaker
porque el método usa la dirección en el parámetro para buscar el campo especificado de la instancia de clase. El campo willIChange
del struct en el método de llamada no se cambia mediante la llamada al método StructTaker
porque el valor del argumento es una copia del propio struct, no una copia de su dirección. StructTaker
cambia la copia, y la copia se pierde cuando se completa la llamada a StructTaker
.
class TheClass
{
public string? willIChange;
}
struct TheStruct
{
public string willIChange;
}
class TestClassAndStruct
{
static void ClassTaker(TheClass c)
{
c.willIChange = "Changed";
}
static void StructTaker(TheStruct s)
{
s.willIChange = "Changed";
}
public static void Main()
{
TheClass testClass = new TheClass();
TheStruct testStruct = new TheStruct();
testClass.willIChange = "Not Changed";
testStruct.willIChange = "Not Changed";
ClassTaker(testClass);
StructTaker(testStruct);
Console.WriteLine("Class field = {0}", testClass.willIChange);
Console.WriteLine("Struct field = {0}", testStruct.willIChange);
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Class field = Changed
Struct field = Not Changed
*/
Cómo se pasa un argumento y si es un tipo de referencia o un tipo de valor controla qué modificaciones realizadas en el argumento son visibles desde el autor de la llamada.
Pasar un tipo de valor por valor
Cuando se pasa un tipo de valorpor valor:
- Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios no son visibles desde el autor de la llamada.
- Si el método modifica el estado del objeto al que hace referencia el parámetro, esos cambios no son visibles desde el autor de la llamada.
En el ejemplo siguiente se muestra cómo pasar los parámetros de tipo de valor en función del valor. La variable n
se pasa en función del valor al método SquareIt
. Los cambios que tienen lugar dentro del método no tienen ningún efecto en el valor original de la variable.
int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);
SquareIt(n); // Passing the variable by value.
System.Console.WriteLine("The value after calling the method: {0}", n);
// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
static void SquareIt(int x)
// The parameter x is passed by value.
// Changes to x will not affect the original value of x.
{
x *= x;
System.Console.WriteLine("The value inside the method: {0}", x);
}
/* Output:
The value before calling the method: 5
The value inside the method: 25
The value after calling the method: 5
*/
La variable n
es un tipo de valor. Contiene sus datos, el valor 5
. Cuando se invoca SquareIt
, el contenido de n
se copia en el parámetro x
, que se multiplica dentro del método. En Main
, sin embargo, el valor de n
es el mismo después de llamar al métodoSquareIt
que el que era antes. El cambio que tiene lugar dentro del método solo afecta a la variable local x
.
Pasar un tipo de valor por referencia
Cuando se pasa un tipo de valorpor referencia:
- Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios no son visibles desde el autor de la llamada.
- Si el método modifica el estado del objeto al que hace referencia el parámetro, esos cambios son visibles desde el autor de la llamada.
El ejemplo siguiente es el mismo que el anterior, salvo que el argumento se pasa como un parámetro ref
. El valor del argumento subyacente, n
, se cambia cuando se modifica x
en el método.
int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);
SquareIt(ref n); // Passing the variable by reference.
System.Console.WriteLine("The value after calling the method: {0}", n);
// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
static void SquareIt(ref int x)
// The parameter x is passed by reference.
// Changes to x will affect the original value of x.
{
x *= x;
System.Console.WriteLine("The value inside the method: {0}", x);
}
/* Output:
The value before calling the method: 5
The value inside the method: 25
The value after calling the method: 25
*/
En este ejemplo, no es el valor de n
el que se pasa, sino una referencia a n
. El parámetro x
no es un entero; se trata de una referencia a int
, en este caso, una referencia a n
. Por tanto, cuando x
se multiplica dentro del método, lo que realmente se multiplica es a lo que x
hace referencia, n
.
Pasar un tipo de referencia por valor
Cuando se pasa un tipo de referenciapor valor:
- Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios no son visibles desde el autor de la llamada.
- Si el método modifica el estado del objeto al que hace referencia el parámetro, esos cambios son visibles desde el autor de la llamada.
En el ejemplo siguiente, se muestra cómo pasar un parámetro de tipo de referencia, arr
, en función del valor a un método, Change
. Dado que el parámetro es una referencia a arr
, es posible cambiar los valores de los elementos de matriz. En cambio, el intento de volver a asignar el parámetro a otra ubicación de memoria solo funciona dentro del método y no afecta a la variable original, arr
.
int[] arr = { 1, 4, 5 };
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);
Change(arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);
static void Change(int[] pArray)
{
pArray[0] = 888; // This change affects the original element.
pArray = new int[5] { -3, -1, -2, -3, -4 }; // This change is local.
System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside the method, the first element is: -3
Inside Main, after calling the method, the first element is: 888
*/
En el ejemplo anterior, la matriz, arr
, que es un tipo de referencia, se pasa al método sin el parámetro ref
. En tal caso, se pasa al método una copia de la referencia, que apunta a arr
. El resultado muestra que es posible que el método cambie el contenido de un elemento de matriz, en este caso de 1
a 888
. En cambio, si se asigna una nueva porción de memoria al usar el operador new dentro del método Change
, la variable pArray
hace referencia a una nueva matriz. Por tanto, cualquier cambio que hubiese después no afectará a la matriz original, arr
, que se ha creado dentro de Main
. De hecho, se crean dos matrices en este ejemplo, una dentro de Main
y otra dentro del método Change
.
Pasar un tipo de referencia por referencia
Cuando se pasa un tipo de referenciapor referencia:
- Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios son visibles desde el autor de la llamada.
- Si el método modifica el estado del objeto al que hace referencia el parámetro, esos cambios son visibles desde el autor de la llamada.
El ejemplo siguiente es el mismo que el anterior, salvo que la palabra clave ref
se agrega a la llamada y al encabezado de método. Los cambios que tengan lugar en el método afectan a la variable original en el programa que realiza la llamada.
int[] arr = { 1, 4, 5 };
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);
Change(ref arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);
static void Change(ref int[] pArray)
{
// Both of the following changes will affect the original variables:
pArray[0] = 888;
pArray = new int[5] { -3, -1, -2, -3, -4 };
System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside the method, the first element is: -3
Inside Main, after calling the method, the first element is: -3
*/
Todos los cambios que tienen lugar dentro del método afectan a la matriz original en Main
. De hecho, la matriz original se reasigna mediante el operador new
. Por tanto, después de llamar al método Change
, cualquier referencia a arr
apunta a la matriz de cinco elementos, que se crea en el método Change
.
Ámbito de las referencias y los valores
Los métodos pueden almacenar los valores de los parámetros en campos. Cuando los parámetros se pasan por valor, siempre es seguro. Los valores se copian y se puede acceder a los tipos de referencia cuando se almacenan en un campo. Pasar parámetros por referencia de forma segura requiere que el compilador defina cuándo es seguro asignar una referencia a una nueva variable. Para cada expresión, el compilador define un ámbito que enlaza el acceso a una expresión o una variable. El compilador usa dos ámbitos: safe_to_escape y ref_safe_to_escape.
- El ámbito safe_to_escape define el ámbito en el que se puede acceder de forma segura a cualquier expresión.
- El ámbito ref_safe_to_escape define el ámbito en el que se puede acceder o modificar de forma segura una referencia a cualquier expresión.
Informalmente, puede considerar estos ámbitos como un mecanismo para asegurarse de que el código nunca accede o modifica una referencia que ya no es válida. Una referencia es válida siempre que haga referencia a un objeto o estructura válidos. El ámbito safe_to_escape define cuándo se puede asignar o reasignar una variable. El ámbito ref_safe_to_escape define cuándo una variable se puede asignar ref o reasignar ref. La asignación asigna una variable a un nuevo valor; la asignación por referencia asigna la variable para hacer referencia a otra ubicación de almacenamiento.
Modificadores
Los parámetros declarados para un método sin in, ref o out se pasan al método llamado por valor. Los modificadores ref
, in
y out
difieren en las reglas de asignación:
- El argumento de un parámetro
ref
se debe asignar definitivamente. El método llamado puede reasignar ese parámetro. - El argumento de un parámetro
in
se debe asignar definitivamente. El método llamado no puede reasignar ese parámetro. - No es necesario asignar definitivamente el argumento de un parámetro
out
. El método llamado debe asignar el parámetro.
Esta sección describe las palabras clave que puede usar para declarar parámetros de métodos:
- params especifica que este parámetro puede tomar un número variable de argumentos.
- in especifica que este parámetro se pasa por referencia, pero solo se lee mediante el método llamado.
- ref especifica que este parámetro se pasa por referencia y puede ser leído o escrito por el método llamado.
- out especifica que este parámetro se pasa por referencia y se escribe mediante el método llamado.
Vea también
- Referencia de C#
- Palabras clave de C#
- Listas de argumentos en la especificación del lenguaje C#. La especificación del lenguaje es la fuente definitiva de la sintaxis y el uso de C#.