1. Introducción
Recordando el ámbito
Imagina que las variables son empleados de una gran oficina, y los métodos, bucles y bloques de código son habitaciones y despachos. A algunos empleados solo se les deja entrar en su propia habitación, y a otros en todo el edificio. Dónde puede estar cada uno — eso es su ámbito (scope).
El ámbito determina dónde una variable declarada es “visible” en el programa y dónde se puede usar.
Tipos principales de ámbitos
En C# se pueden distinguir estos ámbitos principales:
| Ámbito | Ejemplo | Dónde es "visible" la variable |
|---|---|---|
| Local | Dentro de un método o bloque | Solo dentro de ese bloque |
| Parámetro de método | En la signatura del método | Solo dentro del método |
| Variable de clase (campo) | En el cuerpo de la clase fuera de los métodos | En todos los métodos de esa clase |
| Variable dentro de un bucle/condición | Dentro de de un bucle/if |
Solo dentro de esos |
Ejemplo con explicaciones
public class Office
{
int buildingNumber = 50; // Campo de clase: visible en todos los métodos public void PrintInfo() {
int roomNumber = 101; // Variable local: visible solo dentro de PrintInfo if (roomNumber > 100) {
int deskNumber = 5; // Visible solo dentro de este bloque if Console.WriteLine(deskNumber);
} Console.WriteLine(deskNumber); //
¡Error! deskNumber ya no es visible aquí
}
}
2. Funciones locales y ámbito
¿Quién ve a quién?
Cuando declaras una función local dentro de un método (o incluso dentro de un bucle o condición), está en el mismo ámbito que las variables declaradas arriba. Una función local es como “parte de ese mismo despacho”.
Ejemplo
La función local ve las variables del ámbito que la rodea
void PrintWithPrefix(string mensaje)
{
string
prefijo = "[LOG]: "; void Print() {
Console.WriteLine(
prefijo +
mensaje); // ¡ve ambas variables!
} Print();
}
Aquí las variables prefijo y mensaje son visibles dentro de la función local Print, porque están declaradas en el mismo o en un ámbito más amplio.
¿Cuántos ámbitos hay aquí?
En el ejemplo de arriba:
- hay el ámbito del método PrintWithPrefix
- dentro de él — el ámbito de la función Print
3. Captura de variables (Capture)
Captura de variables — es cuando una función local usa variables que fueron declaradas fuera de esa función, pero en el mismo ámbito.
Las funciones locales y los métodos anónimos (expresiones lambda, ya llegaremos a eso) recuerdan (o “capturan”) todas las variables que tenían disponibles al declararse.
Se puede decir que las funciones hacen como una foto (capture) del mundo que las rodea — y pueden usar esas variables incluso cuando se llaman mucho después.
Esquemáticamente
Método Main
└─ variable x
└─ función local F() ← "captura" x
Ejemplo — captura más simple
void CounterExample()
{
int contador = 0;
void Increase()
{
contador++; // Esta función captura la variable contador
}
Increase();
Increase();
Console.WriteLine(contador); // Mostrará 2
}
Aquí, después de dos llamadas a la función local Increase, el valor de contador sube a 2.
4. Uso de la captura de variables
La captura de variables permite “pasar” datos entre el ámbito del método y las funciones locales de forma cómoda, sin tener que usar parámetros extra.
Si no existiera la captura, tendrías que pasar todas las variables como parámetros:
void CounterExampleWithoutCapture()
{
int contador = 0;
void Increase(ref int c)
{
c++;
}
Increase(ref contador);
Increase(ref contador);
Console.WriteLine(contador);
}
Eso es incómodo — ¿para qué estar escribiendo ref y estropear la signatura de la función, si puede “ver” fácilmente las variables de fuera?
5. Funciones locales y vida de las variables después de salir del método
¿Las variables viven más que el método?
Si pasas una función local (o un delegado con lambda) fuera del método actual, las variables capturadas automáticamente dejan de “morir” al salir del método. El CLR (máquina virtual .NET) se encarga — todo lo necesario se “mantiene agarrado” en memoria.
Ejemplo: funciones viven fuera del método
Func<int> GetCounter()
{
int cuenta = 0;
int Increment()
{
cuenta++;
return cuenta;
}
return Increment; // ¡Devolvemos la función hacia fuera!
}
var contador = GetCounter();
Console.WriteLine(contador()); // 1
Console.WriteLine(contador()); // 2
Aquí, incluso después de que termine el método GetCounter, la variable cuenta sigue viva, porque la función devuelta la capturó. Esto se llama closure (cierre) — ya hablaremos de esto en otra lección, pero a nivel de funciones locales el mecanismo es igual.
6. Errores típicos y escenarios curiosos
Reescribir la variable antes de llamar a la función local
A veces puede pasar que la variable que captura la función local se cambia antes de llamarla — y entonces el resultado puede ser diferente al esperado.
Ejemplo:
void Example()
{
int x = 42;
void PrintX() { Console.WriteLine(x); }
x = 100; // ¡La variable ha cambiado!
PrintX(); // ¡Mostrará 100, no 42!
}
Truco: La función local siempre ve el valor más reciente de la variable en el momento de la llamada.
Captura de variables en bucles for/foreach (otra vez)
El clásico dolor: si estás escribiendo lógica en una entrevista o en un proyecto grande, revisa siempre: ¿no estaré capturando una variable “viva” del bucle, y cómo se va a comportar?
GO TO FULL VERSION