关于c#:switch case编程实践

switch case programming practice

1
2
3
4
5
6
enum SQLErrorCode{
      OK = 0,
      PARTIAL_OK = 1,
      SOMEWHAT_OK = 2,
      NOT_OK = 3,
};

代码1:

1
2
3
4
5
6
7
8
int error = getErrorCode();
if((error == SQLErrorCode.PARTIAL_OK) ||
  (error == SQLErrorCode.SOMEWHAT_OK) ||
  (error == SQLErrorCode.NOT_OK) ||
  (error < 0))
   callFunction1();
else
    callFunction2();

代码2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch(error){
       case SQLErrorCode.PARTIAL_OK:
                                    callFunction1();
                                    break;
        case SQLErrorCode.SOMEWHAT_OK:
                                    callFunction1();
                                    break;
        case SQLErrorCode.NOT_OK:
                                    callFunction1();
                                    break;
        default:
                                    callFunction2();
                                    break;
}

我更喜欢哪种方法。就性能而言,不应该有太大的差别。如何处理开关柜的误差小于0的情况。

编辑:乔尔的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
switch(error) {
     case SQLErrorCode.PARTIAL_OK:
     case SQLErrorCode.SOMEWHAT_OK:
     case SQLErrorCode.NOT_OK:
         callFunction1();
         break;
     case SQLErrorCode.OK:
         callFunction2();
         break;
     default:     // error < 0 is handled
         callFunction1();
         break;
}

q.处理误差<0。如果我必须处理其他不属于这里任何情况(包括默认情况)的错误号码。


在不表达对哪一个最好的偏好的情况下,还有另一种可能性:

1
2
3
4
5
6
7
8
9
10
switch(error){
    case SQLErrorCode.PARTIAL_OK:
    case SQLErrorCode.SOMEWHAT_OK:
    case SQLErrorCode.NOT_OK:
                                callFunction1();
                                break;
    default:
                                callFunction2();
                                break;
}


对于这样一小部分情况来说,这并不重要,但是对于整数来说,switch实际上更快:它可以而且通常是作为一个跳转表而不是一系列条件检查。

作为比较,将不同案例的数量增加到10个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
enum SQLErrorCode{
    CODE0 = 0,
    CODE1 = 1,
    CODE2 = 2,
    CODE3 = 3,
    CODE4 = 4,
    CODE5 = 5,
    CODE6 = 6,
    CODE7 = 7,
    CODE8 = 8,
    CODE9 = 9
};

enum SQLErrorCode getErrorCode();

void run()
{
    int error = getErrorCode();
#ifdef CASE1
    if((error == CODE0) ||      
       (error == CODE1) ||
       (error == CODE2) ||
       (error == CODE3) ||
       (error == CODE4) ||
       (error == CODE5) ||
       (error == CODE6) ||
       (error == CODE7) ||
       (error == CODE8) ||
       (error == CODE9) ||
       (error < 0))
        callFunction1();
    else
        callFunction2();
#endif
#ifdef CASE2
    switch(error)
    {
        case CODE0:
            callFunction1();
            break;
    case CODE1:
        callFunction1();
        break;
    case CODE2:
        callFunction1();
        break;
    case CODE3:
        callFunction1();
        break;
    case CODE4:
        callFunction1();
        break;
    case CODE5:
        callFunction1();
        break;
    case CODE6:
        callFunction1();
        break;
    case CODE7:
        callFunction1();
        break;
    case CODE8:
        callFunction1();
        break;
    case CODE9:
        callFunction1();
        break;
    default:
        callFunction2();
        break;
}
#endif

}

现在看看在使用gcc的Linux上构建的第一种情况与第二种情况所生成的程序集。

如果你看一下这个组件,你会发现一个显著的区别(对于更大的声明):EDOCX1系列(或EDOCX1系列〔7〕/EDOCX1系列〔8〕,如果你这样做的话)是一次取一个分支。switch变成了一个大表:它需要更多的代码,但这意味着它可以在一次跳转中处理。

(顺便说一下,我们在这里谈论的是C,对吗?不是C?您将无法编译的代码:在C枚举器中,不使用枚举名称作为前缀。所以它是PARTIAL_OK,没有SQLErrorCode.

代码1:cc -DCASE1 -s switch.s switch.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
        .file  "1241256.c"
        .text
.globl run
        .type   run, @function
run:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        call    getErrorCode
        movl    %eax, -4(%ebp)
        cmpl    $0, -4(%ebp)
        je      .L2
        cmpl    $1, -4(%ebp)
        je      .L2
        cmpl    $2, -4(%ebp)
        je      .L2
        cmpl    $3, -4(%ebp)
        je      .L2
        cmpl    $4, -4(%ebp)
        je      .L2
        cmpl    $5, -4(%ebp)
        je      .L2
        cmpl    $6, -4(%ebp)
        je      .L2
        cmpl    $7, -4(%ebp)
        je      .L2
        cmpl    $8, -4(%ebp)
        je      .L2
        cmpl    $9, -4(%ebp)
        je      .L2
        cmpl    $0, -4(%ebp)
        jns     .L13
.L2:
        call    callFunction1
        jmp     .L15
.L13:
        call    callFunction2
.L15:
        leave
        ret
        .size   run, .-run
        .ident "GCC: (GNU) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)"
        .section        .note.GNU-stack,"",@progbits

代码2:cc -DCASE2 -s switch.s switch.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
        .text
.globl run
        .type   run, @function
run:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        call    getErrorCode
        movl    %eax, -4(%ebp)
        cmpl    $9, -4(%ebp)
        ja      .L2
        movl    -4(%ebp), %eax
        sall    $2, %eax
        movl    .L13(%eax), %eax
        jmp     *%eax
        .section        .rodata
        .align 4
        .align 4
.L13:
        .long   .L3
        .long   .L4
        .long   .L5
        .long   .L6
        .long   .L7
        .long   .L8
        .long   .L9
        .long   .L10
        .long   .L11
        .long   .L12
        .text
.L3:
        call    callFunction1
        jmp     .L15
.L4:
        call    callFunction1
        jmp     .L15
.L5:
        call    callFunction1
        jmp     .L15
.L6:
        call    callFunction1
        jmp     .L15
.L7:
        call    callFunction1
        jmp     .L15
.L8:
        call    callFunction1
        jmp     .L15
.L9:
        call    callFunction1
        jmp     .L15
.L10:
        call    callFunction1
        jmp     .L15
.L11:
        call    callFunction1
        jmp     .L15
.L12:
        call    callFunction1
        jmp     .L15
.L2:
        call    callFunction2
.L15:
        leave
        ret
        .size   run, .-run
        .ident "GCC: (GNU) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)"
        .section        .note.GNU-stack,"",@progbits


为什么不。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch(error) {
    case SQLErrorCode.PARTIAL_OK:
    case SQLErrorCode.SOMEWHAT_OK:
    case SQLErrorCode.NOT_OK:
         callFunction1();
         break;
    case SQLErrorCode.OK:
         callFunction2();
         break;
    default:
         if (error < 0)
              callFunction1();
         else
              callFunction2();
         break;
}

写起来比开关容易,读起来比中频容易。但它仍然处理小于0的错误。

编辑:

理查德提出了一个很好的观点。我已经编辑过处理已知范围之外的正负错误。


您还可以编写一个函数来确定什么是OK错误或NotOK错误代码:

1
2
3
4
bool isOK(int code)
{
  return code == SQLErrorCode.OK;
}

你的代码可能会变成:

1
2
3
4
5
6
7
8
if (isOk(getErrorCode()))
{
  callFunction2;
}
else
{
  callFunction1;
}


假设getErrorCode()返回一个枚举值或小于0的值,如何

1
2
3
4
5
int error = getErrorCode();
if (error == SQLErrorCode.OK)
  callFunction2(); // Good path
else
  callFunction1(); // Error / not good enough path

显然,如果您的代码需要在error > 3上使用callFunction2(),那么这不起作用。


每当你有多种方法获得相同的效果,你就会引起混乱。因为,在交换机版本中,SqlErrorCode.PARTIAL_OKSqlErrorCode.SOMEWHAT_OK有不同的情况,这意味着它们有不同的处理。需要做一些研究来看看发生了什么(并且它与if语句处理不完全兼容,这可能意味着它会让您困惑)。

在本例中,我将使用一个if语句,因为这个想法是使用一个或另一个函数。


我有一阵子没碰过C了,但它是不是掉下来了?所以你可以这样写第二个查克…

1
2
3
4
5
6
7
8
9
switch(error){
   case SQLErrorCode.PARTIAL_OK:
    case SQLErrorCode.SOMEWHAT_OK:
    case SQLErrorCode.NOT_OK:
                                callFunction1();
                                break;
    default:
                                callFunction2();
                                break;

}