Fabulous Adventures In Coding

Eric Lippert's Blog

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

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

Published Monday, August 03, 2009 7:08 AM by Eric Lippert

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Fj_ said:

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

August 3, 2009 12:49 PM
 

Matthew Jones said:

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 ;-)

August 4, 2009 4:30 AM
 

Paul said:

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

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

August 4, 2009 5:16 AM
 

Greg said:

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.

August 4, 2009 9:02 AM
 

richard_deeming said:

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

August 4, 2009 11:04 AM
 

Pavel Minaev [MSFT] said:

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

August 4, 2009 3:47 PM
 

Ben Voigt [C++ MVP] said:

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.

August 5, 2009 1:47 AM
 

Greg said:

@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)

.

August 5, 2009 5:14 PM
 

ShuggyCoUk said:

"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 :)

August 5, 2009 5:50 PM

Leave a Comment

(required) 
(optional)
(required) 

  
Enter Code Here: Required
Submit

About Eric Lippert

Eric Lippert is a senior developer on the Microsoft C# compiler team. Before that he worked on the framework of Visual Studio Tools For Office. Before that, he worked on the compilers, runtimes and tools for VBScript, JScript, Windows Script Host and other Microsoft Scripting technologies. He lives in Seattle and spends his free time editing books about programming languages, playing the piano, and trying to keep his tiny sailboat upright in Puget Sound.

This Blog

Syndication


© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker