C#プログラミングにおいて、演算子はプログラムの論理を組み立てるための最も基本的な要素です。

その中でも「2項演算子」は、2つの値を組み合わせて新しい値を生成したり、条件を判定したりする際に欠かせない役割を果たします。

初心者からベテランまで、演算子の正確な仕様を理解することは、バグの少ない、そしてパフォーマンスの高いコードを書くための第一歩となります。

本記事では、C#における2項演算子の種類を網羅的に整理し、それぞれの具体的な使い方や実戦で役立つTipsを詳しく解説します。

最新のC#の文脈も踏まえ、NULL合体演算子や代入演算子の効率的な活用方法についても触れていきます。

2項演算子とは何か

2項演算子とは、2つのオペランド(演算の対象となる値や変数)を必要とする演算子のことです。

例えば、a + b という式において、+ は2項演算子であり、ab がオペランドに該当します。

C#には多くの2項演算子が存在し、それらは機能ごとに「算術演算子」「代入演算子」「比較演算子」「論理演算子」「ビット演算子」「NULL合体演算子」などに分類されます。

これらを適切に使い分けることで、複雑な計算や条件分岐を簡潔に記述できるようになります。

算術演算子:数値計算の基本

算術演算子は、数値型(int, double, decimalなど)に対して計算を行うために使用されます。

基本的な5種類の算術演算子

C#で最も頻繁に使用される算術演算子は以下の5種類です。

演算子名称内容
+加算左辺と右辺の値を足す。文字列の連結にも使用される。
-減算左辺から右辺の値を引く。
*乗算左辺と右辺の値を掛ける。
/除算左辺を右辺の値で割る。
%剰余左辺を右辺で割った時の余りを算出する。

除算と剰余における注意点

C#の除算で特に注意が必要なのが、整数同士の計算です。

整数型(intなど)同士で除算を行うと、結果も整数となり、小数点以下は切り捨てられます。

C#
int a = 10;
int b = 3;

int resultInt = a / b;     // 結果は3
double resultDouble = a / b; // これも結果は3.0(計算後にキャストされるため)
double correctResult = (double)a / b; // 結果は3.333...

Console.WriteLine($"整数除算: {resultInt}");
Console.WriteLine($"キャスト後の除算: {correctResult}");
Console.WriteLine($"剰余: {a % b}"); // 10 / 3 の余りは 1
実行結果
整数除算: 3
キャスト後の除算: 3.3333333333333335
剰余: 1

小数点以下の精度が必要な場合は、少なくとも片方のオペランドを doublefloat にキャストしてから演算を行う必要があります。

また、0で除算を行うと「DivideByZeroException」が発生するため、実行前に右辺が0でないことを確認する習慣が重要です。

代入演算子:値の格納と複合演算

代入演算子は、変数に値を格納するために使用されます。

最も基本的なものは = ですが、算術演算と組み合わせた「複合代入演算子」を活用することでコードを簡潔に保てます。

複合代入演算子の活用

x = x + 5 のような記述は、x += 5 と書き換えることができます。

演算子意味
+=a += ba = a + b
-=a -= ba = a - b
*=a *= ba = a * b
/=a /= ba = a / b
%=a %= ba = a % b

これにより、同じ変数名を二度書く手間が省けるだけでなく、意図が明確になり読みやすいコードになります。

比較演算子:条件分岐の要

比較演算子は、2つの値を比較し、その結果を bool 型(true または false)で返します。

主に if 文や while 文の条件式で使用されます。

比較演算子の一覧

  • == : 等しい
  • != : 等しくない
  • > : より大きい
  • < : より小さい
  • >= : 以上
  • <= : 以下

文字列とオブジェクトの比較

C#では、数値だけでなく文字列の比較にも == を使用できます。

C#
string name1 = "Alice";
string name2 = "Alice";

if (name1 == name2)
{
    Console.WriteLine("名前が一致しました。");
}

ただし、参照型(クラスのインスタンスなど)を == で比較する場合、デフォルトでは「メモリ上の参照先が同じかどうか」を判定します。

中身のプロパティが同じかどうかを判定したい場合は、Equals メソッドのオーバーライドや、record型の使用を検討してください。

論理演算子:複雑な条件の組み合わせ

論理演算子は、複数の条件式を組み合わせて、最終的な真偽値を判定するために使用されます。

短絡評価(ショートサーキット)の重要性

C#の論理演算子 && (かつ)と || (または)は、短絡評価を行います。

  • && : 左辺が false の場合、右辺は評価されません(全体が必ず false になるため)。
  • || : 左辺が true の場合、右辺は評価されません(全体が必ず true になるため)。
C#
string? message = null;

