What's The Difference, Part Two: Scope vs Declaration Space vs Lifetime

What's The Difference, Part Two: Scope vs Declaration Space vs Lifetime

Rate This
  • Comments 13

"Scope" has got to be one of the most confusing words in all of programming language design. People seem to use it casually to mean whatever is convenient at the time; I most often see it confused with lifetime and declaration space. As in "the memory will be released when the variable goes out of scope".

In an informal setting, of course it is perfectly acceptable to use "scope" to mean whatever you want, so long as the meaning is clearly communicated to the audience. In a more formal setting, like a book or a language specification, it's probably better to be precise.

The difference between scope and declaration space in C# is subtle.

The scope of a named entity is the region of program text in which it is legal to refer to that entity by its unqualified name.

There are some subtleties here. The implication does not "go the other way" -- it is not the case that if you can legally use the unqualified name of an entity, that the name refers to that entity. Scopes are allowed to overlap. For example, if you have:

class C
{
  int x;
  void M()
  {
    int x;
  }
}

then the field is in scope throughout the entire body text of C, including the entirity of M. Local variable x is in scope throughout the body of M, so the scopes overlap. When you say "x", whether the field or the local is chosen depends on where you say it.

A declaration space, by contrast, is a region of program text in which no two entities are allowed to have the same name. For example, in the region of text which is body of C excluding the body of M, you're not allowed to have anything else named x. Once you've got a field called x, you cannot have another field, property, nested type, or event called x.

Thanks to overloading, methods are a bit of an oddity here. One way to characterize declaration spaces in the context of methods would be to say that "the set of all overloaded methods in a class that have the same name" constitutes an "entity". Another way to characterize methods would be to tweak the definition of declaration space to say that no two things are allowed to have the same name except for a set of methods that all differ in signature.

In short, scope answers the question "where can I use this name?" and declaration space answers the question "where is this name unique?"

