Cuando programás en C#, tenés varias formas de pasar argumentos a un método: por valor (el comportamiento predeterminado) o por referencia. Esta última opción es muy útil cuando necesitás que el método pueda modificar directamente la variable original, sin copiarla.
En este post te cuento las distintas formas de hacerlo: ref
, out
, in
, y punteros (unsafe
). Además, te muestro cómo declararlos, cuándo conviene usarlos, y qué diferencias tienen entre sí.
ref
: Lectura y escritura
Con ref
, el método puede leer y modificar la variable pasada. Pero el valor debe estar inicializado antes de pasarla.
void SumarUno(ref int numero)
{
numero += 1;
}
int x = 5;
SumarUno(ref x); // x ahora vale 6
out
: Solo escritura
Con out
, el método debe asignar un valor. La variable no necesita estar inicializada previamente.
void ObtenerDoble(out int resultado)
{
resultado = 84;
}
int r;
ObtenerDoble(out r); // r ahora vale 84
Ideal cuando querés devolver más de un valor desde un método.
in
: Solo lectura
Con in
, pasás una variable por referencia, pero solo para lectura. Sirve especialmente con structs grandes, para evitar la copia de memoria.
void Mostrar(in int numero)
{
Console.WriteLine(numero);
}
int y = 10;
Mostrar(in y); // y no se puede modificar dentro del método
Punteros (unsafe
): Bajo nivel
Si necesitás controlar la memoria directamente, podés usar punteros en código unsafe
.
unsafe void Incrementar(int* ptr)
{
*ptr += 1;
}
unsafe
{
int z = 7;
Incrementar(&z); // z ahora vale 8
}
Declaración rápida
// ref
void Modificar(ref int x)
// out
void Inicializar(out int x)
// in
void SoloLeer(in int x)
// unsafe pointer
unsafe void Modificar(int* x)
Comparativa rápida
Keyword | ¿Inicialización previa? | ¿Se puede leer? | ¿Se puede escribir? | Uso típico |
---|---|---|---|---|
ref |
✅ | ✅ | ✅ | Modificar valor |
out |
❌ | ❌ | ✅ | Devolver datos |
in |
✅ | ✅ | ❌ | Structs grandes |
* |
✅ | ✅ | ✅ | Bajo nivel |
Ejemplos útiles
// struct con ref
struct Punto { public int X, Y; }
void Mover(ref Punto p) => p.X += 10;
// out con TryParse
bool TryParseNumero(string s, out int resultado)
{
return int.TryParse(s, out resultado);
}
// in con cálculo
void MostrarDistancia(in Punto p)
{
Console.WriteLine($"Distancia: {Math.Sqrt(p.X * p.X + p.Y * p.Y)}");
}
// unsafe con array
unsafe void Duplicar(int* arr, int n)
{
for (int i = 0; i < n; i++) arr[i] *= 2;
}
C# te da muchas formas de pasar datos, y conocer ref
, out
, in
y los punteros te permite optimizar tu código, mejorar la performance, y diseñar APIs más expresivas. Cada palabra clave tiene su contexto ideal, y entenderlas bien te va a dar más poder sobre lo que tu código realmente hace.