Parámetros de métodos
De manera predeterminada, los argumentos de C# se pasan a funciones por valor. Esto significa que se pasa una copia de la variable al método. Para los tipos de valor (struct
), se pasa una copia del valor al método. Para los tipos de referencia (class
), se pasa una copia de la referencia al método. Los modificadores de parámetro permiten pasar argumentos por referencia. Los conceptos siguientes le ayudarán a comprender estas diferencias y a usar los modificadores de parámetro:
- 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.
Dado que una estructura es un tipo de valor, el método recibe y opera en una copia del argumento de estructura cuando se pasa una estructura por valor a un método. 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 una 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. Ambas variables hacen referencia al mismo objeto. El parámetro es una copia de la referencia. El método llamado no puede reasignar la instancia en el método autor de la llamada. Sin embargo, el método llamado puede usar la copia de la referencia para acceder a los miembros de la instancia. Si el método llamado cambia un miembro de la instancia, el método autor de la llamada también ve esos cambios, ya que hace referencia a la misma instancia.
En el resultado del ejemplo siguiente se ilustra la diferencia. El método ClassTaker
cambia el valor del campo willIChange
porque el método usa la dirección en el parámetro para buscar el campo especificado de la instancia de la clase. El campo willIChange
de la estructura en el método autor de la llamada no se cambia mediante la llamada al método StructTaker
porque el valor del argumento es una copia de la propia estructura, 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);
}
}
/* Output:
Class field = Changed
Struct field = Not Changed
*/
Combinaciones de tipo de parámetro y modo de argumento
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:
- Cuando se pasa un tipo de valor por 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.
- Cuando se pasa un tipo de referencia por 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.
- Cuando se pasa un tipo de valor por referencia:
- Si el método asigna el parámetro para hacer referencia a un objeto diferente mediante
ref =
, 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.
- Si el método asigna el parámetro para hacer referencia a un objeto diferente mediante
- Cuando se pasa un tipo de referencia por 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.
Pasar un tipo de referencia por referencia permite que el método llamado pueda reemplazar el objeto al que hace referencia el parámetro de referencia en el autor de la llamada. La ubicación de almacenamiento del objeto se pasa al método como el valor del parámetro de referencia. Si cambia el valor de la ubicación de almacenamiento del parámetro (para que apunte a un nuevo objeto), también debe cambiar la ubicación de almacenamiento a la que se refiere el autor de la llamada. En el ejemplo siguiente se pasa una instancia de un tipo de referencia como un parámetro ref
.
class Product
{
public Product(string name, int newID)
{
ItemName = name;
ItemID = newID;
}
public string ItemName { get; set; }
public int ItemID { get; set; }
}
private static void ChangeByReference(ref Product itemRef)
{
// Change the address that is stored in the itemRef parameter.
itemRef = new Product("Stapler", 12345);
}
private static void ModifyProductsByReference()
{
// Declare an instance of Product and display its initial values.
Product item = new Product("Fasteners", 54321);
System.Console.WriteLine("Original values in Main. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
// Pass the product instance to ChangeByReference.
ChangeByReference(ref item);
System.Console.WriteLine("Calling method. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
}
// This method displays the following output:
// Original values in Main. Name: Fasteners, ID: 54321
// Calling method. Name: Stapler, ID: 12345
Contexto seguro de referencias y valores
Los métodos pueden almacenar los valores de los parámetros en campos. Cuando los parámetros se pasan por valor, normalmente 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 contexto seguro que enlaza el acceso a una expresión o una variable. El compilador usa dos ámbitos: safe-context y ref-safe-context.
- El ámbito safe-context define el ámbito en el que se puede acceder de forma segura a cualquier expresión.
- El ámbito ref-safe-context 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-context define cuándo se puede asignar o reasignar una variable. El ámbito ref-safe-context define cuándo una variable se puede asignar por referencia o reasignar por referencia. 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.
Parámetros de referencia
Se aplica uno de los modificadores siguientes a una declaración de parámetro para pasar argumentos por referencia en lugar de por valor:
ref
: el argumento se debe inicializar antes de llamar al método. El método puede asignar un nuevo valor al parámetro, pero no es necesario hacerlo.out
: el método autor de la llamada no tiene que inicializar el argumento antes de llamar al método. El método debe asignar un valor al parámetro.ref readonly
: el argumento se debe inicializar antes de llamar al método. El método no puede asignar un nuevo valor al parámetro.in
: el argumento se debe inicializar antes de llamar al método. El método no puede asignar un nuevo valor al parámetro. El compilador puede crear una variable temporal para contener una copia del argumento en los parámetrosin
.
Los miembros de una clase no pueden tener signaturas que se diferencien solo por ref
, ref readonly
, in
o out
. Si la única diferencia entre dos miembros de un tipo es que uno de ellos tiene un parámetro ref
y el otro tiene un parámetro out
, ref readonly
o in
, se produce un error de compilador. En cambio, los métodos se pueden sobrecargar cuando un método tiene un parámetro ref
, ref readonly
, in
o out
, y el otro tiene un parámetro que se pasa por valor, como se muestra en el ejemplo siguiente. En otras situaciones que requieran signatura coincidente, como ocultar o reemplazar, in
, ref
, ref readonly
y out
forman parte de la signatura y no coinciden entre sí.
Cuando un parámetro tiene uno de los modificadores anteriores, el argumento correspondiente puede tener un modificador compatible:
- Un argumento de un parámetro
ref
debe incluir el modificadorref
. - Un argumento de un parámetro
out
debe incluir el modificadorout
. - Un argumento de un parámetro
in
puede incluir opcionalmente el modificadorin
. Si, en su lugar, se usa en el argumento el modificadorref
, el compilador emite una advertencia. - Un argumento de un parámetro
ref readonly
debe incluir los modificadoresin
oref
, pero no ambos. Si no se incluye ninguno de los modificadores, el compilador emite una advertencia.
Al usar estos modificadores, describen cómo se usa el argumento:
ref
significa que el método puede leer o escribir el valor del argumento.out
significa que el método establece el valor del argumento.ref readonly
significa que el método lee, pero no puede escribir, el valor del argumento. El argumento se debe pasar por referencia.in
significa que el método lee, pero no puede escribir, el valor del argumento. El argumento se pasará por referencia o mediante una variable temporal.
No puede usar los modificadores de parámetros anteriores en los siguientes tipos de métodos:
- Métodos asincrónicos, que se definen mediante el uso del modificador async.
- Métodos de iterador, que incluyen una instrucción yield return o
yield break
.
Los métodos de extensión también tienen restricciones en el uso de estas palabras clave en los argumentos:
- No se puede usar la palabra clave
out
en el primer argumento de un método de extensión. - No se puede usar la palabra clave
ref
en el primer argumento de un método de extensión cuando el argumento no es de tipostruct
ni un tipo genérico no restringido para ser de tipo struct. - No se pueden usar las palabras claves
ref readonly
ein
a menos que el primer argumento sea de tipostruct
. - No se pueden usar las palabras clave
ref readonly
ein
en ningún tipo genérico, incluso cuando está restringido para ser de tipo struct.
Las propiedades no son variables. Son métodos. Las propiedades no pueden ser argumentos para ref
parámetros.
ref
(modificador de parámetro)
Para usar un parámetro ref
, la definición de método y el método de llamada deben utilizar explícitamente la palabra clave ref
, como se muestra en el ejemplo siguiente. (Salvo que el método de llamada puede omitir ref
al realizar una llamada COM).
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45
Un argumento que se pasa a un parámetro ref
se debe inicializar antes de pasarlo.
out
(modificador de parámetro)
Para usar un parámetro out
, tanto la definición de método como el método de llamada deben utilizar explícitamente la palabra clave out
. Por ejemplo:
int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod); // value is now 44
void OutArgExample(out int number)
{
number = 44;
}
Las variables que se han pasado como argumentos out
no tienen que inicializarse antes de pasarse en una llamada al método. En cambio, se necesita el método que se ha llamado para asignar un valor antes de que el método se devuelva.
Los métodos de deconstrucción declaran sus parámetros con el modificador out
para devolver varios valores. Otros métodos pueden devolver tuplas de valores para varios valores devueltos.
Puede declarar una variable en una instrucción independiente antes de pasarla como un argumento out
. También puede declarar la variable out
en la lista de argumentos de la llamada de método, en lugar de en una declaración de variable independiente. Las declaraciones de variables out
generan un código legible más compacto y, además, evitan que asigne un valor a la variable antes de la llamada al método de manera involuntaria. El ejemplo siguiente define la variable number
en la llamada al método Int32.TryParse.
string numberAsString = "1640";
if (Int32.TryParse(numberAsString, out int number))
Console.WriteLine($"Converted '{numberAsString}' to {number}");
else
Console.WriteLine($"Unable to convert '{numberAsString}'");
// The example displays the following output:
// Converted '1640' to 1640
También puede declarar una variable local con tipo implícito.
Modificador ref readonly
El modificador ref readonly
debe estar presente en la declaración del método. Un modificador en el sitio de llamada es opcional. Se puede usar el modificador in
o ref
. El modificador ref readonly
no es válido en el sitio de llamada. El modificador que se usa en el sitio de llamada puede ayudar a describir las características del argumento. Solo puede usar ref
si el argumento es una variable y se puede escribir. Solo puede usar in
cuando el argumento es una variable. Puede ser de escritura o de solo lectura. No se puede agregar ningún modificador si el argumento no es una variable, sino una expresión. En los ejemplos siguientes, se muestran estas condiciones. El método siguiente usa el modificador ref readonly
para indicar que una estructura grande se debe pasar por referencia por motivos de rendimiento:
public static void ForceByRef(ref readonly OptionStruct thing)
{
// elided
}
Puede llamar al método mediante el modificador ref
o in
. Si omite el modificador, el compilador emite una advertencia. Cuando el argumento es una expresión, no una variable, no se pueden agregar los modificadores in
ni ref
, por lo que debe suprimir la advertencia:
ForceByRef(in options);
ForceByRef(ref options);
ForceByRef(options); // Warning! variable should be passed with `ref` or `in`
ForceByRef(new OptionStruct()); // Warning, but an expression, so no variable to reference
Si la variable es una variable readonly
, debe usar el modificador in
. El compilador emite un error si usa el modificador ref
en su lugar.
El modificador ref readonly
indica que el método espera que el argumento sea una variable en lugar de una expresión que no sea una variable. Algunos ejemplos de expresiones que no son variables son las constantes, los valores devueltos de un método y las propiedades. Si el argumento no es una variable, el compilador emite una advertencia.
in
(modificador de parámetro)
El modificador in
es necesario en la declaración del método, pero no es necesario en el sitio de llamada.
int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument); // value is still 44
void InArgExample(in int number)
{
// Uncomment the following line to see error CS8331
//number = 19;
}
El modificador in
permite al compilador crear una variable temporal para el argumento y pasar una referencia de solo lectura a ese argumento. El compilador siempre crea una variable temporal cuando se debe convertir el argumento, cuando hay una conversión implícita del tipo de argumento o cuando el argumento es un valor que no es una variable. Por ejemplo, cuando el argumento es un valor literal o el valor devuelto desde el descriptor de acceso de una propiedad. Cuando la API requiera que el argumento se pase por referencia, elija el modificador ref readonly
en lugar del modificador in
.
Los métodos definidos mediante parámetros in
pueden obtener una optimización del rendimiento. Algunos argumentos de tipo struct
pueden tener un gran tamaño y, cuando se llama a métodos en bucles de pequeñas dimensiones o rutas de acceso de código crítico, el costo de copiar esas estructuras resulta importante. Los métodos declaran parámetros in
para especificar qué argumentos se pueden pasar por referencia sin ningún riesgo porque el método llamado no modifica el estado de ese argumento. Al pasar esos argumentos por referencia se evita la copia (potencialmente) costosa. Se agrega explícitamente el modificador in
en el sitio de llamada para garantizar que el argumento se pasa por referencia, y no por valor. Usar explícitamente in
tiene los dos efectos siguientes:
- Al especificar
in
en el sitio de llamada, se fuerza al compilador a seleccionar un método definido con un parámetroin
coincidente. En caso contrario, cuando dos métodos se diferencian solo en presencia dein
, la sobrecarga por valor es una coincidencia mejor. - Al especificar
in
, declara su intención de pasar un argumento por referencia. Los argumentos usados conin
deben representar una ubicación a la que se pueda hacer referencia directamente. Se aplican las mismas reglas generales para los argumentosout
yref
: no se pueden usar constantes, propiedades normales u otras expresiones que produzcan valores. En caso contrario, si se omitein
en el sitio de llamada, se informa al compilador de que le permitirá crear una variable temporal para pasar por referencia de solo lectura al método. El compilador crea una variable temporal para superar varias restricciones con argumentosin
:- Una variable temporal permite constantes en tiempo de compilación como parámetros
in
. - Una variable temporal permite propiedades u otras expresiones para parámetros
in
. - Una variable temporal permite argumentos en los que hay una conversión implícita desde el tipo de argumento hacia el tipo de parámetro.
- Una variable temporal permite constantes en tiempo de compilación como parámetros
En todas las instancias anteriores, el compilador crea una variable temporal que almacena el valor de la constante, la propiedad u otra expresión.
Estas reglas se muestran en este código:
static void Method(in int argument)
{
// implementation removed
}
Method(5); // OK, temporary variable created.
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // OK, temporary int created with the value 0
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // passed by readonly reference
Method(in i); // passed by readonly reference, explicitly using `in`
Supongamos ahora que hay disponible otro método que usa argumentos por valor. Los resultados cambian como se muestra en este código:
static void Method(int argument)
{
// implementation removed
}
static void Method(in int argument)
{
// implementation removed
}
Method(5); // Calls overload passed by value
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // Calls overload passed by value.
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // Calls overload passed by value
Method(in i); // passed by readonly reference, explicitly using `in`
La única llamada de método donde se pasa el argumento por referencia es la última.
Nota:
El código anterior usa int
como el tipo de argumento para simplificar el trabajo. Como int
no es más grande que una referencia en la mayoría de máquinas modernas, no supone ninguna ventaja pasar un único int
como una referencia de solo lectura.
Modificador params
No se permiten otros parámetros después de la palabra clave params
en una declaración de método, y solo se permite una palabra clave params
en una declaración de método.
El tipo declarado del parámetro params
debe ser un tipo de colección. Los tipos de colección reconocidos son los siguientes:
- Un tipo de matriz unidimensional
T[]
, en cuyo caso el tipo de elemento esT
. - Tipo de intervalo:
System.Span<T>
System.ReadOnlySpan<T>
Aquí, el tipo de elemento esT
.
- Tipo con un método de creación accesible con un tipo de elemento correspondiente. El método de creación se identifica mediante el mismo atributo que se usa para las expresiones de colección.
- Un tipo de clase o estructura que implementa System.Collections.Generic.IEnumerable<T> donde:
- El tipo tiene un constructor que se puede invocar sin argumentos y el constructor es al menos tan accesible como el miembro declarante.
- El tipo tiene un método de instancia
Add
(no una extensión) donde:- El método se puede invocar con un único argumento de valor.
- Si el método es genérico, los argumentos de tipo se pueden deducir del argumento.
- El método es al menos tan accesible como el miembro declarante. Aquí, el tipo de elemento es el tipo de iteración del tipo.
- Tipo de interfaz:
Antes de C# 13, el parámetro debe ser una matriz unidimensional.
Cuando se llama a un método con un parámetro params
, se puede pasar:
- Una lista separada por comas de argumentos del tipo de los elementos de la matriz.
- Colección de argumentos del tipo especificado.
- Sin argumentos. Si no envía ningún argumento, la longitud de la lista
params
es cero.
En el ejemplo siguiente se muestran varias maneras de enviar argumentos a un parámetro params
.
public static void ParamsModifierExample(params int[] list)
{
for (int i = 0; i < list.Length; i++)
{
System.Console.Write(list[i] + " ");
}
System.Console.WriteLine();
}
public static void ParamsModifierObjectExample(params object[] list)
{
for (int i = 0; i < list.Length; i++)
{
System.Console.Write(list[i] + " ");
}
System.Console.WriteLine();
}
public static void TryParamsCalls()
{
// You can send a comma-separated list of arguments of the
// specified type.
ParamsModifierExample(1, 2, 3, 4);
ParamsModifierObjectExample(1, 'a', "test");
// A params parameter accepts zero or more arguments.
// The following calling statement displays only a blank line.
ParamsModifierObjectExample();
// An array argument can be passed, as long as the array
// type matches the parameter type of the method being called.
int[] myIntArray = { 5, 6, 7, 8, 9 };
ParamsModifierExample(myIntArray);
object[] myObjArray = { 2, 'b', "test", "again" };
ParamsModifierObjectExample(myObjArray);
// The following call causes a compiler error because the object
// array cannot be converted into an integer array.
//ParamsModifierExample(myObjArray);
// The following call does not cause an error, but the entire
// integer array becomes the first element of the params array.
ParamsModifierObjectExample(myIntArray);
}
/*
Output:
1 2 3 4
1 a test
5 6 7 8 9
2 b test again
System.Int32[]
*/
- 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#.