View C # (2) through IL)
Switch Statement (lower)
Address: http://www.cnblogs.com/AndersLiu/archive/2008/11/06/csharp-via-il-switch-2.html
Original: Anders Liu
Abstract: The switch statement is a common jump statement in C #. Different codes can be executed based on different values of a parameter. This article introduces the IL code generated by the compiler when different types of parameters are passed into the switch statement. This section describes how to use the string type in the switch statement.
Previously, we introduced the use of integer and enumeration types in the switch statement. This section describes how to use the string type. The string type is the only reference type parameter accepted by the switch statement.
Here is a piece of C # code.
Code 1-switch statement using a string-type parameter
<Br/> static void TestSwitchString (string s) <br/>{< br/> switch (s) <br/>{< br/> case null: Console. writeLine ("<null>"); break; <br/> case "one": Console. writeLine (1); break; <br/> case "two": Console. writeLine (2); break; <br/> case "three": Console. writeLine (3); break; <br/> case "four": Console. writeLine (4); break; <br/> default: Console. writeLine ("<unknown>"); break; <br/>}</p> <p> Console. writeLine ("After switch. "); <br/>}< br/> </unknown> </null>
Code 1 shows only one switch statement. It receives a string-type parameter s and displays different texts based on six different situations. What code will the compiler translate? Can this switch statement still use the switch command in IL?
The answer will be revealed immediately. Check the IL obtained from code 1, as shown in Code 2.
Code 2-code 1 get the IL code
<Br/>. method private hidebysig static void TestSwitchString (string s) di-managed <br/>{< br/> // Code size 124 (0x7c) <br/>. maxstack 2 <br/>. locals init (string V_0) <br/> IL_0000: ldarg.0 <br/> IL_0001: dup <br/> IL_0002: stloc.0 <br/> IL_0003: brfalse. s IL_003b <br/> IL_0005: ldloc.0 <br/> IL_0006: ldstr "one" <br/> IL_000b: call bool [mscorlib] System. string: op_Equality (string, <br/> string) <br/> IL_0010: brtrue. s IL_0047 <br/> IL_0012: ldloc.0 <br/> IL_0013: ldstr "two" <br/> IL_0018: call bool [mscorlib] System. string: op_Equality (string, <br/> string) <br/> IL_001d: brtrue. s IL_004f <br/> IL_001f: ldloc.0 <br/> IL_0020: ldstr "three" <br/> IL_0025: call bool [mscorlib] System. string: op_Equality (string, <br/> string) <br/> IL_002a: brtrue. s IL_0057 <br/> IL_002c: ldloc.0 <br/> IL_002d: ldstr "four" <br/> IL_0032: call bool [mscorlib] System. string: op_Equality (string, <br/> string) <br/> IL_0037: brtrue. s IL_005f <br/> IL_0039: br. s IL_0067 <br/> IL_003b: ldstr "<null>" <br/> IL_0040: call void [mscorlib] System. console: WriteLine (string) <br/> IL_0045: br. s IL_0071 <br/> IL_0047: ldc. i4.1 <br/> IL_0048: call void [mscorlib] System. console: WriteLine (int32) <br/> IL_004d: br. s IL_0071 <br/> IL_004f: ldc. i4.2 <br/> IL_0050: call void [mscorlib] System. console: WriteLine (int32) <br/> IL_0055: br. s IL_0071 <br/> IL_0057: ldc. i4.3 <br/> IL_0058: call void [mscorlib] System. console: WriteLine (int32) <br/> IL_005d: br. s IL_0071 <br/> IL_005f: ldc. i4.4 <br/> IL_0060: call void [mscorlib] System. console: WriteLine (int32) <br/> IL_0065: br. s IL_0071 <br/> IL_0067: ldstr "<unknown>" <br/> IL_006c: call void [mscorlib] System. console: WriteLine (string) <br/> IL_0071: ldstr "After switch. "<br/> IL_0076: call void [mscorlib] System. console: WriteLine (string) <br/> IL_007b: ret <br/>}// end of method Program: TestSwitchString <br/> </unknown> </null>
Haha, the first thing I feel is that I didn't see the switch command. Next we will briefly analyze the code.
The first is IL_0000 to IL_0002. Here we will first copy the parameter to a local variable. We also see a similar situation when we introduced the use of integers and enumeration. Old Liu guessed that this is becauseNoCommands for modifying method parameters (such as starg), while C # supports assigning values to parameters in the method body-although this change does not affect the real parameters passed when a method is called. Therefore, in order to satisfy the characteristics of C #, the compiler generates a local variable and copies the parameter values at the beginning of the method, then you can operate on this parameter (modify its value ). I reiterate that this is Liu's own speculation.
If the above speculation is true, then the C # compiler can actually make some improvements, that is, if the method body does not modify the parameter value, you can save this local variable.
The next line of IL_0003 is a conditional jump. The command given by ILDasm is brfalse. In fact, it is more appropriate to write brnull. Brfalse, brnull, and brzero commands are synonyms (their underlying command code is the same ). This set of commands is used to retrieve an element from the top of the stack and determine whether the value is 0 (all fields of the value type are completely zero, and the reference type is null). If yes, the command jumps to the statement specified by the command parameter for execution. Otherwise, the next command is executed.
Obviously, if the parameter s is null, this command will cause the execution process to jump directly to the instruction block indicating case null.
Next, from IL_0005 to IL_0037, each of the four commands is a group, which compares the equality between s and four different strings. If it is equal to a value, it jumps to the corresponding address, this address is the case clause corresponding to this string constant. The equality of strings is performed using the op_Equality ity method, which is equivalent to using the "=" operator to determine whether the strings are equal.
After each command block (case clause) is executed, there will be a line of br. s IL_0071. This IL_0071 corresponds to other statements after the switch statement.
It can be seen that for the C # program segment shown in code 1, the compiler translates the switch statement into a string of if Statements. So, when there are too many case clauses, wouldn't it lead to program slowdown?
Next let's take a look at the code. We can add more case clauses in the switch. For more information, see Code 3.
Code 3-switch statements with more case clauses
<Br/> static void TestSwitchString2 (string s) <br/>{< br/> switch (s) <br/>{< br/> case null: Console. writeLine ("<null>"); break; <br/> case "one": Console. writeLine (1); break; <br/> case "two": Console. writeLine (2); break; <br/> case "three": Console. writeLine (3); break; <br/> case "four": Console. writeLine (4); break; <br/> case "five": Console. writeLine (5); break; <br/> default: Console. writeLine ("<unknown>"); break; <br/>}</p> <p> Console. writeLine ("After switch. "); <br/>}< br/> </unknown> </null>
Haha, Old Liu is not so honest. Isn't there an additional case "five" clause.
Yes, that's all. Next, let's take a look at the IL code corresponding to code 3.
Code 4-The IL code corresponding to code 3
<Br/>. method private hidebysig static void TestSwitchString2 (string s) di-managed <br/>{< br/> // Code size 205 (0xcd) <br/>. maxstack 4 <br/>. locals init (string V_0, <br/> int32 V_1) <br/> IL_0000: ldarg.0 <br/> IL_0001: dup <br/> IL_0002: stloc.0 <br/> IL_0003: brfalse. s IL_0084 <br/> IL_0005: volatile. <br/> IL_0007: lds1_class [mscorlib] System. collections. generic. dictionary '2 <string> '<pr Ivateimplementationdetails> {16CB032A-D97A-40BA-84F1-233334FEF4FA} ':' $ method0x6000007-1 '<br/> IL_000c: brtrue. s IL_0057 <br/> IL_000e: ldc. i4.5 <br/> IL_000f: newobj instance void class [mscorlib] System. collections. generic. dictionary '2 <string> ::. ctor (int32) <br/> IL_0014: dup <br/> IL_0015: ldstr "one" <br/> IL_001a: ldc. i4.0 <br/> IL_001b: call instance void class [mscorlib] System. collections. G Eneric. Dictionary '2 <string >:: Add (! 0, <br/>! 1) <br/> IL_0020: dup <br/> IL_0021: ldstr "two" <br/> IL_0026: ldc. i4.1 <br/> IL_0027: call instance void class [mscorlib] System. collections. generic. dictionary '2 <string >:: Add (! 0, <br/>! 1) <br/> IL_002c: dup <br/> IL_002d: ldstr "three" <br/> IL_0032: ldc. i4.2 <br/> IL_0033: call instance void class [mscorlib] System. collections. generic. dictionary '2 <string >:: Add (! 0, <br/>! 1) <br/> IL_0038: dup <br/> IL_0039: ldstr "four" <br/> IL_003e: ldc. i4.3 <br/> IL_003f: call instance void class [mscorlib] System. collections. generic. dictionary '2 <string >:: Add (! 0, <br/>! 1) <br/> IL_0044: dup <br/> IL_0045: ldstr "five" <br/> IL_004a: ldc. i4.4 <br/> IL_004b: call instance void class [mscorlib] System. collections. generic. dictionary '2 <string >:: Add (! 0, <br/>! 1) <br/> IL_0050: volatile. <br/> IL_0052: sts0000class [mscorlib] System. collections. generic. dictionary '2 <string> '<privateimplementationdetails> {16CB032A-D97A-40BA-84F1-233334FEF4FA}': '$ method0x6000007-1' <br/> IL_0057: volatile. <br/> IL_0059: lds1_class [mscorlib] System. collections. generic. dictionary '2 <string> '<privateimplementationdetails> {16CB032A-D97A-40BA-84F1-233334FEF4FA}': '$ method0 X6000007-1 '<br/> IL_005e: ldloc.0 <br/> IL_005f: ldloca. s V_1 <br/> IL_0061: call instance bool class [mscorlib] System. collections. generic. dictionary '2 <string >:: TryGetValue (! 0, <br/>! 1 &) <br/> IL_0066: brfalse. s IL_00b8 <br/> IL_0068: ldloc.1 <br/> IL_0069: switch (<br/> IL_0090, <br/> IL_0098, <br/> IL_00a0, <br/> IL_00a8, <br/> IL_00b0) <br/> IL_0082: br. s IL_00b8 <br/> IL_0084: ldstr "<null>" <br/> IL_0089: call void [mscorlib] System. console: WriteLine (string) <br/> IL_008e: br. s IL_00c2 <br/> IL_0090: ldc. i4.1 <br/> IL_0091: call void [mscorlib] System. console: WriteLine (int32) <br/> IL_0096: br. s IL_00c2 <br/> IL_0098: ldc. i4.2 <br/> IL_0099: call void [mscorlib] System. console: WriteLine (int32) <br/> IL_009e: br. s IL_00c2 <br/> IL_00a0: ldc. i4.3 <br/> IL_00a1: call void [mscorlib] System. console: WriteLine (int32) <br/> IL_00a6: br. s IL_00c2 <br/> IL_00a8: ldc. i4.4 <br/> IL_00a9: call void [mscorlib] System. console: WriteLine (int32) <br/> IL_00ae: br. s IL_00c2 <br/> IL_00b0: ldc. i4.5 <br/> IL_00b1: call void [mscorlib] System. console: WriteLine (int32) <br/> IL_00b6: br. s IL_00c2 <br/> IL_00b8: ldstr "<unknown>" <br/> IL_00bd: call void [mscorlib] System. console: WriteLine (string) <br/> IL_00c2: ldstr "After switch. "<br/> IL_00c7: call void [mscorlib] System. console: WriteLine (string) <br/> IL_00cc: ret <br/>}// end of method Program :: testSwitchString2 <br/> </unknown> </null> </string> </privateimplementationdetails> </string>> </string> </privateimplementationdetails> </string>
Yeah? Something strange appears. Did you see the IL_0007 command at first glance? Don't be busy. Let's take it apart at 1.1.
First, this command is ldsfld -- Load Static fields. Then the field type is given, which is the class [mscorlib] System. Collections. Generic. Dictionary '2 <string, int32> type. This is an instantiated Dictionary Generic class. Then the specific field to be loaded should be in the form of "ClassName: FieldName". Therefore, we can see that the type of this field is <PrivateImplementationDetails> {16CB032A-D97A-40BA-84F1-233334FEF4FA }, the field name is $ method0x6000007-1.
Annotation
In the ILAsm language, #, $, @, _ (underline), and '(note that it is not a single quotation mark, but a Tilde line "~" Are valid characters in the identifier. In addition, ILAsm also supports enclose identifiers with single quotes, so that some invalid characters can be used.
Use ILDasm to open the generated assembly in a graphical interface. You can see such a type and its field, as shown in 1.
Figure 1-Internal type generated by the compiler for the switch statement
Before continuing, Liu will give you another guess-what is the name of this type and the field name.
The first is the type name. The angle brackets and curly braces in the name are mainly used to prevent conflicts with user-written identifiers, because in most advanced languages, both angle brackets and curly brackets cannot be used in identifiers. "PrivateImplementationDetails" in angle brackets clearly states that this type is implemented internally by the compiler and does not belong to users. Behind the angle brackets, a pair of curly braces is obviously a GUID, and you will find that, this GUID is the MVID of the current module (you can double-click the "m a n I F E S T" node to see the mvid ).
The next step is the field name. The first two $ s also prevent name conflicts. The following method0x6000007 indicates that this is used to identify the metadata as "0x6000007. The last "-1" indicates that this field is the first internal implementation structure used in this method.
Now we know that the compiler automatically generates a type for us and provides a static field of the dictionary class. Next, let's take a closer look at what happened.
First, the commands from IL_0000 to IL_0003 are the same as those in Code 2. The IL_0005 line is a prefix command volatile. It indicates that the ldsfld command next to it will load a "variable" field, that is, this field may be changed by other processes. This tells the runtime environment not to cache its value when accessing a field.
Annotation
In the IL command, the prefix command only modifies the next command. Other commands are not affected.
The IL_0007 and IL_000C lines determine whether the previously mentioned "internal field" is null. If it is not null, the system jumps to IL_0057. Otherwise, the following command is executed to create a new Dictionary <string, int32> type field. Similarly, brtrue is more suitable for writing brinst here (brtrue and brinst are also a group of synonyms, and their command code is the same ).
Next, IL_000e is written to IL_0052. First, a dictionary class object is initialized, and the five strings (except null) in the case clause are inserted into the dictionary as keys respectively, each string corresponds to an integer ranging from 0 to 4. Finally, save the object in "internal fields.
The next step is IL_0057, which is the position to jump to when "internal field" is not null. From IL_0057 to IL_0061, you can call the TryGetValue method of the dictionary class to find that the key value in the dictionary is the item specified by the switch parameter s.
From this, we can see that when calling a method in IL, the parameters are pushed from left to right to the stack. If the instance method is called, the method is called on which object, this object should be first pushed into the stack. For example, here, we first press "internal field", then 0th local variables (copied parameter s), and finally the address of the 1st parameters.
In addition, we also see how the out parameter in C # is implemented. For method declaration, the out parameter is declared as a Type similar to "Type &", which is a managed pointer. When passing values, you can use the ldloca command to obtain the address of the local variable.
IL_0066. If no corresponding key is found in the above TryGetValue method, it will jump to IL_00b8 -- from the content of this row -- the location of the default clause.
IL_0069, aha, we are familiar with the switch command. Based on the obtained integer, jump to each case clause to run.
Let's look at the command at the location of IL_0082 after the switch command. This is an unconditional jump, directly jump to IL_00b8 -- default clause. Review the switch command usage. When the integer obtained from the top of the stack is larger than the number of Jump addresses in the switch command, the switch command is ignored and the next command is executed directly. Therefore, it can be considered that the command followed by the switch command should be similar to the default clause in the switch statement in C # language. But here, the compiler puts the IL code corresponding to the default clause to the end, and places an unconditional jump next to the switch command to jump to the default clause.
Now, the code is basically analyzed.
Summary
This section describes how to use a String object as a parameter in a switch statement.
As you can see, when there are not many case clauses, the compiler translates them into a structure similar to a series of if Statements and compares them with each case using the "=" operator.
When the number of case clauses is large, the compiler generates an internal class and provides a dictionary field. The key of this dictionary field is a string type and value is an integer type. The key records each case, and the value records the sequence number of the corresponding case clause. Then, take the switch statement's parameter s as the key, obtain the corresponding value, and use the switch command for redirection.
In this way, the time complexity of the Dictionary <TKey, TValue> type using key is close to that of O (1) this feature (see the description of Dictionary generic classes on MSDN) helps improve efficiency. In addition, this field is initialized only once when necessary, further improving the overall efficiency of the program.
If your program uses a large number of if Statements to determine whether a String object has a given value, you may want to change it to a switch statement. If you have other reference type objects that require similar judgment and cannot use the switch statement (C # syntax is not allowed), you can try to write a dictionary field by yourself, keys are given based on several possible objects, and continuous integers are used as values. When each judgment is performed, the given object (parameter) is used as the key, after obtaining the vlaue, use the switch to judge it.
Returned Directory: view C # Through IL #