Scope defines where a variable is accessible in code — a variable declared inside a block is only visible within that block and any nested blocks inside it. Understanding scope prevents “variable not found” compiler errors and the subtler problem of unintentionally reusing a variable name in a nested scope that shadows an outer variable. Local functions — methods declared inside another method — are a powerful C# 7+ feature that allows helper logic to be placed exactly where it is used, while having access to the outer method’s variables through closure.
Variable Scope
// Method scope — variable lives for the duration of the method
public void DemoScope()
{
int x = 10; // x is accessible anywhere in this method
if (x > 5)
{
int y = 20; // y is only accessible inside this if block
Console.WriteLine(x + y); // ✓ x visible here (outer scope)
}
// Console.WriteLine(y); // ← compile error: y is out of scope here!
for (int i = 0; i < 3; i++)
{
int loopVar = i * 2; // loopVar is scoped to one iteration
Console.WriteLine(loopVar);
}
// Console.WriteLine(i); // ← compile error: i is out of scope
// Console.WriteLine(loopVar); // ← compile error: loopVar is out of scope
}
// C# does NOT allow a local variable to shadow another in an enclosing scope
public void NoShadowing()
{
int value = 10;
{
// int value = 20; // ← compile error! shadows outer 'value'
}
}
// Note: C# is stricter than C++ and Java here — no shadowing of locals
this.fieldName is sometimes needed), but local-to-local shadowing is forbidden.static modifier on a local function prevents it from accidentally capturing outer variables.int captured = i; var fn = () => Console.WriteLine(captured);. In C# foreach loops this is safe since C# 5 (each iteration has its own closure variable), but for loops still have this trap.Local Functions
// Local function — declared inside another method
public string ProcessData(string[] lines)
{
if (lines is null || lines.Length == 0)
return string.Empty;
var results = new List<string>();
foreach (string line in lines)
{
string processed = CleanLine(line); // call local function
if (!string.IsNullOrWhiteSpace(processed))
results.Add(processed);
}
return string.Join(Environment.NewLine, results);
// ── Local function — defined after the usage, still valid ─────────────
string CleanLine(string line)
{
return line.Trim()
.Replace("\t", " ")
.ToLowerInvariant();
}
}
// Local functions can access outer method variables (closure)
public decimal CalculateTotal(IEnumerable<OrderItem> items, decimal taxRate)
{
decimal subtotal = items.Sum(i => i.Price * i.Quantity);
decimal tax = ApplyTax(subtotal); // uses taxRate from outer scope
return subtotal + tax;
decimal ApplyTax(decimal amount) => amount * taxRate; // closes over taxRate
}
// Static local function — cannot capture outer variables (prevents accidental closure)
public void ProcessItems(List<int> numbers)
{
foreach (int n in numbers)
Console.WriteLine(Format(n));
static string Format(int value) => $"Item: {value:N0}";
// static prevents accidentally using 'numbers' or other outer vars
}
Local Function vs Private Method
| Aspect | Local Function | Private Method |
|---|---|---|
| Scope | Only visible inside the containing method | Visible to entire class |
| Closures | Can close over outer method variables | Cannot — no outer method variables |
| Reuse | Not reusable outside the method | Reusable by any method in the class |
| When to use | Helper used only by one method; recursive helper; named step | Logic reused by multiple methods |
Common Mistakes
Mistake 1 — Using a variable outside its scope
❌ Wrong — compile error: variable declared inside a block is not accessible outside:
if (condition) { int result = Compute(); }
Console.WriteLine(result); // compile error: result does not exist here
✅ Correct — declare the variable in the outer scope:
int result = 0;
if (condition) result = Compute();
Console.WriteLine(result); // ✓
Mistake 2 — Capturing a loop variable in a non-static local function
❌ Wrong in a for loop — all closures capture the same variable:
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
actions.Add(() => Console.WriteLine(i)); // captures reference to i
actions.ForEach(a => a()); // prints 3 3 3 — i is 3 after the loop!
✅ Correct — copy to a local variable before capturing:
for (int i = 0; i < 3; i++)
{
int copy = i; // new variable per iteration
actions.Add(() => Console.WriteLine(copy)); // captures copy, not i
}
actions.ForEach(a => a()); // prints 0 1 2 ✓