演算子

【C#】演算子一覧と使い方完全リファレンス—算術・比較・論理・ビット演算・null合体をマスター

C#の演算子は、数値計算の+ - * / % ++ --、比較の== != < > <= >=、論理の&& || !、ビット操作の& | ^ ~ << >>、複合代入+= -= *= /= %= &= |= ^= <<= >>=などが基本です。

加えて?:で条件選択、??でnull対策、is as typeof newで型関連を扱えます。

種類と優先順位を把握すれば意図が伝わる簡潔なコードを書けます。

算術演算子

算術演算子は、数値の計算を行うために使われる基本的な演算子です。

C#では整数型や浮動小数点型の変数に対して利用でき、プログラムの中で数値の加減乗除や剰余計算を簡単に実装できます。

ここでは基本の四則演算からインクリメント・デクリメント、符号演算子まで詳しく解説いたします。

基本の四則演算

四則演算は、加算、減算、乗算、除算の4つの基本的な算術演算を指します。

C#ではそれぞれ+-*/の演算子を使います。

整数型や浮動小数点型の変数に対して適用可能です。

+ 加算

+演算子は、2つの数値を足し合わせるために使います。

整数同士、浮動小数点同士、または混合しても利用可能です。

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = 20;
        int sum = a + b; // aとbを加算
        double x = 3.5;
        double y = 2.5;
        double total = x + y; // 浮動小数点の加算
        Console.WriteLine($"整数の加算: {a} + {b} = {sum}");
        Console.WriteLine($"浮動小数点の加算: {x} + {y} = {total}");
    }
}
整数の加算: 10 + 20 = 30
浮動小数点の加算: 3.5 + 2.5 = 6

このように、+は単純に2つの値を足し合わせる演算子です。

文字列に対しても連結演算子として使えますが、ここでは数値演算に限定して説明します。

– 減算

-演算子は、左の数値から右の数値を引く減算を行います。

加算と同様に整数や浮動小数点に使えます。

using System;
class Program
{
    static void Main()
    {
        int a = 50;
        int b = 15;
        int difference = a - b; // aからbを減算
        double x = 7.5;
        double y = 2.0;
        double result = x - y; // 浮動小数点の減算
        Console.WriteLine($"整数の減算: {a} - {b} = {difference}");
        Console.WriteLine($"浮動小数点の減算: {x} - {y} = {result}");
    }
}
整数の減算: 50 - 15 = 35
浮動小数点の減算: 7.5 - 2 = 5.5

減算は加算と同様に基本的な演算で、数値の差を求める際に使います。

* 乗算

*演算子は、2つの数値を掛け合わせる乗算を行います。

整数や浮動小数点の両方に対応しています。

using System;
class Program
{
    static void Main()
    {
        int a = 6;
        int b = 7;
        int product = a * b; // aとbの乗算
        double x = 2.5;
        double y = 4.0;
        double result = x * y; // 浮動小数点の乗算
        Console.WriteLine($"整数の乗算: {a} * {b} = {product}");
        Console.WriteLine($"浮動小数点の乗算: {x} * {y} = {result}");
    }
}
整数の乗算: 6 * 7 = 42
浮動小数点の乗算: 2.5 * 4 = 10

乗算は繰り返し加算のような計算や、面積計算などでよく使われます。

/ 除算

/演算子は、左の数値を右の数値で割る除算を行います。

整数同士の除算は商の整数部分のみが返され、小数点以下は切り捨てられます。

浮動小数点型の場合は小数点以下も含めた正確な商が返ります。

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = 3;
        int quotientInt = a / b; // 整数除算(小数点以下切り捨て)
        double x = 10.0;
        double y = 3.0;
        double quotientDouble = x / y; // 浮動小数点除算
        Console.WriteLine($"整数の除算: {a} / {b} = {quotientInt}");
        Console.WriteLine($"浮動小数点の除算: {x} / {y} = {quotientDouble}");
    }
}
整数の除算: 10 / 3 = 3
浮動小数点の除算: 10 / 3 = 3.3333333333333335

整数除算では割り切れない場合、小数点以下は切り捨てられるため注意が必要です。

正確な商が必要な場合は浮動小数点型を使いましょう。

% 剰余

%演算子は、左の数値を右の数値で割った余り(剰余)を求めます。

整数型に対してよく使われますが、浮動小数点型にも適用可能です。

using System;
class Program
{
    static void Main()
    {
        int a = 17;
        int b = 5;
        int remainder = a % b; // aをbで割った余り
        double x = 7.5;
        double y = 2.0;
        double remainderDouble = x % y; // 浮動小数点の剰余
        Console.WriteLine($"整数の剰余: {a} % {b} = {remainder}");
        Console.WriteLine($"浮動小数点の剰余: {x} % {y} = {remainderDouble}");
    }
}
整数の剰余: 17 % 5 = 2
浮動小数点の剰余: 7.5 % 2 = 1.5

剰余演算は、偶数・奇数判定や周期的な処理、配列の循環アクセスなどに役立ちます。

インクリメントとデクリメント

インクリメント・デクリメント演算子は、変数の値を1だけ増減させる簡潔な方法です。

++がインクリメント、--がデクリメントを表します。

前置と後置の2種類があり、動作のタイミングが異なります。

++ 前置・後置インクリメント

++演算子は変数の値を1増やします。

前置++varは値を増やしてから式の評価を行い、後置var++は式の評価後に値を増やします。

using System;
class Program
{
    static void Main()
    {
        int a = 5;
        int preIncrement = ++a; // aを先に増やしてから代入
        Console.WriteLine($"前置インクリメント後のa: {a}, 代入値: {preIncrement}");
        a = 5; // aをリセット
        int postIncrement = a++; // 代入後にaを増やす
        Console.WriteLine($"後置インクリメント後のa: {a}, 代入値: {postIncrement}");
    }
}
前置インクリメント後のa: 6, 代入値: 6
後置インクリメント後のa: 6, 代入値: 5

この違いはループや複雑な式の中で重要になるため、使い分けに注意してください。

— 前置・後置デクリメント

--演算子は変数の値を1減らします。

インクリメントと同様に前置--varと後置var--があります。

using System;
class Program
{
    static void Main()
    {
        int a = 5;
        int preDecrement = --a; // aを先に減らしてから代入
        Console.WriteLine($"前置デクリメント後のa: {a}, 代入値: {preDecrement}");
        a = 5; // aをリセット
        int postDecrement = a--; // 代入後にaを減らす
        Console.WriteLine($"後置デクリメント後のa: {a}, 代入値: {postDecrement}");
    }
}
前置デクリメント後のa: 4, 代入値: 4
後置デクリメント後のa: 4, 代入値: 5

デクリメントもインクリメントと同様に、前置・後置の違いを理解して使い分けることが大切です。

符号演算子

符号演算子は、数値の符号を明示的に指定するための単項演算子です。

+は単項正符号、-は単項負符号として使います。

数値の符号を変えたり、明示的に正の値であることを示したりする際に利用します。

+ 単項正符号

単項の+演算子は、数値の符号を変えずにそのままの値を返します。

