2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

なんとなく使っていた C# の修飾子を整理する(readonly / const / ref / in / out)

Last updated at Posted at 2026-01-18

はじめに

日々、プログラムを書く中で、変数を宣言しない日はほぼ無いと思います。
一方で、readonlyconst などの修飾子について、

  • 名前は知っている
  • なんとなく使ったことはある
  • でも「なぜ使うのか」を説明できない

という状態になっていないでしょうか。

実際のところ、型さえ合っていれば、

int value = 10;

のような宣言でプロブラムは大抵は動きます
そのため、気づくと同じような宣言ばかりを書いてしまいがちです。
(少なくとも、私はそうでした)

この記事では、
使用頻度は低いが、理解すると設計の質が上がる修飾子を中心に整理してみます。

前提として知っておきたい修飾子

アクセス修飾子

アクセス範囲の概要のみを整理します。

修飾子 アクセス範囲
public どこからでもアクセス可能
private クラス内部のみ(最も安全、迷ったらこれ)
protected 派生クラスからのみアクセス可能
internal 同一アセンブリ内のみアクセス可能
protected internal 同一アセンブリ または 派生クラス
private protected 同一アセンブリ かつ 派生クラス

継承用修飾子

クラスやメンバーの 継承・上書き可否 を制御する修飾子です。

クラスに付ける修飾子

修飾子 意味
abstract インスタンス化できない。派生クラスでの実装を強制
sealed 継承を禁止する

メンバーに付ける修飾子

修飾子 意味
virtual 派生クラスでの上書きを許可
abstract 派生クラスでの実装を強制
override 基底クラスの virtual / abstract メンバーを上書き
sealed override をこれ以上許可しない
(修飾子なし) 継承されるが override は不可(暗黙的に上書き不可)

変更可否・意味を表す修飾子(本題)

readonly

readonly は、変数の値が実行中に変更されないことを保証する修飾子です。

代入できるタイミングは、次の2箇所のみです。

  • 宣言時
  • コンストラクタ内
public class Sample {
    // OK(宣言時)
    readonly int value = 1;

    public Sample() {
        // OK(コンストラクタ内)
        value = 10;
    }

    public void Func() {
        // NG(実行中の再代入は不可)
        value = 20;
    }
}

readonly と参照型の注意点

readonly再代入を禁止する 修飾子であり、
オブジェクトの 状態そのものを不変にするものではありません

readonly List<int> list = new();

list.Add(1);  // OK
list = new(); // コンパイルエラー

const

constは、設定値や定数を表し、コンパイル時に値が決定される修飾子です。
値そのものがコード中に埋め込まれるため、
実行時に決まる値や外部から与えられる値を扱うことはできません。

public class Sample {
    // OK
    const int value1 = 10;

    // OK
    const int value2 = value1;

    public void XXX( int x ) {
        // OK
        const int value3 = 10;

        // OK
        const int value4 = value1;

        // NG(コンパイル時に決定されないため)
        const int value5 = x;
    }
}

const と readonly の比較

項目 const readonly
値の決定 コンパイル時 実行時(宣言時 or コンストラクタ)
初期化可能な場所 宣言時のみ 宣言時 / コンストラクタ
プリミティブ / string / enum 制限なし
再代入 不可 不可

static

static は、フィールドやメソッドを クラスに1つだけ存在させる 修飾子です。
インスタンスを生成しなくてもアクセスできます。

static int count;

static は便利な反面、状態を持たせると設計が壊れやすくなります。
特に、複数スレッドからアクセスされる場合は注意が必要です。

static readonly int MaxRetryCount = 3;

このように、staticreadonly を組み合わせることで、
「グローバルに共有されるが変更されない値」を安全に表現できます。

volatile(使用率低・重要)

volatile は、複数スレッド間での値の可視性を保証する 修飾子です。

volatile bool _running;

volatile を付けることで、
あるスレッドで変更された値が、他のスレッドからも必ず最新の状態で参照されます。
ただし、排他制御を行うものではありません。

そのため、CancellationToken など、
より安全で明確な仕組みが使われることが多いです。

ref / in / out

ref / in / out は、引数の渡し方(参照の扱い) を制御する修飾子です。

修飾子 役割 主な用途
ref 入出力 値を更新する
in 入力 コピー回避・意図明示
out 出力 結果を返す

ref(入出力)

ref は、メソッド内で変更した値が呼び出し元に反映される修飾子です。

注意点

  • 呼び出し側・呼び出され側の両方に ref が必要
  • 状態変更が見えにくくなるため多用は非推奨
void Increment(ref int value)
{
    value++;
}

int x = 10;
Increment(ref x);

Console.WriteLine(x); // 11

in(読み取り専用参照)

in は、読み取り専用の参照渡しを行うための修飾子です。
ref と同様に参照渡しですが、メソッド内での変更が禁止されます。

inref readonly と同じ意味になります。

void Print(in int value)
{
    value++; // ❌ コンパイルエラー
    Console.WriteLine(value);
}

int x = 10;
Print(in x);

in を使う場面

  • コピーを避けたい
  • 「変更しない」ことを明示できる

int 型など小さな値型では、引数渡し時にコピーが発生しても
パフォーマンス上の問題になることはほとんどありません。

一方、struct 型(複数の値をまとめた値型)などの場合、引数渡し時にコピーが発生すると、
このコピーコストが無視できなくなります。

そのような場合に in を使って参照渡しにすることで、
パフォーマンス改善につながる場面があります。

out(出力専用)

out は、メソッドから呼び出し元へ値を返すための修飾子です。
引数として渡しますが、役割は「入力」ではなく 出力専用 になります。

特徴

  • メソッド内で 必ず代入しなければならない
  • メソッドの戻り値とは別に、追加の結果を返せる

基本例

void SetValue(out int x)
{
    x = 10;
}

SetValue(out int value);
Console.WriteLine(value); // 10

実務よりの例

bool TryCreateValue(string text, out int value)
{
    if (int.TryParse(test, out int v))
    {
        value = v;
        return true;
    }
    
    return false;
}

if (TryCreateValue("123", out int value))
{
    Console.WriteLine(value); // 123
}

タプル型 との比較

C# 7.0以降では、戻り値にタプル型が使えるようになり、
複数値を返すことが容易になりました。

そのため、先程の「実務よりの例」は以下のように記載することもできます。

(bool, int) TryCreateValue(string text)
{
    if (int.TryParse(text, out int v))
    {
        return (true, v);
    }
    
    return (false, 0);
}

var (success, value) = TryCreateValue("123");

if (success)
{
    Console.WriteLine(value); // 123
}

タプル型の登場により、out を使う場面は減りましたが、

  • Tryパターンとの相性の良さ
  • .NET 標準 APIが大量に out を使用

により、今もまだ使う場面はあります。

最後に

readonlyconst などの修飾子は使用しなくても
プログラムを動作させることは可能です。

しかし、しっかりと修飾子を使用することで、
保守性やコードの読みやすさの向上が期待できます。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?