Lifetime and scope are often confused because of the strong connection between the lifetime and scope of a local variable. The most succinct way to put it is that the contents of a local variable are guaranteed to be alive at least as long as the current "point of execution" is inside the scope of the local variable. "At least as long" of course implies "or longer"; capturing a local variable, for example, extends its lifetime.

  • > The most succinct way to put it is that the contents of a local variable are guaranteed to be alive at least as long as the current "point of execution" is inside the scope of the local variable.

    AFAIK not really, unless you use GC.KeepAlive(). Some versions of .NET 2.0 even managed to collect "this" in the middle of a method if you didn't use any fields later, which led to a really ugly problem with handles, as your Dispose could release this.someIntPtr right between it being ldfld'ed and passed to some unmanaged method. Of course now we have SafeHandle which closes the handle itself, and with some pretty dark magic of CriticalFinalizerObject.

  • Hi Eric,

    I'm really enjoying this "What's The Difference" series of posts, it's really cool that you're taking the time to dispel these common mis-conceptions.  It's also very cool to learn a new thing or two from reading your blog.  How many more posts will there be in this series?

    Hope you're enjoying your holiday.  I wouldn't mind seeing some photographic evidence of these Beaver Sharks that we've heard so much about ;-)

  • >the current "point of execution" is inside the scope

    Be careful not to mix up "lifetime" and "extent". ;)

  • This is why we refactored a large vb.net 1.x project to declare variables at the innermost scope instead at the head of the function.    This helped reduce bugs greatly and force the application to clean up resources as soon as possible instead of at the end of each method.  For some reason, the original offshore development team put in a large number of 500+ line functions via cut and paste code.

    VS at some future version should allow refactoring to push varialbes to innermost scope.

  • "no two things are allowed to have the same name except for a set of methods that all differ in signature"

    Alternatively, you could consider the method "name" to be a combination of the name and signature. For example, given:

    void M()

    void M(int)

    void M(string)

    void M(int, string)

    you could assume that the method names look something like:

    M

    M@Int32

    M@String

    M@Int32@String

    As I recall, the C++ compiler does something like this for overloading exported functions.

  • > force the application to clean up resources as soon as possible instead of at the end of each method

    Neither scope nor declaration space of your locals have any effect on when GC will clean up resources. If you look at the IL generated from your code, you'll see that all local variables in a method are actually declared as "method scope" in IL. It doesn't really matter in the end, because the runtime is pretty smart at figuring out when a given local variable won't ever be used again, and thus should no longer be considered a root for GC purposes.

  • Pavel,

    That's a consequence of the fact that scope is property of identifiers, and lifetime is not (it's a property of storage).  Storage can be identified by a different name in a different scope (with ref parameters), or even in the same scope (C++ references, or C# methods with multiple ref parameters with the same type).  At least one resource is reclaimed (although not by the GC) when execution leaves the defining (as opposed to aliasing) scope -- stack space.

    Then in a GC environment such as .NET, instances of reference types have storage and lifetime completely distinct from the scope of handles to them.  But these handles are not identifiers for the instance's storage in the same way a ref parameter is, they can be reassigned to point to different storage.

    Once new programmers learn the difference between storage and the identifier that names it, the distinction between scope and lifetime becomes straightforward.

  • @paul

    > all local variables in a method are actually declared as "method scope" in IL.

    >  It doesn't really matter in the end, because the runtime is pretty

    > smart at figuring out when a given local variable won't ever be used

    > again, and thus should no longer be considered a root for GC purposes.

    Forgetting to cleanup allocated resources / variables is a symptom of poorly written code.   This covers obvious problems like not cleaning up open files, open sockets, ... to much harder ones like failing to properly deallocate a memory block returned by a thinly wrapped win32 api call.  

    Developers that rely on the GC to cleanup introduce many expensive bugs into their code.

    Moving variables to innermost scope greatly helps larger scale refactorings like extract method.  We used this many times to extract the inner body of an overly complicated method and make that extracted method static.  This resulted in much easier code to test, debug and verify it functions correctly.  

    Essentially, it reduces to:

     "Reduce the context needed to understand a given line of code."

    - Reduce nesting

    - Reduce the need to know the state of an object before calling a method (i.e., use static methods when possible)

    - Reduce the lines of code you need to read to get from the start of a method to any given line in that function

          - check for and return from errors before handling the non-error case (reduce nesting, ensure error handling is done for resource allocation)

          - don't declare variables at top of function, declare them inline where they are needed

          - precompute things that are used over and over (i.e., don't have 20 lines of object.array(25).methodz(1,2,3).propertyX called over and over just to access the properties)

    - eliminate unused methods and properties (e.g., fold properties into the constructor if they are only used when constructing an object)

    .

  • "Alternatively, you could consider the method "name" to be a combination of the name and signature. For example, given:"

    It's rather complex because it, in your example includes only the name and return type of the signature for the purposes of treating them as one entity with multiple parts. Anything with a different return type in that situation is considered a clash within the declarative space.

    However when you do it with methods in the base class as well

    public class Bottom

    {

    public int Blah() {return 0;}

    }

    public class Bar : Bottom

    {

       public double Blah() {return 0.0;}

    }

    Then this is considered acceptable (albeit with a compiler warning without new) as you are allowed to shadow, thus cover up one declarative space with another.

    Of course the moment you make Bottom's Blah private this issue goes away so the *maximum* access level in the base class(es) alters the declarative space for sub classes but has no effect on the space *within* the class.

    but wait. it gets worse :)

    public interface IFoo

    {

    int Blah();

    }

    public class Bottom : IFoo

    {

        int IFoo.Blah() {return 0;}

    }

    public class Bar : Bottom

    {

       public double Blah() {return 0.0;}

    }

    suddenly the compiler warning goes away because we have pushed the declarative space into the interface and, despite Bar being usable as an IFoo and truly implementing it, it is only accessible from variables where you stop being able to treat it as a Bar, hence the spaces cannot clash. Good that the compiler is clever about this but you begin to see the complexity of it.

    I'd hate to have to implement a compiler, though it's probably a source of combined maddening and marvellous all at once :)

  • > The scope of a named entity is the region of program text

    > in which it is legal to refer to that entity by its *unqualified name.*

    > ...

    > [x] is in scope throughout the entire body text of C, including the

    > entirity of M.

    So just to clarify, within the method M() of your example, saying "this.x" does not count as qualifying the name?  What does unqualified mean, exactly?

    Hobbes

  • i think many people might arrive at this website because they are hitting the 'variable name is already defined' and such like errors.

    I would point out to those people wishing to re-use a variable name that it is perfectly ok as long as the scopes do not form a parent-child relation ship.

    In other words it is fine to have foreach and for loops use the same variable names as long as the same name does not also exist in the enclosing scope

    For example

    foreach (string s in list)

     Console.WriteLine(s)

    foreach (string s in anotherlist)

    Console.WriteLine(s)

    is fine as long as s in not also declared in the enclosing scope of the loops

  • @Hobbes: No, this.x would count as qualifying the name.  However, it is still *legal* to refer to the field x by its unqualified name (that is, just with x) from within the method M().  In other words, there's no *rule* that you can't refer to a field of a class with its unqualified name from within a method of that class.

    However (and as Eric points out) just because it's *legal* to refer to the field x in this way from within the method, does not guarantee that the name actually *will* refer to that entity.  In this case, there is a local variable x  whose scope overlaps with that of the field x, and within the body of M() it is this local variable that is *actually* referred to by x.  But this doesn't change the "legality" of referring to the field with an unqualified name, it just means something else trumps it.  Similarly, it's *legal* to call a method public foo(object o) with the call foo(5), but if you also have a method public foo(int i) in the same class, then the method that takes an object won't be the *actual* one called by foo(5).

    In contrast, you can't refer to the field x from outside C (or classes derived from C) by using the unqualified name, even if x is public.  This is true regardless of the presence or absence of other entities named x.  It is simply illegal to use the unqualified name of the field x in that part of the code.  That part of your code is outside the scope of x.

  • Or in short: the rules for determining scope don't depend on if other entities have the same name.  See here: msdn.microsoft.com/.../aa691132(v=VS.71).aspx

    When there are multiple entities with the same name and overlapping scope, you have name hiding, as described in the subsequent sections of that specification.

Page 1 of 1 (13 items)