通常は省略されることが多いですが、明示的に正の値であることを示したい場合に使います。

using System;
class Program
{
    static void Main()
    {
        int a = -10;
        int b = +a; // 単項正符号、値は変わらない
        Console.WriteLine($"元の値: {a}");
        Console.WriteLine($"単項正符号適用後の値: {b}");
    }
}
元の値: -10
単項正符号適用後の値: -10

この例のように、+は値を変えずにそのまま返します。

– 単項負符号

単項の-演算子は、数値の符号を反転させます。

正の値を負に、負の値を正に変換します。

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = -a; // 符号を反転
        int c = -20;
        int d = -c; // 符号を反転
        Console.WriteLine($"元の値a: {a}, 符号反転後b: {b}");
        Console.WriteLine($"元の値c: {c}, 符号反転後d: {d}");
    }
}
元の値a: 10, 符号反転後b: -10
元の値c: -20, 符号反転後d: 20

符号演算子は数値の符号を明示的に操作したい場合に便利です。

特に負の値を扱う計算や符号の切り替えに使います。

代入演算子

単純代入

= 値の設定

=演算子は、右辺の値を左辺の変数に代入するために使います。

C#の代入演算子の基本であり、変数の初期化や値の更新に欠かせません。

using System;
class Program
{
    static void Main()
    {
        int number;
        number = 10; // numberに10を代入
        string message = "こんにちは"; // 宣言と同時に代入
        Console.WriteLine($"numberの値: {number}");
        Console.WriteLine($"messageの値: {message}");
    }
}
numberの値: 10
messageの値: こんにちは

このように、=は右辺の値を左辺の変数にセットします。

左辺は変数やプロパティ、インデクサなど代入可能な場所でなければなりません。

複合代入

複合代入演算子は、演算と代入を同時に行う便利な構文です。

例えば+=は加算して代入、-=は減算して代入を意味します。

コードが簡潔になり、可読性も向上します。

+= 加算して代入

+=は左辺の変数に右辺の値を加算し、その結果を左辺に代入します。

using System;
class Program
{
    static void Main()
    {
        int count = 5;
        count += 3; // count = count + 3 と同じ
        Console.WriteLine($"countの値: {count}");
    }
}
countの値: 8

文字列の連結にも使えます。

using System;
class Program
{
    static void Main()
    {
        string greeting = "Hello";
        greeting += " World"; // greeting = greeting + " World"
        Console.WriteLine(greeting);
    }
}
Hello World

-= 減算して代入

-=は左辺の変数から右辺の値を引き、その結果を左辺に代入します。

using System;
class Program
{
    static void Main()
    {
        int score = 100;
        score -= 30; // score = score - 30 と同じ
        Console.WriteLine($"scoreの値: {score}");
    }
}
scoreの値: 70

*= 乗算して代入

*=は左辺の変数に右辺の値を掛け、その結果を左辺に代入します。

using System;
class Program
{
    static void Main()
    {
        int price = 200;
        price *= 3; // price = price * 3 と同じ
        Console.WriteLine($"priceの値: {price}");
    }
}
priceの値: 600

/= 除算して代入

/=は左辺の変数を右辺の値で割り、その結果を左辺に代入します。

整数型の場合は商の整数部分のみが代入されます。

using System;
class Program
{
    static void Main()
    {
        int total = 50;
        total /= 4; // total = total / 4 と同じ
        Console.WriteLine($"totalの値: {total}");
    }
}
totalの値: 12

%= 剰余で代入

%=は左辺の変数を右辺の値で割った余りを左辺に代入します。

using System;
class Program
{
    static void Main()
    {
        int remainder = 17;
        remainder %= 5; // remainder = remainder % 5 と同じ
        Console.WriteLine($"remainderの値: {remainder}");
    }
}
remainderの値: 2

&= ビットAND代入

&=は左辺の変数と右辺の値のビットごとのAND演算を行い、その結果を左辺に代入します。

主に整数型で使います。

using System;
class Program
{
    static void Main()
    {
        int flags = 0b_1101; // 13
        flags &= 0b_1011;    // 11 とビットAND
        Console.WriteLine($"flagsの値: {Convert.ToString(flags, 2)} (10進数: {flags})");
    }
}
flagsの値: 1001 (10進数: 9)

|= ビットOR代入

|=は左辺の変数と右辺の値のビットごとのOR演算を行い、その結果を左辺に代入します。

using System;
class Program
{
    static void Main()
    {
        int flags = 0b_0101; // 5
        flags |= 0b_1010;    // 10 とビットOR
        Console.WriteLine($"flagsの値: {Convert.ToString(flags, 2)} (10進数: {flags})");
    }
}
flagsの値: 1111 (10進数: 15)

^= ビットXOR代入

^=は左辺の変数と右辺の値のビットごとの排他的OR(XOR)演算を行い、その結果を左辺に代入します。

using System;
class Program
{
    static void Main()
    {
        int flags = 0b_1100; // 12
        flags ^= 0b_1010;    // 10 とビットXOR
        Console.WriteLine($"flagsの値: {Convert.ToString(flags, 2)} (10進数: {flags})");
    }
}
flagsの値: 110 (10進数: 6)

<<= 左シフト代入

<<=は左辺の変数のビットを右辺の数だけ左にシフトし、その結果を左辺に代入します。

シフトは2のべき乗倍の計算に使われます。

using System;
class Program
{
    static void Main()
    {
        int value = 3;      // 0b_0011
        value <<= 2;        // 2ビット左シフト
        Console.WriteLine($"valueの値: {Convert.ToString(value, 2)} (10進数: {value})");
    }
}
valueの値: 1100 (10進数: 12)

>>= 右シフト代入

>>=は左辺の変数のビットを右辺の数だけ右にシフトし、その結果を左辺に代入します。

符号付き整数の場合は符号ビットを保持しながらシフトします。

using System;
class Program
{
    static void Main()
    {
        int value = 12;     // 0b_1100
        value >>= 2;        // 2ビット右シフト
        Console.WriteLine($"valueの値: {Convert.ToString(value, 2)} (10進数: {value})");
    }
}
valueの値: 11 (10進数: 3)

比較演算子

等価判定

== 等しい

==演算子は、2つの値が等しいかどうかを比較し、等しければtrue、そうでなければfalseを返します。

数値型だけでなく、文字列やオブジェクトの比較にも使われますが、オブジェクトの場合は参照の等価性を比較するため注意が必要です。

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = 10;
        int c = 20;
        Console.WriteLine($"a == b: {a == b}"); // true
        Console.WriteLine($"a == c: {a == c}"); // false
        string s1 = "hello";
        string s2 = "hello";
        string s3 = "world";
        Console.WriteLine($"s1 == s2: {s1 == s2}"); // true(文字列の内容比較)
        Console.WriteLine($"s1 == s3: {s1 == s3}"); // false
    }
}
a == b: True
a == c: False
s1 == s2: True
s1 == s3: False

文字列の場合、==は内容の等価性を比較します。

カスタムクラスの場合はEqualsメソッドや==演算子のオーバーロードによって挙動が変わることがあります。

!= 等しくない

!=演算子は、2つの値が等しくないかどうかを比較し、等しくなければtrue、等しければfalseを返します。

==の否定にあたります。

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = 20;
        Console.WriteLine($"a != b: {a != b}"); // true
        Console.WriteLine($"a != 10: {a != 10}"); // false
        string s1 = "apple";
        string s2 = "orange";
        Console.WriteLine($"s1 != s2: {s1 != s2}"); // true
    }
}
a != b: True
a != 10: False
s1 != s2: True

大小比較

< より小さい

<演算子は、左辺の値が右辺の値より小さいかどうかを判定し、真偽値を返します。

数値型や文字列(辞書順)などで使えます。

using System;
class Program
{
    static void Main()
    {
        int a = 5;
        int b = 10;
        Console.WriteLine($"a < b: {a < b}"); // true
        Console.WriteLine($"b < a: {b < a}"); // false
        string s1 = "apple";
        string s2 = "banana";
        Console.WriteLine($"s1 < s2: {string.Compare(s1, s2) < 0}"); // true(辞書順)
    }
}
a < b: True
b < a: False
s1 < s2: True

文字列の大小比較は<演算子では直接できないため、string.Compareメソッドを使って辞書順で比較します。

> より大きい

>演算子は、左辺の値が右辺の値より大きいかどうかを判定します。

using System;
class Program
{
    static void Main()
    {
        int a = 15;
        int b = 10;
        Console.WriteLine($"a > b: {a > b}"); // true
        Console.WriteLine($"b > a: {b > a}"); // false
    }
}
a > b: True
b > a: False

<= 以下

<=演算子は、左辺の値が右辺の値以下かどうかを判定します。

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = 10;
        int c = 20;
        Console.WriteLine($"a <= b: {a <= b}"); // true
        Console.WriteLine($"a <= c: {a <= c}"); // true
        Console.WriteLine($"c <= a: {c <= a}"); // false
    }
}
a <= b: True
a <= c: True
c <= a: False

>= 以上

>=演算子は、左辺の値が右辺の値以上かどうかを判定します。

using System;
class Program
{
    static void Main()
    {
        int a = 20;
        int b = 10;
        int c = 20;
        Console.WriteLine($"a >= b: {a >= b}"); // true
        Console.WriteLine($"a >= c: {a >= c}"); // true
        Console.WriteLine($"b >= a: {b >= a}"); // false
    }
}
a >= b: True
a >= c: True
b >= a: False

論理演算子

条件付き論理

&& AND (短絡評価)

&&演算子は、左辺と右辺の両方がtrueの場合にのみtrueを返す論理AND演算子です。

特徴的なのは短絡評価(ショートサーキット)を行う点で、左辺がfalseの場合は右辺を評価せずに結果を返します。

これにより、無駄な計算や副作用のある処理を避けられます。

using System;
class Program
{
    static bool IsPositive(int x)
    {
        Console.WriteLine("IsPositiveが呼ばれました");
        return x > 0;
    }
    static void Main()
    {
        int a = -5;
        int b = 10;
        // 左辺がfalseなので右辺は評価されない
        bool result = (a > 0) && IsPositive(b);
        Console.WriteLine($"結果: {result}");
    }
}
結果: False

この例では、a > 0falseなのでIsPositive(b)は呼ばれません。

これが短絡評価の効果です。

|| OR (短絡評価)

||演算子は、左辺または右辺のどちらかがtrueであればtrueを返す論理OR演算子です。

こちらも短絡評価を行い、左辺がtrueの場合は右辺を評価しません。

using System;
class Program
{
    static bool IsNegative(int x)
    {
        Console.WriteLine("IsNegativeが呼ばれました");
        return x < 0;
    }
    static void Main()
    {
        int a = 5;
        int b = -10;
        // 左辺がtrueなので右辺は評価されない
        bool result = (a > 0) || IsNegative(b);
        Console.WriteLine($"結果: {result}");
    }
}
結果: True

この例では、a > 0trueなのでIsNegative(b)は呼ばれず、効率的に評価が終わります。

単項論理

! NOT

!演算子は、論理値を反転させます。

trueならfalseに、falseならtrueに変換します。

条件式の否定やフラグの反転に使います。

using System;
class Program
{
    static void Main()
    {
        bool isActive = true;
        bool isInactive = !isActive; // trueをfalseに反転
        Console.WriteLine($"isActive: {isActive}");
        Console.WriteLine($"isInactive: {isInactive}");
    }
}
isActive: True
isInactive: False

非条件付き論理

& AND

&演算子は、論理AND演算を行いますが、&&と異なり短絡評価を行いません。

左辺と右辺の両方を必ず評価します。

ビット演算子としても使われますが、ここでは論理演算として説明します。

using System;
class Program
{
    static bool CheckA()
    {
        Console.WriteLine("CheckAが呼ばれました");
        return true;
    }
    static bool CheckB()
    {
        Console.WriteLine("CheckBが呼ばれました");
        return false;
    }
    static void Main()
    {
        bool result = CheckA() & CheckB(); // 両方評価される
        Console.WriteLine($"結果: {result}");
    }
}
CheckAが呼ばれました
CheckBが呼ばれました
結果: False

&&と違い、左辺がfalseでも右辺が評価されるため、副作用のあるメソッド呼び出しがある場合は注意が必要です。

| OR

|演算子は論理OR演算を行い、||と異なり短絡評価を行いません。

左辺と右辺の両方を必ず評価します。

using System;
class Program
{
    static bool CheckA()
    {
        Console.WriteLine("CheckAが呼ばれました");
        return false;
    }
    static bool CheckB()
    {
        Console.WriteLine("CheckBが呼ばれました");
        return true;
    }
    static void Main()
    {
        bool result = CheckA() | CheckB(); // 両方評価される
        Console.WriteLine($"結果: {result}");
    }
}
CheckAが呼ばれました
CheckBが呼ばれました
結果: True

^ XOR

^演算子は、排他的論理和(XOR)を行います。

左右の論理値が異なる場合にtrueを返し、同じ場合はfalseを返します。

using System;
class Program
{
    static void Main()
    {
        bool a = true;
        bool b = false;
        Console.WriteLine($"a ^ b: {a ^ b}"); // true
        Console.WriteLine($"a ^ a: {a ^ a}"); // false
        Console.WriteLine($"b ^ b: {b ^ b}"); // false
    }
}
a ^ b: True
a ^ a: False
b ^ b: False

XORはビット演算でも使われ、ビット単位で異なるビットを検出する際に役立ちます。

論理演算としては、条件が片方だけ真の場合に処理を行いたいときに使います。

ビット演算子

ビット単位の論理演算

& ビットAND

&演算子は、2つの整数値のビットごとにAND演算を行います。

両方のビットが1の場合にのみ結果のビットが1になります。

ビットマスク処理やフラグ管理に使われます。

using System;
class Program
{
    static void Main()
    {
        int a = 0b_1101; // 13
        int b = 0b_1011; // 11
        int result = a & b; // ビットAND
        Console.WriteLine($"a: {Convert.ToString(a, 2)}");
        Console.WriteLine($"b: {Convert.ToString(b, 2)}");
        Console.WriteLine($"a & b: {Convert.ToString(result, 2)} (10進数: {result})");
    }
}
a: 1101
b: 1011
a & b: 1001 (10進数: 9)

この例では、11011011のビットごとにANDを行い、1001(10進数9)が得られています。

| ビットOR

|演算子は、2つの整数値のビットごとにOR演算を行います。

どちらかのビットが1であれば結果のビットが1になります。

using System;
class Program
{
    static void Main()
    {
        int a = 0b_0101; // 5
        int b = 0b_1010; // 10
        int result = a | b; // ビットOR
        Console.WriteLine($"a: {Convert.ToString(a, 2)}");
        Console.WriteLine($"b: {Convert.ToString(b, 2)}");
        Console.WriteLine($"a | b: {Convert.ToString(result, 2)} (10進数: {result})");
    }
}
a: 101
b: 1010
a | b: 1111 (10進数: 15)

ビットORは複数のフラグをまとめる際に便利です。

^ ビットXOR

^演算子は、2つの整数値のビットごとに排他的OR(XOR)演算を行います。

ビットが異なる場合に1、同じ場合に0になります。

using System;
class Program
{
    static void Main()
    {
        int a = 0b_1100; // 12
        int b = 0b_1010; // 10
        int result = a ^ b; // ビットXOR
        Console.WriteLine($"a: {Convert.ToString(a, 2)}");
        Console.WriteLine($"b: {Convert.ToString(b, 2)}");
        Console.WriteLine($"a ^ b: {Convert.ToString(result, 2)} (10進数: {result})");
    }
}
a: 1100
b: 1010
a ^ b: 110 (10進数: 6)

XORはビットの違いを検出したり、ビットの反転に使われます。

~ ビット補数

~演算子は、単項演算子でビットのすべてを反転(補数)します。

0は1に、1は0に変わります。

using System;
class Program
{
    static void Main()
    {
        int a = 0b_1010; // 10
        int result = ~a; // ビット補数
        Console.WriteLine($"a: {Convert.ToString(a, 2)}");
        Console.WriteLine($"~a: {Convert.ToString(result, 2)} (10進数: {result})");
    }
}
a: 1010
~a: -1011 (10進数: -11)

符号付き整数では2の補数表現のため、~a-(a+1)の値になります。

ビット単位の反転に使います。

シフト演算

<< 左シフト

<<演算子は、ビット列を左に指定したビット数だけシフトします。

空いた右側のビットは0で埋められ、2のべき乗倍の乗算に相当します。

using System;
class Program
{
    static void Main()
    {
        int value = 3; // 0b_0011
        int result = value << 2; // 2ビット左シフト
        Console.WriteLine($"value: {Convert.ToString(value, 2)}");
        Console.WriteLine($"value << 2: {Convert.ToString(result, 2)} (10進数: {result})");
    }
}
value: 11
value << 2: 1100 (10進数: 12)

左シフトは高速な乗算処理として使われます。

>> 算術右シフト

>>演算子は、ビット列を右に指定したビット数だけシフトします。

符号付き整数の場合は符号ビットを保持しながらシフトするため、負の数でも符号が維持されます。

using System;
class Program
{
    static void Main()
    {
        int value = -16; // 2の補数表現
        int result = value >> 2; // 2ビット右シフト
        Console.WriteLine($"value: {Convert.ToString(value, 2)}");
        Console.WriteLine($"value >> 2: {Convert.ToString(result, 2)} (10進数: {result})");
    }
}
value: -10000
value >> 2: -100 (10進数: -4)

算術右シフトは符号を保ったままビットを右にずらすため、負の数の除算に使われます。

>>> 論理右シフト

>>>演算子は、符号ビットに関係なくビットを右にシフトし、空いた左側のビットを0で埋めます。

C# 11以降で導入された符号なし右シフト演算子です。

using System;
class Program
{
    static void Main()
    {
        int value = -16; // 2の補数表現
        int result = value >>> 2; // 2ビット論理右シフト
        Console.WriteLine($"value: {Convert.ToString(value, 2)}");
        Console.WriteLine($"value >>> 2: {Convert.ToString(result, 2)} (10進数: {result})");
    }
}
value: -10000
value >>> 2: 1073741820 (10進数: 1073741820)

論理右シフトは符号を無視してビットを右にずらすため、符号なし整数の操作やビット列の操作に適しています。

条件演算子

三項演算子

?: 条件に応じた値選択

三項演算子?:は、条件式の結果に応じて2つの値のうちどちらかを選択する演算子です。

if-else文の簡潔な表現として使われます。

構文は以下の通りです。

条件式 ? 真の場合の値 : 偽の場合の値
using System;
class Program
{
    static void Main()
    {
        int score = 75;
        string result = score >= 60 ? "合格" : "不合格";
        Console.WriteLine($"スコア: {score} => {result}");
    }
}
スコア: 75 => 合格

この例では、scoreが60以上なら「合格」、そうでなければ「不合格」と表示します。

三項演算子は式として使えるため、変数への代入やメソッドの引数としても便利です。

null 合体

?? Null合体

??演算子は、左辺の値がnullでなければその値を返し、nullの場合は右辺の値を返します。

null許容型や参照型の値がnullかどうかを簡単に判定して代替値を設定できます。

using System;
class Program
{
    static void Main()
    {
        string? name = null;
        string displayName = name ?? "名無し";
        Console.WriteLine($"名前: {displayName}");
    }
}
名前: 名無し

この例では、namenullなので右辺の「名無し」が代入されます。

nameに値があればそのまま使われます。

??= Null合体代入

??=演算子は、左辺の変数がnullの場合に右辺の値を代入します。

nullでない場合は何もしません。

using System;
class Program
{
    static void Main()
    {
        string? message = null;
        message ??= "デフォルトメッセージ";
        Console.WriteLine(message);
        message ??= "新しいメッセージ"; // すでに値があるので代入されない
        Console.WriteLine(message);
    }
}
デフォルトメッセージ
デフォルトメッセージ

この演算子は、変数が未設定の場合に初期値を設定するのに便利です。

null 条件

?. Null条件アクセス

?.演算子は、左辺のオブジェクトがnullでなければメンバーにアクセスし、nullの場合は評価を中止してnullを返します。

NullReferenceExceptionを防ぐために使います。

using System;
class Person
{
    public string? Name { get; set; }
}
class Program
{
    static void Main()
    {
        Person? person = null;
        // personがnullなのでNameにはアクセスせずnullを返す
        string? name = person?.Name;
        Console.WriteLine(name == null ? "名前がありません" : name);
        person = new Person { Name = "太郎" };
        name = person?.Name;
        Console.WriteLine(name);
    }
}
名前がありません
太郎

このように、?.を使うと安全にメンバーアクセスができます。

?[] Null条件インデクサ

?[]演算子は、配列やコレクションのインデクサアクセスに対してnull条件を適用します。

左辺がnullの場合は評価を中止してnullを返します。

