Scope and Lifetime
Scope is the stretch of code where a name can be used; narrow it and most naming mistakes stop happening.
{ int temp = 5; } // temp exists only between these bracesScope is the region of code in which a name can be used. Outside it, the name does not exist, and using it is a compile error rather than a run-time surprise.
The three scopes you meet first
A block is a pair of braces, { and }. A variable declared inside a block can be used from its declaration to the closing brace, and nowhere else. A parameter of a method is visible throughout that method. A field, declared in a class rather than inside a method, is visible to every method of that class.
Lifetime is a separate question: how long the value survives. A local variable's value is finished with when its block ends. A field's value lives as long as the object that holds it.
Shadowing
C# does not let a local variable hide another local variable from an enclosing block of the same method. That is compile error CS0136, and it exists so that a name never quietly means two different things in one method.
A local or a parameter may still hide a field, and that is legal. Writing private fields as _total keeps the two apart; otherwise you need this.total = total; to reach the field.
The habit to build
Declare each variable in the smallest block that needs it, as late as you can. A variable that cannot be seen cannot be misused, and a method whose locals are all declared at the top is a method that is hard to split up later.
Example
int total = 0;
{
// A block on its own. Anything declared here is gone at the closing brace.
int bonus = 5;
total = total + bonus;
}
// Console.WriteLine(bonus); // bonus is out of scope here: it does not compile.
Counter counter = new Counter();
counter.Add(3);
counter.Add(4);
Console.WriteLine(total);
Console.WriteLine(counter.Describe());
class Counter
{
// A field: every method of the class can see it, and it lives as long
// as the object does.
private int _total;
public void Add(int amount)
{
// amount is a parameter: visible only inside this method.
_total = _total + amount;
}
public string Describe()
{
// This local is a different variable from the one in the lines above.
int total = _total;
return "counted " + total;
}
}
// Output:
// 5
// counted 7When to use it
- Work out why the compiler says a name does not exist on a particular line
- Decide whether a value belongs in a field or in a local variable
- Fix a constructor parameter that is quietly hiding the field of the same name
- Keep temporary working values from leaking through a long method
More examples
The compiler refuses to let you shadow a local
Reusing a name in a nested block is not allowed, even when the inner one has a different type. The fix is a different name, which is also the clearer code. Expect this error when you copy a few lines into an if or a loop body inside a method that already uses that name.
int total = 0;
{
// int total = 5; // error CS0136: the name total is used in an enclosing scope
int subtotal = 5;
total = total + subtotal;
}
// subtotal does not exist on this line.
Console.WriteLine(total);
// Output:
// 5A parameter hiding a field
Both classes compile. The second one needs this. on every line that touches the field, and a dropped this. assigns the parameter to itself instead, which does nothing. The _field convention removes the whole class of mistake, which is why the .NET runtime uses it.
class Order
{
private string _customer; // the underscore keeps the names apart
public Order(string customer)
{
_customer = customer; // no ambiguity to resolve
}
}
class OrderWithoutPrefix
{
private string customer; // same name as the parameter below
public OrderWithoutPrefix(string customer)
{
// The parameter hides the field here, so this is needed to reach it.
this.customer = customer;
}
}Keeping working values out of the rest of the method
The declaration sits outside the block because the value is needed afterwards; the working variables sit inside it because they are not. In real code the block is usually an if, a loop or a using statement rather than a bare pair of braces, but the scoping rule is identical.
string report;
{
// These two exist only while the report line is being built.
int apples = 3;
int pears = 4;
report = "fruit: " + (apples + pears);
}
// Only report is in scope from here on.
Console.WriteLine(report);
// Output:
// fruit: 7
Discussion