关于c#:使用条件?:(三元)运算符的好处

Benefits of using the conditional ?: (ternary) operator

它的优点和缺点是什么?:运算符,而不是标准if else语句。显而易见的是:

有条件的?操作员

  • 在处理直接价值比较和分配时更简短
  • 似乎不像if/else结构那样灵活

标准if/else

  • 可应用于更多情况(如函数调用)
  • 通常是不必要的长

可读性似乎因语句而异。在第一次被暴露后的一段时间里?:接线员,我花了一段时间才领会到它的工作原理。如果可能的话,你会建议使用它吗?如果我和许多非程序员一起工作,你会坚持使用它吗?


我基本上建议只在结果语句非常短并且在不牺牲可读性的情况下显著提高if/else等效语句的简洁性时使用它。

很好的例子:

1
int result = Check() ? 1 : 0;

坏榜样:

1
int result = FirstCheck() ? 1 : SecondCheck() ? 1 : ThirdCheck() ? 1 : 0;


其他答案也涵盖了这一点,但"这是一种表达"并不能真正解释为什么它如此有用…

在C++和C语言等语言中,可以使用它们定义本地只读字段(在方法体内)。对于常规if/then语句,这是不可能的,因为必须在该单个语句中分配只读字段的值:

1
readonly int speed = (shiftKeyDown) ? 10 : 1;

不同于:

1
2
3
4
5
readonly int speed;  
if (shifKeyDown)  
    speed = 10;    // error - can't assign to a readonly
else  
    speed = 1;     // error

以类似的方式,您可以在其他代码中嵌入第三个表达式。除了使源代码更紧凑(在某些情况下,这样更容易阅读),它还可以使生成的机器代码更紧凑和高效:

1
MoveCar((shiftKeyDown) ? 10 : 1);

…生成的代码可能比必须调用同一方法两次少:

1
2
3
4
if (shiftKeyDown)
    MoveCar(10);
else
    MoveCar(1);

当然,它也是一种更方便和简洁的形式(更少的输入,更少的重复,如果必须在if/else中复制代码块,则可以减少出错的机会)。在这种干净的"共同模式"情况下:

1
object thing = (reference == null) ? null : reference.Thing;

…它只是比长篇大论的if/else等价物更快地读取/解析/理解(一旦习惯了它),所以它可以帮助您更快地"搜索"代码。

当然,仅仅因为它是有用的并不意味着它在每种情况下都是最好的。我建议只在意义清晰(或更清晰)的代码中使用它,方法是使用?:—如果在更复杂的代码中使用它,或者在彼此之间嵌套三元运算符,它会使代码非常难以读取。


当我有很多重复的代码时,我通常选择三元运算符。

1
2
3
4
if (a > 0)
    answer = compute(a, b, c, d, e);
else
    answer = compute(-a, b, c, d, e);

使用三元运算符,可以通过以下方法实现。

1
answer = compute(a > 0 ? a : -a, b, c, d, e);


一个很酷的用法是:

1
2
3
4
x = foo ? 1 :
    bar ? 2 :
    baz ? 3 :
          4;


我发现在进行Web开发时,如果我想将变量设置为请求中发送的值(如果定义了该值),或者设置为某个默认值(如果未定义该值),那么它特别有用。


条件运算符适用于短条件,如:

1
varA = boolB ? valC : valD;

我偶尔使用它,因为这样写东西花费的时间更少…不幸的是,这种分支有时会被另一个浏览代码的开发人员错过。另外,代码通常不是那么短,所以我通常通过放置来帮助可读性?和:在单独的行上,如下所示:

1
2
3
doSomeStuffToSomething(shouldSomethingBeDone()
    ? getTheThingThatNeedsStuffDone()
    : getTheOtherThingThatNeedsStuffDone());

但是,使用if/else块(以及为什么我更喜欢使用这些块)的最大优势是,稍后更容易进入并向分支添加一些额外的逻辑,

1
2
3
4
5
6
if (shouldSomethingBeDone()) {
    doSomeStuffToSomething(getTheThingThatNeedsStuffDone());
    doSomeAdditionalStuff();
} else {
doSomeStuffToSomething(getTheOtherThingThatNeedsStuffDone());
}

或添加其他条件:

1
2
3
4
5
6
if (shouldSomethingBeDone()) {
    doSomeStuffToSomething(getTheThingThatNeedsStuffDone());
    doSomeAdditionalStuff();
} else if (shouldThisOtherThingBeDone()){
    doSomeStuffToSomething(getTheOtherThingThatNeedsStuffDone());
}

所以,归根结底,这是为了方便你现在(更短的使用:?)对你(和其他人)的方便。这是一个判断的召唤…但与所有其他代码格式问题一样,唯一真正的规则是保持一致,并且在视觉上对那些必须维护(或评分)的人保持礼貌。您的代码。

(所有代码眼已编译)


有时,它可以使bool值的赋值更易于乍一看:

1
2
3
4
5
// With
button.IsEnabled = someControl.HasError ? false : true;