using System;
class Program
{
    static void Main()
    {
        string[]? fruits = null;
        // fruitsがnullなのでアクセスせずnullを返す
        string? firstFruit = fruits ?[0];
        Console.WriteLine(firstFruit == null ? "果物がありません" : firstFruit);
        fruits = new string[] { "りんご", "みかん" };
        firstFruit = fruits ?[0];
        Console.WriteLine(firstFruit);
    }
}
果物がありません
りんご

?[]は配列やリストなどのインデクサアクセス時にnullチェックを簡潔に書ける便利な演算子です。

型関連演算子

型検査

is 型判定

is演算子は、オブジェクトが指定した型と互換性があるかどうかを判定し、trueまたはfalseを返します。

型チェックに使われ、条件分岐や安全なキャストの前提条件としてよく利用されます。

using System;
class Program
{
    static void Main()
    {
        object obj = "Hello";
        if (obj is string)
        {
            Console.WriteLine("objはstring型です");
        }
        else
        {
            Console.WriteLine("objはstring型ではありません");
        }
    }
}
objはstring型です

この例では、objstring型であるためtrueとなり、メッセージが表示されます。

is not 否定型判定

is not演算子は、isの否定形で、オブジェクトが指定した型と互換性がない場合にtrueを返します。

using System;
class Program
{
    static void Main()
    {
        object obj = 123;
        if (obj is not string)
        {
            Console.WriteLine("objはstring型ではありません");
        }
        else
        {
            Console.WriteLine("objはstring型です");
        }
    }
}
objはstring型ではありません

型パターン

is パターンマッチ

C#のis演算子は、型判定だけでなくパターンマッチングとしても使えます。

変数にキャストした結果を代入しつつ判定できるため、コードが簡潔になります。

using System;
class Program
{
    static void Main()
    {
        object obj = 3.14;
        if (obj is double d)
        {
            Console.WriteLine($"objはdouble型で値は {d} です");
        }
        else
        {
            Console.WriteLine("objはdouble型ではありません");
        }
    }
}
objはdouble型で値は 3.14 です

この例では、objdouble型なら変数dにキャストされた値が代入され、そのまま利用できます。

switch 式パターン

switch式は、パターンマッチングを使って型や値に応じた処理を簡潔に書けます。

using System;
class Program
{
    static void Main()
    {
        object obj = 42;
        string result = obj switch
        {
            int i => $"整数: {i}",
            string s => $"文字列: {s}",
            null => "nullです",
            _ => "その他の型です"
        };
        Console.WriteLine(result);
    }
}
整数: 42

この例では、objの型に応じて異なる文字列を返しています。

switch式は複雑な条件分岐をシンプルに表現できます。

型変換

as 安全キャスト

as演算子は、オブジェクトを指定した型に安全にキャストします。

キャストに失敗した場合は例外を投げずにnullを返すため、例外処理を避けたい場合に便利です。

using System;
class Program
{
    static void Main()
    {
        object obj = "Hello";
        string? str = obj as string;
        if (str != null)
        {
            Console.WriteLine($"キャスト成功: {str}");
        }
        else
        {
            Console.WriteLine("キャスト失敗");
        }
    }
}
キャスト成功: Hello

asは参照型やnull許容型にのみ使えます。

値型には使えません。

(T) 明示的キャスト

明示的キャストは、(T)の形式で型変換を行います。

キャストに失敗するとInvalidCastExceptionが発生するため、確実に変換可能な場合に使います。

using System;
class Program
{
    static void Main()
    {
        object obj = 123;
        try
        {
            int i = (int)obj;
            Console.WriteLine($"キャスト成功: {i}");
        }
        catch (InvalidCastException)
        {
            Console.WriteLine("キャスト失敗");
        }
    }
}
キャスト成功: 123

安全性を考慮する場合はisasと組み合わせて使うことが多いです。

型情報

typeof 型オブジェクト取得

typeof演算子は、指定した型のSystem.Typeオブジェクトを取得します。

リフレクションや型情報の取得に使います。

using System;
class Program
{
    static void Main()
    {
        Type t1 = typeof(int);
        Type t2 = typeof(string);
        Console.WriteLine(t1.FullName);
        Console.WriteLine(t2.FullName);
    }
}
System.Int32
System.String

sizeof 型サイズ取得

sizeof演算子は、値型のメモリサイズ(バイト数)を取得します。

アンセーフコードでなくても使えますが、参照型には使えません。

using System;
class Program
{
    static void Main()
    {
        Console.WriteLine($"intのサイズ: {sizeof(int)} バイト");
        Console.WriteLine($"doubleのサイズ: {sizeof(double)} バイト");
    }
}
intのサイズ: 4 バイト
doubleのサイズ: 8 バイト

sizeofはメモリ管理やパフォーマンスチューニングの際に役立ちます。

オブジェクト生成とメンバーアクセス

new オブジェクト生成

new演算子は、クラスや構造体の新しいインスタンスを生成するために使います。

メモリ上にオブジェクトを確保し、コンストラクターを呼び出して初期化します。

using System;
class Person
{
    public string Name { get; set; }
    public Person(string name)
    {
        Name = name;
    }
}
class Program
{
    static void Main()
    {
        Person person = new Person("太郎"); // newでPersonオブジェクトを生成
        Console.WriteLine($"名前: {person.Name}");
    }
}
名前: 太郎

newは配列や匿名型、デリゲートの生成にも使われます。

例えば配列生成はnew int[5]のように書きます。

. メンバーアクセス

.演算子は、オブジェクトや構造体のメンバー(フィールド、プロパティ、メソッド)にアクセスするために使います。

using System;
class Car
{
    public string Model { get; set; }
    public void Drive()
    {
        Console.WriteLine($"{Model}を運転しています");
    }
}
class Program
{
    static void Main()
    {
        Car car = new Car { Model = "トヨタ" };
        Console.WriteLine(car.Model); // プロパティアクセス
        car.Drive();                  // メソッド呼び出し
    }
}
トヨタ
トヨタを運転しています

.は名前空間の区切りや静的メンバーのアクセスにも使われます。

:: 名前空間または型限定

::演算子は、名前空間や型の限定に使います。

主に名前の衝突を避けるために使用され、C#ではglobal::の形でグローバル名前空間を明示する場合に使われます。

using System;
namespace MyNamespace
{
    class Console
    {
        public static void WriteLine(string message)
        {
            System.Console.WriteLine("MyNamespace.Console: " + message);
        }
    }
}
class Program
{
    static void Main()
    {
        MyNamespace.Console.WriteLine("こんにちは");      // 独自のConsoleクラスを呼ぶ
        global::System.Console.WriteLine("Hello World"); // グローバルのSystem.Consoleを呼ぶ
    }
}
MyNamespace.Console: こんにちは
Hello World

このようにglobal::を使うことで、名前空間の衝突を回避できます。

-> ポインタメンバーアクセス

->演算子は、アンセーフコード内でポインタが指す構造体やクラスのメンバーにアクセスするために使います。

ポインタ変数を通じてメンバーにアクセスする際に便利です。

