C#における演算子は、プログラムの論理を構築する上でもっとも基礎的でありながら、もっとも奥が深い要素の一つです。
単純な算術計算から、最新の言語仕様で導入された高度なパターンマッチング、さらにはNull安全を支える特殊な記法まで、演算子を使いこなすことはコードの可読性とパフォーマンスを劇的に向上させることに直結します。
近年のC#の進化に伴い、冗長な条件分岐を排除し、より宣言的に記述できる演算子が数多く追加されてきました。
本記事では、プログラミングの基本となる算術・比較演算子から、開発現場で必須となるモダンな演算子まで、実戦的なコード例を交えて詳しく解説します。
算術演算子と代入演算子の基礎
C#で数値を扱う際に避けて通れないのが算術演算子です。
これらは数学的な計算を行うための記号ですが、C#特有の挙動やデータ型による違いを理解しておく必要があります。
基本的な算術演算子
算術演算子には、加算 +、減算 -、乗算 *、除算 /、そして剰余 % が含まれます。
using System;
int a = 10;
int b = 3;
Console.WriteLine($"加算: {a + b}");
Console.WriteLine($"減算: {a - b}");
Console.WriteLine($"乗算: {a * b}");
Console.WriteLine($"除算: {a / b}"); // 整数同士の除算は切り捨てられる
Console.WriteLine($"剰余: {a % b}");
double x = 10.0;
double y = 3.0;
Console.WriteLine($"浮動小数点数の除算: {x / y}");
加算: 13
減算: 7
乗算: 30
除算: 3
剰余: 1
浮動小数点数の除算: 3.3333333333333335
ここで注意すべき点は、整数型同士の除算では結果が整数に切り捨てられるという点です。
正確な小数点以下の値が必要な場合は、いずれかのオペランドを double や float にキャストする必要があります。
複合代入演算子と増分・減分演算子
変数の値を更新する際、a = a + 5 と書く代わりに a += 5 と記述できる複合代入演算子が便利です。
また、値を1だけ増減させる ++ や -- も多用されます。
int counter = 0;
counter++; // 1増やす
counter += 10; // 10増やす
counter *= 2; // 2倍にする
Console.WriteLine($"最終的な値: {counter}");
最終的な値: 22
インクリメント演算子には「前置」と「後置」があり、式の評価タイミングが異なります。
- 前置 (++a): 値を増やしてから、その値を式の結果として返す。
- 後置 (a++): 元の値を式の結果として返してから、値を増やす。
複雑な式の中でこれらを混ぜるとバグの原因になりやすいため、基本的には単独のステートメントとして記述することが推奨されます。
比較演算子と論理演算子による条件制御
プログラムのフローを制御するためには、値を比較し、その結果に基づいて論理的な判断を行う必要があります。
比較演算子
比較演算子は、2つの値を比較して bool 値(true または false)を返します。
| 演算子 | 説明 | 例 |
|---|---|---|
== | 等しい | a == b |
!= | 等しくない | a != b |
> | より大きい | a > b |
< | より小さい | a < b |
>= | 以上 | a >= b |
<= | 以下 | a <= b |
論理演算子と短絡評価
複数の条件を組み合わせるには、論理演算子(AND &&、OR ||、NOT !)を使用します。
bool isAdult = true;
bool hasLicense = false;
if (isAdult && hasLicense)
{
Console.WriteLine("運転可能です。");
}
else
{
Console.WriteLine("運転できません。");
}
ここで重要な概念が短絡評価(ショートサーキット)です。
&& 演算子では、左側の式が false であれば右側の式は評価されません。
同様に、|| 演算子では左側が true であれば右側は無視されます。
この性質を利用して、Nullチェックとプロパティ参照を一行で行うことができます。
if (user != null && user.IsActive)
{
// userがnullの場合、user.IsActiveは評価されないため例外が発生しない
}
Null許容型を安全に扱う演算子
モダンなC#開発において、NullReferenceException を防ぐことは最優先事項の一つです。
C#には、Nullを簡潔かつ安全に扱うための強力な演算子が備わっています。
Null条件演算子 (?.)
?. 演算子は、オブジェクトが null でない場合にのみメンバーにアクセスし、null の場合はそのまま null を返します。
string? name = GetUserName();
int? length = name?.Length;
Console.WriteLine($"名前の長さ: {length ?? 0}");
Null合体演算子 (??) と Null合体代入演算子 (??=)
?? 演算子は、左側の値が null の場合に右側の値を返します。
これはデフォルト値を設定する際に非常に便利です。
また、C# 8.0で導入された ??= 演算子(Null合体代入)を使用すると、変数が null の場合のみ値を代入するという処理を簡潔に記述できます。
List<string>? items = null;
// itemsがnullなら、新しいListをインスタンス化して代入する
items ??= new List<string>();
items.Add("C#");
Console.WriteLine(items.Count);
1
これにより、以前のような if (items == null) items = new ... という冗長なコードを書く必要がなくなりました。
型判定とパターンマッチング演算子
C# 7.0以降、パターンマッチングの進化により、型チェックや条件判定が劇的に強化されました。
is 演算子による型チェックと変数宣言
is 演算子は、オブジェクトが特定の型であるかどうかを判定します。
さらに、判定と同時にキャスト後の値を変数に格納することも可能です。
object data = "Hello World";
if (data is string text)
{
Console.WriteLine($"文字列として認識: {text.ToUpper()}");
}
switch 式とパターンマッチング
switch 式は演算子のように振る舞い、従来の switch 文よりも簡潔に値を返せます。
int priority = 2;
string message = priority switch
{
1 => "緊急",
2 => "重要",
3 => "通常",
_ => "不明" // デフォルトケース
};
Console.WriteLine($"ステータス: {message}");
さらに、論理パターン(and、or、not)を組み合わせることで、複雑な範囲指定も直感的に記述できます。
int score = 85;
string grade = score switch
{
>= 90 => "A",
>= 80 and < 90 => "B",
>= 70 and < 80 => "C",
_ => "D"
};
Console.WriteLine($"成績: {grade}");
not パターンは、特に Null チェックにおいて可読性を高めます。
if (input is not null)
{
// inputがnullでない場合の処理
}
インデックスと範囲演算子
C# 8.0で導入されたインデックス演算子 ^ と範囲演算子 .. は、配列やコレクションの部分抽出(スライス)を容易にします。
インデックス演算子 (^)
^ は、末尾からの位置を指定します。
^1 は最後の要素、^2 は最後から2番目の要素を指します。
範囲演算子 (..)
.. は、開始位置と終了位置(終了位置は含まない)を指定して範囲を表します。
var numbers = new[] { 0, 1, 2, 3, 4, 5 };
int lastElement = numbers[^1]; // 5
int[] subArray = numbers[1..4]; // { 1, 2, 3 }
int[] allExceptLast = numbers[..^1]; // { 0, 1, 2, 3, 4 }
Console.WriteLine($"最後: {lastElement}");
Console.WriteLine($"スライス: {string.Join(", ", subArray)}");
最後: 5
スライス: 1, 2, 3
これらの演算子は、特に Span や ReadOnlySpan と組み合わせることで、メモリコピーを発生させずに効率的なデータ操作を行うための強力な武器となります。
特殊な用途の演算子
日常的な計算以外にも、C#には特定のコンテキストで威力を発揮する演算子が存在します。
三項演算子 (?:)
条件に基づいて2つの値のいずれかを返すシンプルな演算子です。
int age = 20;
string type = (age >= 18) ? "大人" : "子供";
nameof 演算子
変数、型、メンバーの名前を文字列として取得します。
リファクタリングに強く、マジックストリング(直接書き込まれた文字列)を排除するのに役立ちます。
string myVariable = "test";
Console.WriteLine(nameof(myVariable)); // "myVariable"
with 演算子
C# 9.0で導入された with 式は、レコード(record)型などの非破壊的書き換えに使用されます。
元のオブジェクトを変更せず、一部のプロパティだけを変更した新しいコピーを作成します。
public record Person(string FirstName, string LastName);
var p1 = new Person("Taro", "Yamada");
var p2 = p1 with { FirstName = "Jiro" };
Console.WriteLine(p1);
Console.WriteLine(p2);
演算子の優先順位と結合規則
複数の演算子を一つの式で使う場合、どの演算が先に実行されるかを知っておくことは重要です。
意図しない結果を防ぐため、迷ったときは 括弧 ( ) を使って明示的に優先順位を指定するのが鉄則です。
一般的な優先順位(高い順):
- 一次演算子:
x.y,f(x),a[i],x?,new,typeof - 単項演算子:
+x,-x,!x,++x - 乗法演算子:
*,/,% - 加法演算子:
+,- - シフト演算子:
<<,>> - 関係・型テスト演算子:
<,>,is,as - 等価演算子:
==,!= - 論理演算子:
&,^,|,&&,|| - Null合体演算子:
?? - 条件演算子(三項):
?: - 代入演算子:
=,+=,??=
特に、&& は || よりも優先順位が高い点に注意が必要です。
実戦での演算子活用テクニック
演算子を単体で理解するだけでなく、複数を組み合わせて実戦的な課題を解決する方法をいくつか紹介します。
フラグ管理とビット演算子
複数の状態を一つの整数で管理する場合、ビット演算子(&, |, ^, ~)が活躍します。
[Flags]
public enum Permissions
{
None = 0,
Read = 1,
Write = 2,
Execute = 4
}
Permissions myPerm = Permissions.Read | Permissions.Write;
// 権限が含まれているかチェック
bool canWrite = (myPerm & Permissions.Write) == Permissions.Write;
// もしくは HasFlag メソッド
bool canExecute = myPerm.HasFlag(Permissions.Execute);
Console.WriteLine($"書き込み権限: {canWrite}");
Console.WriteLine($"実行権限: {canExecute}");
数値のオーバーフロー制御 (checked/unchecked)
通常、C#の整数演算でオーバーフロー(最大値を超えて最小値に戻る現象)が発生しても例外はスローされません。
これを制御するために checked 演算子を使用できます。
int max = int.MaxValue;
try
{
int result = checked(max + 1);
}
catch (OverflowException)
{
Console.WriteLine("オーバーフローを検知しました。");
}
安全性が最優先される金融計算などのドメインでは、checked コンテキストを明示的に使用することが推奨されます。
まとめ
C#の演算子は、単なる記号の集まりではなく、言語の設計思想そのものを反映しています。
基本的な算術演算子から、Null安全を実現する ?. や ??、コードを簡潔にする switch 式やパターンマッチングまで、これらを適切に選択することで「意図が明確で、バグが入り込みにくいコード」を書くことができます。
特に最近のC#では、冗長な if 文を演算子や式で置き換える傾向が強まっており、最新の演算子をマスターすることはモダンな開発において必須のスキルと言えるでしょう。
まずは日常的なコーディングの中で、「もっと簡潔に書ける演算子はないか?」と意識することから始めてみてください。
