mike55 Posted January 31, 2007 Posted January 31, 2007 Hi all Which is more effective/appropriate to use, an if statement or a case statement? Mike55. Quote A Client refers to the person who incurs the development cost. A Customer refers to the person that pays to use the product. ------ My software never has bugs. It just develops random features. (Mosabama vbforums.com)
Administrators PlausiblyDamp Posted January 31, 2007 Administrators Posted January 31, 2007 Depends on the situation - a select case is often cleaner and more maintainable when you have one condition but several possibilities, if .. else if .. else allows you to have different and more complex decision making logic for each if / else if. Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
IceAzul Posted January 31, 2007 Posted January 31, 2007 For simple matching, is switch-case more efficient than a long list of if-elses? I think I read somewhere that it is compiled internally to a hashtable, or something similar. Quote
MrPaul Posted January 31, 2007 Posted January 31, 2007 (edited) Select Case compilation It depends on the nature of the Select Case (or switch in C#) block. Those which have sequential numeric conditions compile to a special switch instruction containing the list of branch locations. Those which have non-sequential or non-numeric conditions tend to compile to a series of conditional statements, as if they were each if/else conditions. For example: string s = "hello world"; int i; switch (s[1]) { //What is the second letter? case 'h': i = 1; break; case 'e': i = 2; break; case 'l': i = 3; break; case 'o': i = 4; break; default: i = 5; break; } switch (i) { case 1: s = "h"; break; case 2: s = "e"; break; case 3: s = "l"; break; case 4: s = "o"; break; default: s = string.Empty; break; } The first switch block, being non-sequential, compiles into a series of conditional tests: IL_0008: callvirt instance char [mscorlib]System.String::get_Chars(int32) IL_000d: stloc.2 IL_000e: ldloc.2 IL_000f: ldc.i4.s 104 IL_0011: bgt.s IL_001f ;greater than 'h'? (special conditional) IL_0013: ldloc.2 IL_0014: ldc.i4.s 101 IL_0016: beq.s IL_002f ;conditional branch (e) IL_0018: ldloc.2 IL_0019: ldc.i4.s 104 IL_001b: beq.s IL_002b ;conditional branch (h) IL_001d: br.s IL_003b IL_001f: ldloc.2 IL_0020: ldc.i4.s 108 IL_0022: beq.s IL_0033 ;conditional branch (l) IL_0024: ldloc.2 IL_0025: ldc.i4.s 111 IL_0027: beq.s IL_0037 ;conditional branch (o) IL_0029: br.s IL_003b IL_002b: ldc.i4.1 ;case 'h' IL_002c: stloc.1 IL_002d: br.s IL_003d IL_002f: ldc.i4.2 ;case 'e' IL_0030: stloc.1 IL_0031: br.s IL_003d IL_0033: ldc.i4.3 ;case 'l' IL_0034: stloc.1 IL_0035: br.s IL_003d IL_0037: ldc.i4.4 ;case 'o' IL_0038: stloc.1 IL_0039: br.s IL_003d IL_003b: ldc.i4.5 ;default IL_003c: stloc.1 The second switch block, having sequential conditions, compiles to a switch instruction: IL_0042: switch ( IL_0059, IL_0061, IL_0069, IL_0071) ;single branching instruction IL_0057: br.s IL_0079 IL_0059: ldstr "h" ;case 1 IL_005e: stloc.0 IL_005f: br.s IL_007f IL_0061: ldstr "e" ;case 2 IL_0066: stloc.0 IL_0067: br.s IL_007f IL_0069: ldstr "l" ;case 3 IL_006e: stloc.0 IL_006f: br.s IL_007f IL_0071: ldstr "o" ;case 4 IL_0076: stloc.0 IL_0077: br.s IL_007f IL_0079: ldsfld string [mscorlib]System.String::Empty ;default IL_007e: stloc.0 For a series of string comparisons, the first style is used, with a series of calls to op_Equality and then branching from there: IL_008b: ldstr "foo" IL_0090: call bool [mscorlib]System.String::op_Equality(string, string) IL_0095: brtrue.s IL_00c3 IL_0097: ldloc.s CS$0$0002 IL_0099: ldstr "bar" IL_009e: call bool [mscorlib]System.String::op_Equality(string, string) IL_00a3: brtrue.s IL_00ca IL_00a5: ldloc.s CS$0$0002 IL_00a7: ldstr "pin" IL_00ac: call bool [mscorlib]System.String::op_Equality(string, string) IL_00b1: brtrue.s IL_00d1 IL_00b3: ldloc.s CS$0$0002 IL_00b5: ldstr "pop" IL_00ba: call bool [mscorlib]System.String::op_Equality(string, string) IL_00bf: brtrue.s IL_00d8 Good luck :) Edited January 31, 2007 by MrPaul Quote Never trouble another for what you can do for yourself.
IceAzul Posted January 31, 2007 Posted January 31, 2007 What about a case like this: switch (integer) { case 1: break; case 2: break; case 3: break; case 4: break; case 9: break; case 10: break; case 11: break; } Quote
MrPaul Posted February 1, 2007 Posted February 1, 2007 (edited) Jump targets In that case the switch instruction is used with 11 jump targets (1 to 11), and the jumps for values 5, 6, 7, and 8 all branch to the end of the code block. If, however, the jump conditions are 1, 2, 3, 4 and 999, 1000, 1001 (large gap in values) then the compiler generates 2 switch blocks (1, 2, 3, 4 and 999, 1000, 1001). The values 999, 1000 and 1001 fail the first switch, it then subtracts 999 from the number and enters the second switch block, where they may succeed. Switch blocks are 0-based. Edited February 1, 2007 by MrPaul Quote Never trouble another for what you can do for yourself.
Leaders snarfblam Posted February 1, 2007 Leaders Posted February 1, 2007 I believe the compiler has a little flexibility. If the values are mostly sequential and/or all within a small range the compiler will use a "calculated goto," the list of branches mentioned by MrPaul. I was under the impression that string-based select cases used a hashtable. The best way to find out, though, would be to compile code and disassemble it with reflector. Quote [sIGPIC]e[/sIGPIC]
MrPaul Posted February 1, 2007 Posted February 1, 2007 Hashtables I believe the compiler has a little flexibility. Exactly. Personally I would suggest worrying less about the efficiency and simply choosing constructs which make the most sense and are easiest to read. I was under the impression that string-based select cases used a hashtable. The best way to find out, though, would be to compile code and disassemble it with reflector. I just did a quick test and it appears that if there are fewer than 6 case labels, then the compiler generates a series of conditional tests. With 6 or more case labels, a Dictionary<string,int32> is used to associate case values with their 'index' in the case block (0-based). This is then used in a switch instruction which does the branching. However, this is unlikely to be a set-in-stone rule so you should not rely on it. Compiler code optimizations can be quite advanced. IL_00ea: volatile. IL_00ec: ldsfld class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '<PrivateImplementationDetails>{F321BCDB-EE97-4BEC-9C3A-65BCE7603F07}'::'$$method0x6000005-1' IL_00f1: ldloc.s CS$0$0002 IL_00f3: ldloca.s CS$0$0003 IL_00f5: call instance bool class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::TryGetValue(!0, !1&) IL_00fa: brfalse.s IL_014d ;no match - go to default IL_00fc: ldloc.s CS$0$0003 IL_00fe: switch ( IL_011d, IL_0125, IL_012d, IL_0135, IL_013d, IL_0145) :cool: Quote Never trouble another for what you can do for yourself.
*Experts* Nerseus Posted February 1, 2007 *Experts* Posted February 1, 2007 I generally wouldn't worry about performance between if and case, unless your app really needs that level of tweaking. For me, I rarely need a switch unless I've got some kind of factory method that's making decisions. Two other thoughts: 1. Switch statements might be a bad smell (from refactoring) if you have duplicated switches in code. 2. See Code Complete 2's Checklist Since CC2 doesn't provide a clear "which to use and when" guide, I'm guessing the choice to if or case falls into one of two categories: first, decide if there's a reason to NOT use one or the other (the checklist should help) and then just go by personal/professional opinion. -ner Quote "I want to stand as close to the edge as I can without going over. Out on the edge you see all the kinds of things you can't see from the center." - Kurt Vonnegut
IceAzul Posted February 1, 2007 Posted February 1, 2007 I was under the impression that it did work by table lookup, not because I disassembled the compiled code, but because of how the debugger worked. In a switch, it jumps to the correct case, but by if-else-if-else, if tested each one. Obviously that may not mean anything when it comes down to execution speed, it does make it a little less grating when stepping through code though. Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.