using System;
unsafe class Program
{
    struct Point
    {
        public int X;
        public int Y;
    }
    static void Main()
    {
        Point point = new Point { X = 10, Y = 20 };
        Point* p = &point;
        Console.WriteLine($"X: {p->X}, Y: {p->Y}"); // ポインタ経由でメンバーアクセス
    }
}
X: 10, Y: 20

->はポインタのメンバーアクセス専用で、通常の参照型や値型のメンバーアクセスには.を使います。

アンセーフコードを使う場合はunsafeキーワードが必要です。

ラムダ・デリゲート関連演算子

=> ラムダ演算子

ラムダ演算子=>は、匿名関数(ラムダ式)を定義するために使います。

メソッドのように名前を持たず、簡潔に関数を表現できるため、LINQやイベント処理、デリゲートの引数としてよく利用されます。

式ラムダ

式ラムダは、単一の式を返す簡潔なラムダ式です。

式の評価結果が戻り値となります。

using System;
class Program
{
    static void Main()
    {
        Func<int, int> square = x => x * x; // xを受け取りxの2乗を返す式ラムダ
        int result = square(5);
        Console.WriteLine($"5の2乗は {result}");
    }
}
5の2乗は 25

この例では、x => x * xが式ラムダで、引数xの2乗を返します。

式ラムダはシンプルな処理に適しています。

ステートメントラムダ

ステートメントラムダは、複数の文を含むラムダ式で、波括弧{}で囲みます。

戻り値がある場合はreturn文を使います。

using System;
class Program
{
    static void Main()
    {
        Func<int, int> factorial = n =>
        {
            int result = 1;
            for (int i = 2; i <= n; i++)
            {
                result *= i;
            }
            return result;
        };
        Console.WriteLine($"5の階乗は {factorial(5)}");
    }
}
5の階乗は 120

この例では、nの階乗を計算するステートメントラムダを定義しています。

複雑な処理や複数の文が必要な場合に使います。

delegate 無名メソッド

delegateキーワードを使うと、名前のない無名メソッドを定義できます。

ラムダ式が登場する前は無名メソッドの主な書き方でしたが、現在も互換性のために使われます。

using System;
class Program
{
    static void Main()
    {
        Func<int, int> square = delegate (int x)
        {
            return x * x;
        };
        Console.WriteLine($"6の2乗は {square(6)}");
    }
}
6の2乗は 36

無名メソッドはラムダ式と似ていますが、ラムダ式の方がより簡潔で柔軟に書けるため、現在はラムダ式が主流です。

ただし、delegateは特定のシナリオで明示的に使われることがあります。

クエリ演算子 (LINQ)

基本キーワード

from

fromキーワードは、LINQクエリの開始点として使います。

データソースから要素を取り出し、クエリの範囲変数を定義します。

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        int[] numbers = { 1, 2, 3, 4, 5 };
        var query = from n in numbers
                    select n;
        foreach (var num in query)
        {
            Console.WriteLine(num);
        }
    }
}
1
2
3
4
5

この例では、numbers配列の各要素をnとして取り出し、そのまま選択しています。

where

whereキーワードは、条件に合致する要素だけを抽出するフィルターとして使います。

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        int[] numbers = { 1, 2, 3, 4, 5 };
        var evens = from n in numbers
                    where n % 2 == 0
                    select n;
        foreach (var num in evens)
        {
            Console.WriteLine(num);
        }
    }
}
2
4

この例では、偶数だけを抽出しています。

select

selectキーワードは、クエリの結果として返す要素を指定します。

変換や投影に使われます。

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        string[] words = { "apple", "banana", "cherry" };
        var lengths = from w in words
                      select w.Length;
        foreach (var len in lengths)
        {
            Console.WriteLine(len);
        }
    }
}
5
6
6

この例では、文字列の長さを選択しています。

拡張キーワード

let

letキーワードは、中間結果を変数に代入して再利用できるようにします。

クエリ内で計算結果を保持したい場合に便利です。

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        string[] words = { "apple", "banana", "cherry" };
        var query = from w in words
                    let upper = w.ToUpper()
                    select new { Original = w, Upper = upper };
        foreach (var item in query)
        {
            Console.WriteLine($"{item.Original} -> {item.Upper}");
        }
    }
}
apple -> APPLE
banana -> BANANA
cherry -> CHERRY

into

intoキーワードは、クエリの途中結果を新しいクエリのソースとして再利用できます。

複雑なクエリの分割や連結に使います。

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6 };
        var query = from n in numbers
                    where n % 2 == 0
                    select n
                    into evenNumbers
                    where evenNumbers > 3
                    select evenNumbers;
        foreach (var num in query)
        {
            Console.WriteLine(num);
        }
    }
}
4
6

join

joinキーワードは、2つのシーケンスをキーで結合します。

SQLのJOINに相当し、関連するデータを結びつけるのに使います。

using System;
using System.Linq;
class Program
{
    class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    class Score
    {
        public int StudentId { get; set; }
        public int Value { get; set; }
    }
    static void Main()
    {
        var students = new[]
        {
            new Student { Id = 1, Name = "太郎" },
            new Student { Id = 2, Name = "花子" }
        };
        var scores = new[]
        {
            new Score { StudentId = 1, Value = 80 },
            new Score { StudentId = 2, Value = 90 }
        };
        var query = from s in students
                    join sc in scores on s.Id equals sc.StudentId
                    select new { s.Name, sc.Value };
        foreach (var item in query)
        {
            Console.WriteLine($"{item.Name} の点数は {item.Value}");
        }
    }
}
太郎 の点数は 80
花子 の点数は 90

group

groupキーワードは、要素をキーでグループ化します。

グループごとに集計や処理を行う際に使います。

using System;
using System.Linq;
class Program
{
    class Person
    {
        public string Name { get; set; }
        public string City { get; set; }
    }
    static void Main()
    {
        var people = new[]
        {
            new Person { Name = "太郎", City = "東京" },
            new Person { Name = "花子", City = "大阪" },
            new Person { Name = "次郎", City = "東京" }
        };
        var groups = from p in people
                     group p by p.City into g
                     select new { City = g.Key, Count = g.Count() };
        foreach (var group in groups)
        {
            Console.WriteLine($"{group.City} の人数: {group.Count}");
        }
    }
}
東京 の人数: 2
大阪 の人数: 1

パターンマッチング演算子

when フィルター

whenキーワードは、パターンマッチングの条件に追加のフィルターをかけるために使います。

switch文やcase節で特定の条件を満たす場合のみマッチさせたいときに便利です。

using System;
class Program
{
    static void Main()
    {
        object obj = 15;
        switch (obj)
        {
            case int n when n > 10:
                Console.WriteLine($"10より大きい整数: {n}");
                break;
            case int n:
                Console.WriteLine($"10以下の整数: {n}");
                break;
            default:
                Console.WriteLine("整数ではありません");
                break;
        }
    }
}
10より大きい整数: 15

この例では、objが整数でかつ10より大きい場合に最初のcaseがマッチし、それ以外の整数は次のcaseで処理されます。

