Scope and Lifetime

Scope is the stretch of code where a name can be used; narrow it and most naming mistakes stop happening.

Syntax{ int temp = 5; } // temp exists only between these braces

Scope 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

Example · csharp
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 7

When 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.

Example · csharp
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:
// 5

A 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.

Example · csharp
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.

Example · csharp
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

  • Be the first to comment on this lesson.