短い条件分岐を1行で書ける三項演算子(?:)は、if-elseの簡潔な代替として非常に便利です。
ただし、型や評価順序のルール、可読性の配慮を知らないと意図しない動作を招きます。
本記事では、C#の三項演算子を初心者の方にもわかりやすく、実行可能なサンプルとともに丁寧に解説します。
三項演算子(?:)とは?
基本構文
三項演算子は「条件に応じて2つの値のどちらかを選ぶ」ための演算子です。
構文は次のとおりです。
- 構文:
条件式 ? 条件がtrueのときの値 : 条件がfalseのときの値
値を返す「式」なので、代入、出力、メソッドの引数や戻り値など、値が使える場所であればどこでも使えます。
// 三項演算子の最小例
int age = 20;
// 条件(age >= 20)がtrueなら"成人"、falseなら"未成年"を返す
string label = (age >= 20) ? "成人" : "未成年";
Console.WriteLine(label);
成人
if-elseとの等価表現
if-elseで書くと複数行になる分岐を、三項演算子なら1行にできます。
意味はまったく同じです。
// if-else版
int score = 72;
string grade;
if (score >= 80)
{
grade = "A";
}
else
{
grade = "B";
}
Console.WriteLine(grade);
// 三項演算子版(等価)
int score2 = 72;
string grade2 = (score2 >= 80) ? "A" : "B";
Console.WriteLine(grade2);
B
B
初心者がまず覚えるポイント
三項演算子を安全に使うための最初の要点は次のとおりです。
条件式は必ずbool型であること、結果は必ず「値」になること、そして短い分岐だけに使うことが基本です。
複雑な処理や副作用を伴う場合は、迷わずif-elseに戻すのが読みやすさの面で有利です。
三項演算子の使い方(サンプルコード集)
代入での分岐(値の選択)
最もよく使うのは、代入での分岐です。
スコアから成績を決める簡単な例を示します。
2段階のネストも控えめなら読みやすく書けます。
// スコアから成績ラベルを決める
int[] samples = { 92, 76, 61, 48 };
foreach (var s in samples)
{
// 80点以上:A、65点以上:B、それ以外:C
string g = (s >= 80) ? "A"
: (s >= 65) ? "B"
: "C"; // 右結合なので実は s>=80 ? "A" : (s>=65 ? "B" : "C")
Console.WriteLine($"score={s} -> grade={g}");
}
score=92 -> grade=A
score=76 -> grade=B
score=61 -> grade=C
score=48 -> grade=C
式や出力での分岐(文字列補間)
文字列補間の中で三項演算子を使えば、メッセージを簡潔に分けられます。
int balance = 1500;
bool isLow = balance < 2000;
Console.WriteLine($"残高:{balance}円 ({(isLow ? "少なめ" : "十分")})");
int temp = 28;
Console.WriteLine($"気温:{temp,3}℃ -> {(temp >= 30 ? "暑い" : "普通")}");
残高:1500円 (少なめ)
気温: 28℃ -> 普通
メソッド引数・戻り値での分岐
戻り値を選ぶ場合、三項演算子は相性が良いです。
メソッド呼び出しの引数として直接使うこともできます。
// 配送費を会員種別で切り替える
static int CalcShipping(bool isPremium, int weightKg)
{
// プレミアムなら無料、そうでなければ重さ×100円
return isPremium ? 0 : weightKg * 100;
}
bool premium = true;
bool regular = false;
Console.WriteLine($"Premiumの送料:{CalcShipping(premium, 3)}円");
Console.WriteLine($"通常会員の送料:{CalcShipping(regular, 3)}円");
// メソッドの引数で直接分岐
int qty = 5;
int unitPrice = 120;
int total = qty * (qty >= 10 ? 100 : unitPrice); // 10個以上は100円に割引
Console.WriteLine($"合計:{total}円");
Premiumの送料:0円
通常会員の送料:300円
合計:600円
注意として、三項演算子は「値」を返す演算子なので、void
メソッドの呼び分けには向きません。
その場合はif-elseを使います。
C#の型と評価のルールを理解する
結果の型(共通型への変換)
二項目と三項目の「型」をどう決めるかは重要です。
C#では、次のように結果の型が決まります。
両方が同じ型ならその型、片方がもう片方へ暗黙変換できるなら変換先の型、参照型なら共通の基底型やインターフェイスに暗黙変換できるならその型になります。
using System;
class Program
{
static void Main()
{
// 数値の例: int と double -> double に昇格
var n = (true ? 1 : 2.0);
Console.WriteLine($"{n} / Type={n.GetType().Name}");
// 整数の例: short と int -> int
short s = 10;
var m = (false ? s : 20); // elseが選ばれるが、式の型はint
Console.WriteLine($"{m} / Type={m.GetType().Name}");
// 参照型の例: 派生型どうし -> 基底型に
Animal a = (DateTime.Now.DayOfWeek == DayOfWeek.Sunday)
? new Dog()
: new Cat();
Console.WriteLine($"{a} / Type={a.GetType().Name}");
}
}
class Animal
{
public override string ToString() => "Animal";
}
class Dog : Animal
{
public override string ToString() => "Dog";
}
class Cat : Animal
{
public override string ToString() => "Cat";
}
1 / Type=Double
20 / Type=Int32
Cat / Type=Cat
C# 9.0以降では「ターゲット型の推論」により、代入先や引数の型から結果型が補われるケースが増えています。
もしコンパイラが型を決められない場合は、明示的なキャストで補助すると安全です。
// ターゲット型に合わせて条件式の結果を統一する例
IEnumerable<int> MakeSeq(bool useList)
{
// C# 9以降ならターゲット型(IEnumerble<int>)に暗黙変換可能ならOK
return useList ? new List<int> { 1, 2 } : new int[] { 3, 4 };
// 旧バージョン互換ならキャストで明示
// return useList ? (IEnumerable<int>)new List<int> { 1, 2 } : new int[] { 3, 4 };
}
var seq = MakeSeq(DateTime.Now.Second % 2 == 0);
Console.WriteLine(string.Join(",", seq));
3,4
(秒により出力は変わります)
参考表: 代表的な型決定の例
左辺の型 | 右辺の型 | 結果の型 | 例 |
---|---|---|---|
int | double | double | cond ? 1 : 2.0 |
short | int | int | cond ? s : 10 |
Dog | Cat | Animal | cond ? new Dog() : new Cat() |
List<int> | int[] | IEnumerable<int> | 代入先やキャストで統一 |
nullとNullableの扱いの注意
リテラルnull
は参照型やNullable<T>
にしか代入できません。
値型(intなど)の結果にnull
を含めたい場合は、int?
のようにNullable型にします。
bool ok = false;
// 参照型はnullを含められる
string? name = ok ? "Alice" : null;
Console.WriteLine(name ?? "(null)");
// 値型はNullable<int>にする
int? maybe = ok ? 10 : (int?)null; // キャストで型を揃えるのがポイント
Console.WriteLine(maybe?.ToString() ?? "(null)");
(null)
(null)
nullable参照型有効(C# 8.0+)のプロジェクトでは、string?
などの注釈に従うと警告を抑えやすくなります。
片方の分岐がnull
、もう片方が非nullなら、ターゲット側の型注釈(?
の有無)と合わせるようにしましょう。
評価順序と副作用を避ける
三項演算子は「条件 → 合格なら2項目、失敗なら3項目」の順に評価され、選ばれなかった側は評価されません(短絡評価)。
この性質を理解すると、意図しない副作用を避けられます。
// どちらの分岐が評価されるかの確認
int a = 0, b = 0;
int Inc(ref int x)
{
x++;
Console.WriteLine($"Inc called -> x={x}");
return x;
}
bool chooseA = true;
// chooseAがtrueなら前者だけが呼ばれ、後者は呼ばれません
int result = chooseA ? Inc(ref a) : Inc(ref b);
Console.WriteLine($"a={a}, b={b}, result={result}");
Inc called -> x=1
a=1, b=0, result=1
副作用のあるメソッド呼び出しや、重い処理を両方の分岐に書くと読みづらくなります。
処理の実行自体を選びたいなら、if-elseに素直に書くほうが意図が明確です。
可読性と注意点(優先順位・ネスト)
演算子の優先順位と括弧
三項演算子は多くの演算子より「優先順位が低い」部類に入ります。
特に??
(null合体)や論理演算子と組み合わせると読み取りが難しくなるため、括弧で意図を明確にするのが安全です。
参考として覚えておくと安心な並びは次のとおりです(高い→低いの順、抜粋)。
加減乗除などの算術 > 比較 > 論理AND/OR(&&
, ||
) > null合体(??
) > 三項演算子(?:
) > 代入(=
)。
つまり、多くの場合は?:
の外側に他の演算子が食い込みます。
??
と?:
を混在させると読み取りが難しくなります。
次の2つは結果が異なる可能性があるため、必ず括弧で意図を固定しましょう。
// 例: 括弧の有無で解釈が変わることを示す
string x1 = true ? null : "" ?? "X"; // 条件がtrueなので x1 は null
string x2 = (true ? null : "") ?? "X"; // (null) ?? "X" なので x2 は "X"
Console.WriteLine($"x1={(x1 ?? "(null)")}, x2={x2}");
x1=(null), x2=X
三項演算子は式としては低い優先順位にあるため、混在させるときは括弧で明示すると安全です。
特に??
と混ぜる場合は括弧でどちらを結合させたいかを必ず示してください。
右結合とネストの書き方
三項演算子は「右結合」です。
つまり、a ? b : c ? d : e
はa ? b : (c ? d : e)
として解釈されます。
2段階程度のネストは読みやすく整理できますが、3段階以上はif-elseにしたほうが読みやすいことが多いです。
int score = 73;
// 右結合のネスト(括弧で意図をはっきりさせると親切)
string grade = (score >= 80) ? "A"
: (score >= 65) ? "B"
: "C";
Console.WriteLine(grade);
B
複雑に感じたら潔くif-elseに展開しましょう。
チーム開発では「読みやすさ」が最優先です。
使わないほうが良いケースと判断基準
三項演算子は短く書ける反面、乱用すると可読性が下がります。
次のような場合はif-elseを選ぶのが賢明です。
- 分岐ごとに複数の文(ローカル変数の更新、複数のメソッド呼び出しなど)が必要なとき。三項演算子は「式」であり、文の並列配置には向きません。
- 2段階を超えるネストや、分岐条件が長くなるとき。読み手が追いづらくなります。
- 分岐で返す型が複雑に異なり、明確な共通型がないとき。意図が伝わりづらく、キャストだらけになります。
void
メソッドの呼び分けをしたいとき。三項演算子は値を返す前提なので不適です。
次のような書き方はコンパイルエラーになるため避けてください。
void
メソッドの呼び分けはif-elseに置き換えます。
// コンパイルエラーの例: 条件演算子の分岐にvoidは使えません
// bool cond = true;
// cond ? Console.WriteLine("A") : Console.WriteLine("B");
明快さを最優先にし、短さは「結果として」付いてくるものだと考えるとよいです。
まとめ
三項演算子(?:)は、if-elseで書いていた「値の選択」を1行で記述できる便利な演算子です。
条件はboolであること、結果は「値」であること、そして型の整合性(共通型・Nullableの取り扱い)を理解しておけば、代入や文字列補間、メソッドの引数・戻り値などで強力に使えます。
評価は短絡されるため副作用の混入には注意し、??
など他の演算子と混在させる場合は括弧で意図を明確にしてください。
最後に、ネストが深くなる、処理が複雑になる、void
の呼び分けをしたい、といった場面では素直にif-elseへ切り替えるのが可読性の観点で最善です。
短く、正しく、読みやすくを心がけて三項演算子を活用しましょう。