157 suggestions for writing high-quality code to improve C # programs-suggestion 61: Avoid writing Invalid code in finally,
Suggestion 61: Avoid writing Invalid code in finally
Before explaining the suggestions, you need to ask whether there is a situation that breaks the try-finally execution sequence. The answer is: does not exist (unless the application exits in the try block due to some rare special circumstances ). Always think that the finally code will be executed before the return method, even if the return is in the try block.
This may cause you to write Invalid code. Sometimes, such invalid code is a hidden Bug.
See the following code:
Private static int TestIntReturnBelowFinally () {int I; try {I = 1;} finally {I = 2; Console. writeLine ("\ t change int result to 2, finally executed");} return I ;}
The return value is 2.
However:
Private static int TestIntReturnInTry () {int I; try {return I = 1;} finally {I = 2; Console. writeLine ("\ t change int result to 2, finally executed ");}}
The return value is 1.
Let's look at the following code:
Static User TestUserReturnInTry () {User user User = new user () {Name = "Mike", BirthDay = new DateTime (2010, 1, 1)}; try {return User ;} finally {user. name = "Rose"; user. birthDay = new DateTime (2010, 2, 2); Console. writeLine ("\ t set user. change Name to Rose ");}}
User class:
Class User {public string Name {get; set;} public DateTime BirthDay {get; set ;}}View Code
In the User returned by TestUserReturnInTry, the value of Name has been changed to Rose.
Now let's explain why the above three functions have three results. Check the IL code of the finally part of TestIntReturnBelowFinally:
finally { IL_0004: ldc.i4.2 IL_0005: stloc.0 IL_0006: ldstr bytearray (09 00 06 5C 69 00 6E 00 74 00 D3 7E 9C 67 39 65 // ...\i.n.t..~.g9e 3A 4E 32 00 0C FF 66 00 69 00 6E 00 61 00 6C 00 // :N2...f.i.n.a.l. 6C 00 79 00 67 62 4C 88 8C 5B D5 6B ) // l.y.gbL..[.k IL_000b: call void [mscorlib]System.Console::WriteLine(string) IL_0010: endfinally } // end handler IL_0011: ldloc.0 IL_0012: ret}
"IL_0004: ldc. i4.2" first pushes 2 to the top of the stack.
"IL_0005: stloc.0" assigns the value of the top-level stack, that is, 2 to the local variable, that is, I (index 0)
"IL_0011: ldloc.0" pushes the value of the local variable I (index 0) to the stack again.
"IL_0012: ret" ends the function and pushes the returned values in the stack to the caller's stack. The function assigns 2 to the return value.
Check the IL code of the Debug version of TestIntReturnInTry:
. Method private hidebysig static int32 TestIntReturnInTry () cel managed {// code size 27 (0x1b ). maxstack 2. locals init ([0] int32 I, [1] int32 CS $1 $0000) IL_0000: nop. try {IL_0001: nop IL_0002: ldc. i4.1 IL_0003: dup IL_0004: stloc.0 IL_0005: stloc.1 IL_0006: leave. s IL_0018} // end. try finally {IL_0008: nop IL_0009: ldc. i4.2 IL_000a: stloc.0 IL_000b: ldstr bytearray (09 00 06 5C 69 00 6E 00 74 00 D3 7E 9C 67 39 65 //... \ I. n. t ..~. G9e 3A 4E 32 00 0C FF 66 00 69 00 6E 00 61 00 6C 00 //: N2... f. i. n. a. l. 6C 00 79 00 67 62 4C 88 8C 5B D5 6B) // l. y. gbL .. [. k IL_0010: call void [mscorlib] System. console: WriteLine (string) IL_0015: nop IL_0016: nop IL_0017: endfinally} // end handler IL_0018: nop IL_0019: ldloc.1 IL_001a: ret} // end of method Program: Unknown
TestIntReturnInTry creates two local variables I and CS $1 $0000 in IL, I stores 1, and I is assigned 2 in finally. The caller actually obtains the value of CS $1 $0000 created by IL. Use Reflector to view C # code:
Private static int TestIntReturnInTry () {int I; int CS $1 $0000; try {CS $1 $0000 = I = 1;} finally {I = 2; Console. writeLine ("\ t change int result to 2, finally executed");} return CS $1 $0000 ;}
In fact, I = 2 in finally is meaningless, so in the release version of this function, the corresponding Code cannot be found in IL:
. Method private hidebysig static int32 TestIntReturnInTry () cel managed {// code size 17 (0x11 ). maxstack 1. locals init ([0] int32 CS $1 $0000 ). try {IL_0000: ldc. i4.1 IL_0001: stloc.0 IL_0002: leave. s IL_000f} // end. try finally {IL_0004: ldstr bytearray (09 00 06 5C 69 00 6E 00 74 00 D3 7E 9C 67 39 65 //... \ I. n. t .. ~. G9e 3A 4E 32 00 0C FF 66 00 69 00 6E 00 61 00 6C 00 //: N2... f. i. n. a. l. 6C 00 79 00 67 62 4C 88 8C 5B D5 6B) // l. y. gbL .. [. k IL_0009: call void [mscorlib] System. console: WriteLine (string) IL_000e: endfinally} // end handler IL_000f: ldloc.0 IL_0010: ret} // end of method Program: TestIntReturnInTry
Use Reflector to view C # code in the release version:
Private static int TestIntReturnInTry () {int CS $1 $0000; try {CS $1 $0000 = 1;} finally {Console. writeLine ("\ t change int result to 2, finally executed");} return CS $1 $0000 ;}
Then explain why "Rose" is returned in the third method TestUserReturnInTry ". Reflector view C # code in the release version:
Private static User TestUserReturnInTry () {User CS $1 $0000; User <> g _ initLocal0 = new User {Name = "Mike", BirthDay = new DateTime (0x7da, 1, 1)}; User user = <> g _ initLocal0; try {CS $1 $0000 = user;} finally {user. name = "Rose"; user. birthDay = new DateTime (0x7da, 2, 2); Console. writeLine ("\ t set user. change Name to Rose ");} return CS $1 $0000 ;}
User is a reference type. CS $1 $0000 = user indicates that CS $1 $0000 and user point to the same object. when Name = "Rose", the Name of CS $1 $0000 will also change to "Rose ". Therefore, the Name of the returned CS $1 $0000 is "Rose ".
Another example:
Private static User TestUserReturnInTry2 () {User user = new User () {Name = "Mike", BirthDay = new DateTime (2010, 1, 1)}; try {return user ;} finally {user. name = "Rose"; user. birthDay = new DateTime (2010, 2, 2); user = null; Console. writeLine ("\ t set user to anull ");}}
The returned result is not null, but a User object with Name = "Rose", BirthDay = new DateTime (2010, 2, 2. Reflector view C # code in the release version:
Private static User TestUserReturnInTry2 () {User CS $1 $0000; User <> g _ initLocal1 = new User {Name = "Mike", BirthDay = new DateTime (0x7da, 1, 1)}; User user = <> g _ initLocal1; try {CS $1 $0000 = user;} finally {user. name = "Rose"; user. birthDay = new DateTime (0x7da, 2, 2); user = null; Console. writeLine ("\ t set user to anull");} return CS $1 $0000 ;}
CS $1 $0000 and user point to the same object. When user = null in finally, only the user points to null, the object pointed to by CS $1 $0000 has not changed.