_ ディスカード

_はディスカード(無視する値)を表すパターンで、値を受け取らずにマッチだけさせたい場合に使います。

変数名を使わずにパターンマッチングの一部として利用可能です。

using System;
class Program
{
    static void Main()
    {
        object obj = "hello";
        if (obj is int _)
        {
            Console.WriteLine("整数です");
        }
        else
        {
            Console.WriteLine("整数ではありません");
        }
    }
}
整数ではありません

また、switch文のdefaultの代わりにcase _:を使うこともできます。

.. Range

..演算子は、範囲(Range)を表すパターンマッチングやスライス操作に使われます。

配列や文字列の部分取得や、パターンマッチングで範囲を指定する際に便利です。

using System;
class Program
{
    static void Main()
    {
        int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        // 配列の3番目から6番目までを取得(インデックス3から6の範囲)
        int[] slice = numbers[3..7];
        Console.WriteLine("スライスした配列:");
        foreach (var num in slice)
        {
            Console.WriteLine(num);
        }
    }
}
スライスした配列:
3
4
5
6

パターンマッチングでも..を使って範囲を指定できます。

using System;
class Program
{
    static void Main()
    {
        int[] arr = { 1, 2, 3, 4, 5 };
        if (arr is [1, 2, .. var rest])
        {
            Console.WriteLine("最初の2つの要素は1と2です");
            Console.WriteLine("残りの要素:");
            foreach (var n in rest)
            {
                Console.WriteLine(n);
            }
        }
    }
}
最初の2つの要素は1と2です
残りの要素:
3
4
5

このように..は範囲指定や残りの要素を表すのに使われ、パターンマッチングの表現力を高めます。

Null許容型と演算子

? Null許容修飾子

?は型の後ろに付けることで、その型がnullを許容することを示します。

通常、値型(intboolなど)はnullを取れませんが、int?bool?のように書くとnullも扱えるようになります。

これにより、データベースの値や未設定の状態を表現しやすくなります。

using System;
class Program
{
    static void Main()
    {
        int? nullableInt = null; // null許容型のint
        Console.WriteLine($"nullableIntの値: {nullableInt}");
        nullableInt = 100;
        Console.WriteLine($"nullableIntの値: {nullableInt}");
        if (nullableInt.HasValue)
        {
            Console.WriteLine($"値は {nullableInt.Value} です");
        }
        else
        {
            Console.WriteLine("値はnullです");
        }
    }
}
nullableIntの値:
nullableIntの値: 100
値は 100 です

nullの状態かどうかはHasValueプロパティで判定でき、値を取り出すにはValueプロパティを使います。

nullの場合にアクセスすると例外が発生するため注意が必要です。

! Null抑制演算子

!はNull抑制演算子(null-forgiving operator)と呼ばれ、コンパイラーに対して「この変数はnullではない」と明示的に伝えるために使います。

主にNullable Reference Types(NRT)機能と組み合わせて、警告を抑制する目的で利用されます。

using System;
class Program
{
    static void Main()
    {
        string? nullableString = "こんにちは";
        // コンパイラーはnullableStringがnullの可能性を警告するが、
        // !を付けることでnullでないと断言できる
        int length = nullableString!.Length;
        Console.WriteLine($"文字列の長さ: {length}");
    }
}
文字列の長さ: 5

この例では、nullableStringnullでないことを開発者が保証しているため、!を使ってコンパイラーの警告を回避しています。

ただし、実際にnullだった場合は実行時にNullReferenceExceptionが発生するため、使用は慎重に行う必要があります。

クロスカッティング演算子

checked オーバーフロー検出

checked演算子は、算術演算や型変換でオーバーフローが発生した場合に例外をスローして検出します。

デフォルトではオーバーフローは無視されて値がラップアラウンド(巻き戻り)しますが、checkedを使うことで安全に検出可能です。

using System;
class Program
{
    static void Main()
    {
        try
        {
            int max = int.MaxValue;
            int result = checked(max + 1); // オーバーフロー検出
            Console.WriteLine($"結果: {result}");
        }
        catch (OverflowException)
        {
            Console.WriteLine("オーバーフローが検出されました");
        }
    }
}
オーバーフローが検出されました

この例では、int.MaxValueに1を加えるとオーバーフローしますが、checkedにより例外が発生し安全に検出できます。

unchecked オーバーフロー無視

unchecked演算子は、オーバーフローが発生しても例外をスローせず、値をラップアラウンドさせます。

checkedの逆で、パフォーマンス重視や意図的にオーバーフローを許容する場合に使います。

using System;
class Program
{
    static void Main()
    {
        int max = int.MaxValue;
        int result = unchecked(max + 1); // オーバーフローを無視
        Console.WriteLine($"結果: {result}");
    }
}
結果: -2147483648

この例では、int.MaxValue + 1uncheckedによりラップアラウンドし、最小値-2147483648になります。

stackalloc スタックメモリ割当

stackalloc演算子は、スタック領域に固定長のメモリを割り当てるために使います。

主にアンセーフコード内で使われ、ヒープ割当てより高速でガベージコレクションの影響を受けません。

主にバッファやパフォーマンスクリティカルな処理に利用されます。

using System;
unsafe class Program
{
    static void Main()
    {
        int length = 5;
        int* buffer = stackalloc int[length]; // スタックにint配列を割り当て
        for (int i = 0; i < length; i++)
        {
            buffer[i] = i * 10;
        }
        for (int i = 0; i < length; i++)
        {
            Console.WriteLine(buffer[i]);
        }
    }
}
0
10
20
30
40

stackallocunsafeコンテキスト内でのみ使用可能で、割り当てたメモリはメソッドのスコープを抜けると自動的に解放されます。

メモリ管理の負担が少なく高速ですが、サイズが大きすぎるとスタックオーバーフローの原因になるため注意が必要です。

安全なコード用演算子

* ポインタ

*演算子は、ポインタ型の変数を宣言したり、ポインタが指すメモリの値にアクセスするために使います。

C#ではアンセーフコード内でのみ使用可能で、低レベルのメモリ操作を行う際に利用されます。

using System;
unsafe class Program
{
    static void Main()
    {
        int value = 42;
        int* pointer = &value; // ポインタにvalueのアドレスを代入
        Console.WriteLine($"valueの値: {value}");
        Console.WriteLine($"ポインタが指す値: {*pointer}");
        *pointer = 100; // ポインタ経由で値を書き換え
        Console.WriteLine($"書き換え後のvalueの値: {value}");
    }
}
valueの値: 42
ポインタが指す値: 42
書き換え後のvalueの値: 100

この例では、*pointerでポインタが指すメモリの値を読み書きしています。

ポインタ操作はメモリの直接操作が可能ですが、誤用するとメモリ破壊やセキュリティ問題を引き起こすため注意が必要です。

& アドレス取得

&演算子は、変数のメモリアドレスを取得してポインタとして扱うために使います。

こちらもアンセーフコード内でのみ使用可能です。