// messageがnullの場合、後半のLengthプロパティへのアクセスは実行されないため、エラーにならない
if (message != null && message.Length > 0)
{
    Console.WriteLine("メッセージが存在します。");
}
else
{
    Console.WriteLine("メッセージは空またはnullです。");
}
実行結果
メッセージは空またはnullです。

この特性を理解しておくことで、NullReferenceException をスマートに回避するコードが記述できます。

NULL合体演算子:モダンなC#の書き方

NULL許容型が一般化した現代のC#開発において、最も重要な2項演算子の一つが ?? (NULL合体演算子)です。

?? 演算子の使い方

a ?? b は、「もし a が NULL でなければ a を返し、NULL であれば b を返す」という意味になります。

C#
string? input = null;
string displayName = input ?? "ゲスト";

Console.WriteLine($"こんにちは、{displayName}さん。");
実行結果
こんにちは、ゲストさん。

??= 演算子(NULL合体代入演算子)

C# 8.0で導入された ??= は、さらに便利です。

変数が NULL の場合にのみ、値を代入します。

C#
List<string>? items = null;

// itemsがnullなら新しいリストを作成して代入する
items ??= new List<string>();
items.Add("C#");

Console.WriteLine($"リストの要素数: {items.Count}");
実行結果
リストの要素数: 1

初期化処理を簡潔に記述できるため、プロパティの遅延初期化などで頻繁に利用されます。

ビット演算子:低レイヤー処理とフラグ管理

ビット演算子は、値をビット単位(0と1)で操作するための演算子です。

パフォーマンスが要求される処理や、複数の状態を1つの数値で管理する「フラグ(Flags)」処理で活躍します。

演算子名称内容
&論理積 (AND)両方のビットが1なら1。
|論理和 (OR)どちらかのビットが1なら1。
^排他的論理和 (XOR)ビットが異なるなら1。
<<左シフトビットを左にずらす。
>>右シフトビットを右にずらす。

最近のWebアプリケーション開発では直接ビットを操作する機会は減っていますが、Enum(列挙型)の [Flags] 属性と組み合わせて権限管理などを行う際には必須の知識となります。

実戦Tips:演算子の優先順位と可読性

複雑な式を書く際、演算子が実行される順番(優先順位)を意識する必要があります。

優先順位の基本

一般的に、算術演算(乗除 → 加減)は比較演算より先に実行され、比較演算は論理演算より先に実行されます。

  1. 乗算・除算 (*, /, %)
  2. 加算・減算 (+, -)
  3. 比較演算 (<, >, == など)
  4. 論理演算 (&&, ||)

括弧を積極的に使おう

優先順位を完璧に覚えておく必要はありません。

少しでも複雑だと感じたら、丸括弧 ( ) を使って明示的に順序を指定するのがプロの書き方です。

C#
// どちらが読みやすいか?
bool result1 = a + b > c && d < e;
bool result2 = ((a + b) > c) && (d < e); // 意図が明確

括弧を使うことで、コンパイラに対する指示が明確になるだけでなく、後からコードを読む他のエンジニア(あるいは数ヶ月後の自分)の理解を助けることができます。

演算子のオーバーロード:独自クラスでの活用

C#では、自作したクラスや構造体に対して、2項演算子の挙動を定義(オーバーロード)することができます。

例えば、座標を表す Point クラスにおいて、+ 演算子を定義して「座標同士を足す」といった処理を直感的に書けるようにします。

C#
public struct Vector2
{
    public double X { get; set; }
    public double Y { get; set; }

    public Vector2(double x, double y) => (X, Y) = (x, y);

    // + 演算子のオーバーロード
    public static Vector2 operator +(Vector2 a, Vector2 b)
        => new Vector2(a.X + b.X, a.Y + b.Y);
}

// 利用例
var v1 = new Vector2(10, 20);
var v2 = new Vector2(5, 5);
var v3 = v1 + v2; // 直感的な記述が可能

ただし、演算子の本来の意味から逸脱した定義は避けるべきです。

+ なのに引き算が行われるような実装は、コードの混乱を招くため禁物です。

まとめ

C#の2項演算子は、単純な数値計算からNULLチェック、論理判定まで、プログラムのあらゆる場所に存在します。

  • 算術演算子は整数除算に注意する。
  • 代入演算子は複合形式(+=など)でコードを短縮。
  • 論理演算子は短絡評価を活用してエラーを防ぐ。
  • NULL合体演算子は現代的なNULL安全なコードに必須。
  • 優先順位に迷ったら括弧を使う。

これらの演算子をマスターすることで、記述量が減り、意図が明確な美しいコードを書くことができるようになります。

まずは基本を確実に押さえ、NULL合体演算子などの便利な機能を積極的に取り入れてみてください。

演算子の背後にある動作原理を理解することは、C#プログラミングにおける大きな武器となるはずです。