Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explicit Local Scopes - Any True Benefit?

Tags:

scope

c#

I was cleaning up some code and removed an if statement that was no longer necessary. However, I realized I forgot to remove the brackets. This of course is valid and just created a new local scope. Now this got me thinking. In all my years of C# development, I have never come across a reason to use them. In fact, I kind of forgot I could do it.

Is there any actual benefit to defining a local scope? I understand I can define variables in one scope and then define the same ones again in an unrelated scope (for, foreach, etc.) like below:

void SomeMethod()
{    
    {
        int i = 20;
    }

    int i = 50; //Invalid due to i already being used above.
}

void SomeMethod2()
{    
    {
        int i = 20;
    }
    {
        int i = 50; //Valid due to scopes being unrelated.
    }
    {
        string i = "ABCDEF";
    }
}

What's the true point of defining a local scope? Can there actually be any sort of performance gain (or potentially a loss)? I know you could do this in C++ and was part of helping you manage memory, but because this is .NET, would there really be a benefit? Is this just a bi-product of the language that let's us define random scopes even though there is no true benefit?

like image 306
TyCobb Avatar asked Jun 01 '13 09:06

TyCobb


People also ask

What does local scope mean?

Local scope is a characteristic of variables that makes them local (i.e., the variable name is only bound to its value within a scope which is not the global scope).

What is the full form of LEGB?

The letters in the acronym LEGB stand for Local, Enclosing, Global, and Built-in scopes.

What is scope in Python explain with an example?

A variable is only available from inside the region it is created. This is called scope.


2 Answers

In C#, it is purely syntax to turn a group of statements into a single statement. Required for any keyword that expects a single statement to follow, like if, for, using, etc. A few corner cases:

  • the case keyword inside a switch is special since it doesn't require it to be a single statement. The break or goto keyword ends it. Which explains why you can use braces to jam in a variable declaration.
  • the try and catch keywords are special, they require braces even if only a single statement follows. Pretty unusual but probably inspired by forcing the programmer to think about the scope of declarations inside the blocks, a catch block cannot refer to variables inside the try block because of the way exception handling works.

Limiting the scope of local variables with it is a lost cause. It is a big deal in C++ because the ending brace is the place where the compiler will inject destructor calls for variables inside the scope block. This is ab/used all the time for the RAII pattern, nothing terribly pretty about having punctuation in a program have such drastic side-effects.

The C# team didn't have a lot of choice about it, the life-time of local variables is strictly controlled by the jitter. Which is oblivious to any grouping constructs inside a method, it only knows about IL. Which doesn't have any grouping constructs beyond try/except/finally. The scope of any local variable, no matter where it was written, is the body of the method. Something you can see when you run ildasm.exe on compiled C# code, you'll see the local variables hoisted to the top of the method body. Which partly also explains why the C# compiler won't let you declare another local variable in another scope block with the same name.

The jitter has interesting rules about local variable lifetime, they are entirely dominated by how the garbage collector works. When it jits a method, it doesn't just generate the machine code for the method but also creates a table that describes the actual scope of every local variable, the code address where it is initialized and the code address where it is no longer used. The garbage collector uses that table to decide if a reference to an object is valid, based on the active execution address.

Which makes it very efficient at collecting objects. A little too efficient sometimes and troublesome when you interop with native code, you may need the magic GC.KeepAlive() method to extend the lifetime. A very remarkable method, it doesn't generate any code at all. Its only use is to get the jitter to change the table and insert a larger address for the variable life-time.

like image 172
Hans Passant Avatar answered Sep 18 '22 05:09

Hans Passant


Just like functions, these "blocks" are pretty much just to isolate regions of (mostly) unrelated code and their local variables within a function.

You might use it if you needed some temporary variable just to pass between two function calls, for example

int Foo(int a) {

    // ...

    {
        int temp;
        SomeFuncWithOutParam(a, out temp);

        NowUseThatTempJustOnce(temp);            
    }

    MistakenlyTryToUse(temp);    // Doesn't compile!

    // ...

}

One might aruge, however, that if you need this kind of lexical scoping, the inner blocks should be functions on their own anyway.

As for performance, etc. I highly doubt it matters at all. The compiler looks at the function as a whole, and collects all local variables (even ones declared in-line), when determining the stack frame size. So basically all local variables are lumped together anyway. It's a purely lexical thing to give you a little more constraint on your variables' usage.

like image 34
Jonathon Reinhart Avatar answered Sep 21 '22 05:09

Jonathon Reinhart