using System;
unsafe class Program
{
    static void Main()
    {
        int number = 123;
        int* ptr = &number; // numberのアドレスを取得
        Console.WriteLine($"numberのアドレス: {(IntPtr)ptr}");
        Console.WriteLine($"numberの値: {number}");
    }
}
numberのアドレス: 0x7ffee3b8a8ac
numberの値: 123

アドレスは環境によって異なりますが、&で変数のメモリ位置を取得できることがわかります。

fixed ピン留め

fixed文は、ガベージコレクターによるメモリ移動を防ぐために、参照型のオブジェクトのメモリを固定(ピン留め)します。

これにより、アンセーフコードでポインタを安全に使えます。

主に配列や文字列のポインタ操作に使います。

using System;
unsafe class Program
{
    static void Main()
    {
        byte[] data = { 10, 20, 30, 40, 50 };
        fixed (byte* p = data) // data配列のメモリを固定
        {
            for (int i = 0; i < data.Length; i++)
            {
                Console.WriteLine($"data[{i}] = {*(p + i)}");
            }
        }
    }
}
data[0] = 10
data[1] = 20
data[2] = 30
data[3] = 40
data[4] = 50

fixedを使うことで、配列dataのメモリがGCによって移動されることなく、ポインタpを使って安全にアクセスできます。

fixedブロックを抜けるとピン留めは解除されます。

その他の演算子

, カンマ演算子

C#では、カンマ演算子は主に複数の式を一度に評価する際に使われます。

例えば、forループの初期化や更新部分で複数の変数を同時に操作する場合に便利です。

ただし、C#の式の中でカンマ演算子を使うことは限定的で、主に文の区切りとして使われます。

using System;
class Program
{
    static void Main()
    {
        int x = 0, y = 0;
        for (int i = 0, j = 10; i < 5; i++, j--)
        {
            x += i;
            y += j;
            Console.WriteLine($"i={i}, j={j}, x={x}, y={y}");
        }
    }
}
i=0, j=9, x=0, y=9
i=1, j=8, x=1, y=17
i=2, j=7, x=3, y=24
i=3, j=6, x=6, y=30
i=4, j=5, x=10, y=35

この例では、forループの初期化と更新部分でカンマを使い複数の変数を同時に操作しています。

() 丸括弧による優先順位制御

丸括弧()は、演算子の優先順位を明示的に制御するために使います。

複雑な式の中で計算順序を指定し、意図した通りの結果を得るために重要です。

using System;
class Program
{
    static void Main()
    {
        int result1 = 2 + 3 * 4;          // 乗算が先に評価される
        int result2 = (2 + 3) * 4;        // 丸括弧で加算を先に評価
        Console.WriteLine($"2 + 3 * 4 = {result1}");
        Console.WriteLine($"(2 + 3) * 4 = {result2}");
    }
}
2 + 3 * 4 = 14
(2 + 3) * 4 = 20

丸括弧を使うことで、計算の順序を変えられ、意図しない計算ミスを防げます。

with レコード複製

with演算子は、C# 9.0で導入されたレコード型の新しいインスタンスを作成する際に、既存のレコードの値をコピーしつつ一部のプロパティだけを変更するために使います。

イミュータブルなデータ構造を扱う際に便利です。

using System;
record Person(string Name, int Age);
class Program
{
    static void Main()
    {
        var person1 = new Person("太郎", 30);
        var person2 = person1 with { Age = 31 }; // Ageだけ変更して新しいインスタンスを作成
        Console.WriteLine(person1);
        Console.WriteLine(person2);
    }
}
Person { Name = 太郎, Age = 30 }
Person { Name = 太郎, Age = 31 }

withを使うことで、元のオブジェクトを変更せずに新しいオブジェクトを簡単に作成できます。

<- 変数代入 in query comprehension

<-演算子は、LINQのクエリ式内で変数に値を代入するために使われます。

特にfrom句でコレクションの要素を変数に割り当てる際に使われ、クエリの範囲変数を定義します。

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        int[] numbers = { 1, 2, 3, 4, 5 };
        var query = from n in numbers
                    where n % 2 == 0
                    select n;
        foreach (var num in query)
        {
            Console.WriteLine(num);
        }
    }
}
2
4

ここでn in numbersの部分は内部的にn <- numbersのように変数nに要素を代入しているイメージです。

C#の構文上はinを使いますが、LINQのクエリ式の基礎的な動作として理解されます。

演算子の優先順位

演算子の優先順位は、複数の演算子が混在する式において、どの演算子が先に評価されるかを決定します。

正しい計算結果を得るために、優先順位と結合規則を理解することが重要です。

優先順位表

以下はC#の主な演算子の優先順位を高い順にまとめた表です。

上にある演算子ほど優先的に評価されます。

優先順位演算子例説明
1()グループ化(丸括弧)
2++(前置)、--(前置)、+(単項)、-(単項)、!~単項演算子
3*/%乗算、除算、剰余
4+-加算、減算
5<<>>ビットシフト
6<<=>>=比較演算子
7==!=等価・非等価
8&(ビットAND)ビットAND
9^(ビットXOR)ビットXOR
10|(ビットOR)ビットOR
11&&論理AND(短絡評価)
12||論理OR(短絡評価)
13?:三項条件演算子
14=+=-=*=/=など代入演算子
15,カンマ演算子

この表は代表的な演算子を抜粋しています。

詳細は公式ドキュメントを参照してください。

結合規則

結合規則は、同じ優先順位の演算子が複数ある場合に、どちらから評価するかを決めるルールです。

C#の演算子の結合規則は以下の通りです。

  • 左結合(Left-to-Right)

多くの二項演算子は左結合です。

つまり、左から右へ順に評価されます。

例:a - b - c(a - b) - c と評価されます。

  • 右結合(Right-to-Left)

代入演算子や三項演算子は右結合です。

つまり、右から左へ評価されます。

例:a = b = ca = (b = c) と評価されます。

  • 単項演算子

単項演算子はオペランドの前または後ろに付き、オペランドに対して直接作用します。

前置演算子は右結合、後置演算子は左結合です。

例:優先順位と結合規則の影響

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = 5;
        int c = 2;
        int result = a - b + c; // 左結合で (a - b) + c = 7
        Console.WriteLine(result);
        int x = 1;
        int y = 2;
        int z = 3;
        x = y = z; // 右結合で x = (y = z)
        Console.WriteLine($"x={x}, y={y}, z={z}");
    }
}
7
x=3, y=3, z=3

このように、優先順位と結合規則を理解していないと、意図しない計算結果になることがあります。

複雑な式では丸括弧()を使って明示的に評価順序を指定することをおすすめします。

まとめ

この記事では、C#の演算子を幅広く解説しました。

算術演算子や代入演算子、比較・論理演算子からビット演算子、条件演算子、型関連演算子まで、基本から応用まで網羅しています。

さらに、ラムダ式やLINQのクエリ演算子、パターンマッチング、null許容型、ポインタ操作などの特殊な演算子も紹介しました。

演算子の優先順位や結合規則も理解することで、複雑な式の正しい評価が可能になります。

これらを活用すれば、C#でのプログラミングがより効率的かつ安全に行えます。

関連記事

Back to top button