Monday, October 4, 2010

Scope of Return Statement

Last week my colleague came up with one question about Scope and Reachable Code in .Net and we started understanding Reachable Code and found very interesting results.

We all know that in try-catch block, finally block must execute before returning from method, then what happened with variables declaring in Finally Block, when they are getting assign and what is use of them.

Guess, what will be the return value when calling MyFunction method in below code?

private int MyFunction()

{

            int i = 0;


try {

i = 1;

                throw new Exception("Int Error");

}

catch {

i = 2;

return i;

}

finally {

i = 3;

Debug.Print(i.ToString());

}

}

If you execute above code, just after setting value of i to 2 after executing return statement, finally block should called. And in finally block setting value of i to 3, even Debug.print should print 3 but when you get return value (return i), you will get 2 as an output.


Guess, what had happend.


Let me change existing code, let me pass a reference parameter and we will see what happen to reference variable.

private int MyFunction(ref int passInt)

{

int i = 0;

try{

i = 1;

passInt = 1;

throw new Exception("Int Error");

}

catch{

i = 2;

passInt = 2;

return i;

}

finally{

i = 3;

passInt = 3;

Debug.Print(i.ToString());

}

}


In above code, passed parameter value becomes 3 but return value becomes 2.

Now try to understand what had happened in previous code.


“Finally statement is always calling before returning from function”, that means return statement executed but value yet not returned. Then in that case ever after changing value of i in finally block, why return value persisting previous value.

Now go to few years back, and think that every function is called by a pointer and address of that function is set to callee. Now you will understand what had happened.

Below is a code generated by complier before creating IL code.

private int MyFunction()

{

int CS$1$0000;

int i = 0;

try{

i = 1;

throw new Exception("Int Error");

}

catch{

i = 2;

CS$1$0000 = i;

}

finally{

Debug.Print(i.ToString());

}

return CS$1$0000;

}

See that return statement has been reset and return statement placed at then end of function. Also one variable CS$1$0000 added to set return value.

You will see that in Catch block, value of i is set to CS$1$0000 variable and in turns CS$1$0000 is getting return from method.

IL version of above code will give better explanation with stack trace; you will also get clear when you will execute IL code with IL compiler.

IL Version:

.method private hidebysig instance int32 MyFunction() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] int32 CS$1$0000)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: nop 
    L_0004: ldc.i4.1 
    L_0005: stloc.0 
    L_0006: ldstr "Int Error"
    L_000b: newobj instance void [mscorlib]System.Exception::.ctor(string)
    L_0010: throw 
    L_0011: pop 
    L_0012: nop 
    L_0013: ldc.i4.2 
    L_0014: stloc.0 
    L_0015: ldloc.0 
    L_0016: stloc.1 
    L_0017: leave.s L_002b
    L_0019: nop 
    L_001a: ldc.i4.3 
    L_001b: stloc.0 
    L_001c: ldloca.s i
    L_001e: call instance string [mscorlib]System.Int32::ToString()
    L_0023: call void [System]System.Diagnostics.Debug::Print(string)
    L_0028: nop 
    L_0029: nop 
    L_002a: endfinally 
    L_002b: nop 
    L_002c: ldloc.1 
    L_002d: ret 
    .try L_0003 to L_0011 catch object handler L_0011 to L_0019
    .try L_0003 to L_0019 finally handler L_0019 to L_002b
}
 

Summary:

Compiler is setting return value before returning a method and changes into the method level variable is not affecting return value.


Note: I have used Red Gate’s Reflector to get IL code.


Please share me your understanding on this.

No comments:

Post a Comment