// Without
button.IsEnabled = !someControl.HasError;

虽然上述答案是有效的,并且我同意可读性是重要的,但还有2点需要考虑:

  • 在C 6中,可以使用表达式体方法。
  • 这使得使用三元特别简洁:

    1
    2
    3
    string GetDrink(DayOfWeek day)
       => day == DayOfWeek.Friday
          ?"Beer" :"Tea";
  • 当涉及隐式类型转换时,行为会有所不同。
  • 如果您有T1T2两种类型都可以隐式转换为T,则以下内容不起作用:

    1
    T GetT() => true ? new T1() : new T2();

    (因为编译器试图确定三元表达式的类型,并且T1T2之间没有转换。)

    另一方面,下面的if/else版本可以工作:

    1
    2
    3
    4
    5
    T GetT()
    {
       if (true) return new T1();
       return new T2();
    }

    因为T1被转换成TT2也被转换成T


    使用三元运算符时要识别的一件事是,它是表达式而不是语句。

    在诸如Scheme这样的功能语言中,这种区别并不存在:

    (如果(>A B)A B)

    有条件的?操作员"似乎不像if/else结构那样灵活"

    在功能语言中是这样的。

    在命令式语言编程时,我在通常使用表达式(赋值、条件语句等)的情况下应用三元运算符。


    如果我正在设置一个值,我知道它总是一行代码,我通常使用三元(条件)运算符。如果将来我的代码和逻辑有可能发生改变,我会使用if/else,因为它对其他程序员来说更清楚。

    对你更感兴趣的可能是?运算符。


    条件运算符的优点是它是一个运算符。换句话说,它返回一个值。由于if是一个语句,因此它不能返回值。


    我建议限制使用三元(?:)运算符到简单的单行赋值if/else逻辑。类似于这种图案的东西:

    1
    2
    3
    4
    5
    6
    if(<boolCondition>) {
        <variable> = <value>;
    }
    else {
        <variable> = ;
    }

    可以很容易地转换为:

    1
    <variable> = <boolCondition> ? <value> : ;

    我将避免在需要if/else if/else、嵌套if/else或if/else分支逻辑来计算多行的情况下使用三元运算符。在这些情况下应用三元运算符可能会导致无法读取、混淆和无法管理的代码。希望这有帮助。


    如果在相同条件下需要多个分支,请使用if:

    1
    2
    3
    4
    if (A == 6)
      f(1, 2, 3);
    else
      f(4, 5, 6);

    如果您需要具有不同条件的多个分支,那么如果语句计数将滚雪球,则需要使用三元:

    1
    f( (A == 6)? 1: 4, (B == 6)? 2: 5, (C == 6)? 3: 6 );

    此外,还可以在初始化时使用三元运算符。

    1
    const int i = (A == 6)? 1 : 4;

    如果这样做是非常混乱的:

    1
    2
    3
    4
    5
    6
    int i_temp;
    if (A == 6)
       i_temp = 1;
    else
       i_temp = 4;
    const int i = i_temp;

    不能将初始化放在if/else中,因为它会更改作用域。但是引用和常量变量只能在初始化时绑定。


    我发现自己使用它的场景是为了默认值,特别是在返回中

    1
    return someIndex < maxIndex ? someIndex : maxIndex;

    那些确实是我唯一觉得不错的地方,但对他们来说我是。

    不过,如果您要查找布尔值,这有时可能看起来是一个合适的操作:

    1
    bool hey = whatever < whatever_else ? true : false;

    因为它很容易阅读和理解,但是这个想法应该被抛到更明显的地方:

    1
    bool hey = (whatever < whatever_else);

    三元运算符可以包含在右值中,而if-then-else不能包含在右值中;另一方面,if-then-else可以执行循环和其他语句,而三元运算符只能执行(可能是void)右值。

    在相关的注释中,&;&;和运算符允许一些执行模式,这些模式很难用if-then-else实现。例如,如果有多个函数要调用,并且希望在其中任何一个失败时执行一段代码,则可以使用&;运算符很好地完成。如果不使用该运算符,将需要冗余代码、goto或额外的标志变量。


    使用有一些性能优势?在VisualC++中的操作员,但这是一个编译器特有的东西。编译器实际上可以在某些情况下优化条件分支。


    使用C 7,您可以使用新的引用局部变量功能来简化引用兼容变量的条件分配。所以现在,你不仅能做到:

    1
    2
    3
    4
    5
    6
    7
    int i = 0;

    T b = default(T), c = default(T);

    // initialization of C#7 'ref-local' variable using a conditional r-value?1?

    ref T a = ref (i == 0 ? ref b : ref c);

    …但也非常美妙:

    1
    2
    3
    // assignment of l-value?2? conditioned by C#7 'ref-locals'

    (i == 0 ? ref b : ref c) = a;

    该行代码根据i的值将a的值分配给bc

    笔记1。R值是赋值的右侧,即被赋值的值。2。L值是赋值的左侧,即接收赋值的变量。