条件演算子は、一般に三項演算子と呼ばれ、1つの式の中で2つの値から1つを選びます:condition ? valueIfTrue : valueIfFalse。
出力:
0 items, shipping 4.99
1 item, shipping 4.99
5 items, shipping 0
条件は bool でなければなりません。条件が評価された後、2つの分岐のうち1つだけが実行され、もう一方は評価されません。そのため、空のリストで list[0] を評価すると例外になる list.Count > 0 ? list[0] : "empty" のような、ガード付きのアクセスにも安全に使えます。
三項演算子とif/else
三項演算子は式です。代入したり、渡したり、返したりする値を生成します。if 文は文を実行します。次の2つのメソッドは同じことをします。
// with if/else
static string Status(int stock)
{
if (stock > 0)
return "in stock";
else
return "sold out";
}
// with the ternary
static string Status(int stock) => stock > 0 ? "in stock" : "sold out";
両方の分岐が同じ種類の短い値なら三項演算子を使います。分岐が複数の文を実行する、副作用がある、または条件が長くて1行に収まりにくい場合は if を使います。
三項演算子は、動作を実行するだけの if の代わりにはなりません。次のコードはコンパイルできません。
stock > 0 ? Ship(order) : Refund(order);
// error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
elseのない三項演算子もありません。式はどちらの場合にも値を持たなければならないので、? と : は常に両方必要です。
両方の分岐に共通の型が必要
コンパイラは、2つの分岐から式全体の型を導きます。一方の分岐がもう一方の型に暗黙に変換できなければなりません。どちらも変換できないとCS0173になります。
var discount = isMember ? 10 : null;
// error CS0173: Type of conditional expression cannot be determined because
// there is no implicit conversion between 'int' and '<null>'
var label = inStock ? 1 : "none";
// error CS0173: ... between 'int' and 'string'
直し方は、一方の分岐を目的の型にキャストして、もう一方がその型に変換されるようにすることです。
出力:
no discount
7
3.5
points / 2.0 : 0 にキャストは不要です。0 は int で、double に暗黙に変換されるので、式全体は double になります。
C# 9では対象の型による型付けが追加されました。式が宣言された型に代入されるとき、コンパイラはその型を使います。int? discount = isMember ? 10 : null; はC# 9以降でコンパイルできます。var には対象の型がないので、var discount = isMember ? 10 : null; はどのバージョンでも失敗します。
優先順位とかっこ
条件演算子の優先順位は非常に低く、+、==、&& より低くなっています。a > b ? a : b では便利ですが、文字列の連結の中では意外な結果になります。
Console.WriteLine("Status: " + isActive ? "on" : "off");
// error CS0029: Cannot implicitly convert type 'string' to 'bool'
コンパイラは条件を "Status: " + isActive、つまり文字列として読みます。三項演算子をかっこで囲むと直ります:"Status: " + (isActive ? "on" : "off")。
文字列補間には独自の落とし穴があります。{ } の中ではコロンが書式指定子({price:F2})の始まりになるので、かっこのない三項演算子は解析器を混乱させます。かっこで囲みます。
出力:
2 users online
You have 1 unread message
Status: online
三項演算子のネスト
この演算子は右結合なので、a ? x : b ? y : z は a ? x : (b ? y : z) とまとまります。1行に1つの条件を並べた小さな連鎖なら読みやすくなります。
出力:
512 B
20 KB
5 MB
2段か3段を超える場合や、条件がそれぞれ別のものを判定している場合は、if/else if かswitch式に切り替えます。真の分岐の中でのネスト(a ? (b ? x : y) : z)が、最初に読みにくくなる形です。
?? と ?. でよくある三項演算子を置き換える
多くの三項演算子は null を扱うためだけに存在します。C#にはそのための、より短い演算子があります。
| 三項演算子 | 短い形 | 意味 |
|---|---|---|
name != null ? name : "guest" | name ?? "guest" | nullのときの代替値 |
customer != null ? customer.Email : null | customer?.Email | nullを許容するメンバーアクセス |
c != null && c.Address != null ? c.Address.City : "unknown" | c?.Address?.City ?? "unknown" | 両方の組み合わせ |
value != null ? value : throw new ArgumentNullException(...) | value ?? throw new ArgumentNullException(...) | すぐに失敗させる |
出力:
Lena from Berlin
guest from unknown
guest from unknown
throw 式は三項演算子のどちらの分岐にも書けます:int age = input >= 0 ? input : throw new ArgumentOutOfRangeException(nameof(input));。
ref条件式
C# 7.2以降、三項演算子は2つの値ではなく2つの変数から選ぶことができ、その結果を通して代入できます。
// C# 7.2 and later
int wins = 0, losses = 0;
bool won = true;
ref int counter = ref (won ? ref wins : ref losses);
counter++; // increments wins
両方の分岐は、同じ型の変数への ref でなければなりません。アプリケーションのコードではまれで、主に大きな構造体や配列をコピーせずに扱う、性能が重要なコードのために存在します。
よくある質問
C#の三項演算子とは何ですか?
条件演算子 condition ? valueIfTrue : valueIfFalse のことで、C#で唯一3つのオペランドを持つ演算子です。条件を評価し、2つの値のうちちょうど1つを評価して返します:string label = age >= 18 ? "adult" : "minor";。
C#でelseのない三項演算子は使えますか?
使えません。式は常に値を生成しなければならないので、? と : の両方の部分が必須です。条件が成り立つときだけ何かをしたいなら if 文を使います。三項演算子は単独の文にもなれません:x > 0 ? A() : B(); はエラーCS0201です。
「Type of conditional expression cannot be determined」になるのはなぜですか?
2つの分岐に共通の型がないからです。たとえば var x = found ? 42 : null;(int と null)や found ? 1 : "none" です。型が決まるように一方の分岐をキャストします:found ? (int?)42 : null。C# 9以降は宣言された対象の型でも解決できます:int? x = found ? 42 : null; はコンパイルできますが、var では失敗したままです。
文字列補間の中で三項演算子を使うには?
かっこで囲みます:$"{(count == 1 ? "item" : "items")}"。かっこがないと : が書式指定子の始まりとして読まれ、コンパイルできません。
C#で三項演算子をネストできますか?
できます。この演算子は右結合なので、a ? x : b ? y : z は a ? x : (b ? y : z) の意味になります。1段のネストを別々の行に並べれば読みやすいですが、それより深い連鎖は if/else if の連鎖かswitch式のほうが明確です。