bucket-sort logo bucket-sort

プログラミングとインフラエンジニアリングの覚え書き

  • Posts
  • About
  • Contact
  1. Home
  2. All Posts
  3. [C#] フィールドを整理する — const・readonly・static・volatile の使い分け

[C#] フィールドを整理する — const・readonly・static・volatile の使い分け

Apr 23, 2026 C# , .NET bucket-sort

C# の**フィールド(Field)**は、クラスや struct が持つ変数です。プロパティと異なりアクセサ(get / set)を持たず、値をそのまま格納します。

公開メンバーにはプロパティを使うのが C# の慣例ですが、private / protected なデータ保持や定数の定義にはフィールドが欠かせません。本記事ではフィールドの種類と修飾子を整理します。

インスタンスフィールド

各インスタンスごとに独立した値を持つ、もっとも基本的なフィールドです。

public class Person
{
    private string _name;
    private int _age;

    public Person(string name, int age)
    {
        _name = name;
        _age = age;
    }
}

フィールド初期化子

フィールドは宣言時に初期値を設定できます。コンストラクタの実行前に評価されます。

public class Config
{
    private int _retryCount = 3;
    private string _locale = "ja-JP";
    private List<string> _errors = new();
}

初期化子はコンストラクタの base() 呼び出しよりも先に実行されます。実行順序は次のとおりです。

  1. フィールド初期化子(宣言順)
  2. 基底クラスのコンストラクタ(base())
  3. 自クラスのコンストラクタ本体
public class Base
{
    public Base() => Console.WriteLine("2. Base コンストラクタ");
}

public class Derived : Base
{
    private int _value = Log("1. フィールド初期化子");

    public Derived() => Console.WriteLine("3. Derived コンストラクタ");

    private static int Log(string msg) { Console.WriteLine(msg); return 0; }
}

// 出力:
// 1. フィールド初期化子
// 2. Base コンストラクタ
// 3. Derived コンストラクタ

静的フィールド(static field)

static を付けると、インスタンスではなく型に属するフィールドになります。全インスタンスで共有されます。

public class Counter
{
    private static int _count = 0;

    public Counter()
    {
        _count++;
    }

    public static int Count => _count;
}

var a = new Counter();
var b = new Counter();
Console.WriteLine(Counter.Count); // 2

静的フィールドの初期化は、その型に最初にアクセスした時点で一度だけ実行されます。明示的な初期化が必要な場合は静的コンストラクタを使います。

public class AppConfig
{
    private static readonly string _version;

    static AppConfig()
    {
        // 静的コンストラクタで初期化
        _version = LoadVersionFromFile();
    }

    private static string LoadVersionFromFile() => "1.0.0";
}

const(コンパイル時定数)

const はコンパイル時に値が確定する定数を定義します。

public class MathConstants
{
    public const double Pi = 3.14159265358979;
    public const int MaxRetry = 5;
    public const string DefaultLocale = "ja-JP";
}

Console.WriteLine(MathConstants.Pi);      // 3.14159265358979
Console.WriteLine(MathConstants.MaxRetry); // 5

const の特徴

  • コンパイル時に値が埋め込まれる — 呼び出し側のアセンブリに値がコピーされる
  • 暗黙的に static — インスタンスからはアクセスできない
  • 使える型はプリミティブ型(int、double、bool など)、string、enum、null に限られる
  • new を使う型(DateTime、配列、クラスなど)は使えない
public class Constants
{
    public const int MaxSize = 100;           // OK
    public const string AppName = "MyApp";    // OK
    public const double Rate = MaxSize * 0.1; // OK(const 同士の演算は可能)

    // public const DateTime Epoch = new(2000, 1, 1); // コンパイルエラー
    // public const int[] Values = { 1, 2, 3 };       // コンパイルエラー
}

コンパイル時埋め込みの注意点

const の値は参照先ではなく呼び出し側のアセンブリに直接埋め込まれます。そのため、ライブラリの const を変更した場合、呼び出し側も再コンパイルしないと古い値が使われ続けます。

// Library.dll
public class Settings
{
    public const int Timeout = 30; // v1.0
}

// App.exe(Library.dll を参照)
Console.WriteLine(Settings.Timeout); // コンパイル時に「30」が埋め込まれる

Settings.Timeout を 60 に変更して Library.dll だけ再ビルドしても、App.exe は再コンパイルしない限り 30 を表示し続けます。この問題を避けるには static readonly を使います。

readonly(実行時定数)

readonly フィールドは、宣言時またはコンストラクタでのみ値を設定できるフィールドです。以降は変更できません。

public class Circle
{
    private readonly double _radius;

    public Circle(double radius)
    {
        _radius = radius; // コンストラクタ内は OK
    }

    public double Area()
    {
        // _radius = 10; // コンパイルエラー(コンストラクタ外での変更は不可)
        return Math.PI * _radius * _radius;
    }
}

readonly の特徴

  • 実行時に値が決まる — new、メソッド呼び出し、構成ファイルの読み取り結果なども使える
  • 宣言時の初期化子またはコンストラクタ内でのみ設定可能
  • 任意の型に使える(const と異なり DateTime や配列も可)
public class AppConfig
{
    private readonly DateTime _startedAt = DateTime.UtcNow;     // OK
    private readonly int[] _defaultPorts = { 80, 443, 8080 };   // OK
    private readonly HttpClient _client = new();                 // OK
}

readonly は「参照」が不変

参照型の readonly フィールドは、参照先の変更(再代入)は禁止されますが、参照先オブジェクトの中身の変更は防げません。

public class Team
{
    private readonly List<string> _members = new();

    public void AddMember(string name)
    {
        _members.Add(name); // OK(リストの中身を変更するのは許可される)
    }

    public void Reset()
    {
        // _members = new List<string>(); // コンパイルエラー(参照の再代入は不可)
        _members.Clear();                 // OK(中身のクリアは許可される)
    }
}

中身も含めて不変にしたい場合は、IReadOnlyList<T> や ImmutableArray<T> などの不変コレクションを使います。

const vs readonly vs static readonly

3 つの「変更不可」なフィールドの違いを整理します。

public class Comparison
{
    // コンパイル時定数 — 値がアセンブリに埋め込まれる
    public const int CompileTimeConst = 100;

    // インスタンスごとの実行時定数
    private readonly DateTime _createdAt = DateTime.UtcNow;

    // 型レベルの実行時定数(全インスタンスで共有)
    public static readonly DateTime AppStartedAt = DateTime.UtcNow;
}
特徴 const readonly static readonly
値の決定タイミング コンパイル時 実行時 実行時
暗黙の static ✅ ❌ ✅(明示)
使える型 プリミティブ / string / enum / null 任意 任意
設定可能な場所 宣言のみ 宣言 / コンストラクタ 宣言 / 静的コンストラクタ
アセンブリ間の影響 値が埋め込まれる(要再コンパイル) 参照経由(影響なし) 参照経由(影響なし)
パフォーマンス 最速(リテラル扱い) 通常のフィールドアクセス 通常のフィールドアクセス

使い分けの目安

// 絶対に変わらないリテラル値 → const
public const double Pi = 3.14159265358979;
public const int BitsPerByte = 8;

// 実行時に決まる or 別アセンブリから参照される → static readonly
public static readonly DateTime AppStartedAt = DateTime.UtcNow;
public static readonly string Version = Assembly.GetExecutingAssembly()
    .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "0.0.0";

// インスタンスごとに異なる不変値 → readonly
public class Order
{
    private readonly string _orderId;
    private readonly DateTime _createdAt;

    public Order(string orderId)
    {
        _orderId = orderId;
        _createdAt = DateTime.UtcNow;
    }
}

volatile(揮発性フィールド)

volatile は、複数のスレッドからアクセスされるフィールドに対して、コンパイラやCPUの最適化による並べ替え・キャッシュを抑止する修飾子です。

public class Worker
{
    private volatile bool _stopRequested = false;

    public void Run()
    {
        while (!_stopRequested)
        {
            // 作業を続ける
        }
        Console.WriteLine("停止しました");
    }

    public void Stop()
    {
        _stopRequested = true; // 別スレッドから呼ぶ
    }
}

volatile がないと、コンパイラが _stopRequested の値をレジスタにキャッシュして、別スレッドからの変更を検知できない可能性があります。

volatile の制約

  • 使える型は参照型、int、bool、byte、short など限定的(long、double は不可)
  • volatile は単純な読み書きの可視性を保証するだけで、アトミック性は保証しない
  • 複合操作(インクリメントなど)には Interlocked クラスや lock を使う
// volatile だけではスレッドセーフではない例
private volatile int _count = 0;

// _count++ は「読み取り → 加算 → 書き込み」の3ステップなので競合する
// → Interlocked.Increment(ref _count) を使う

一般的なマルチスレッドプログラミングでは lock や Interlocked を優先し、volatile はフラグ変数などの限定的な場面で使います。

フィールドの命名規則

C# の一般的な命名規則(.NET ランタイム / Microsoft のガイドライン)では、フィールドに以下の規則を適用します。

種類 命名規則 例
private / protected インスタンスフィールド _camelCase(アンダースコアプレフィックス) _name, _count
private static フィールド s_camelCase または _camelCase s_instance, _instance
const PascalCase MaxRetry, DefaultTimeout
public static readonly PascalCase Empty, MinValue
public class Example
{
    // const — PascalCase
    public const int MaxItems = 100;

    // static readonly — PascalCase
    public static readonly Example Default = new();

    // private フィールド — _camelCase
    private readonly string _name;
    private int _count;

    // private static — s_camelCase or _camelCase
    private static int s_globalCount;

    public Example(string name = "default")
    {
        _name = name;
    }
}

フィールドとプロパティの使い分け

プロパティの記事でも比較しましたが、フィールド側の視点から改めて整理します。

フィールドを使う場面 プロパティを使う場面
private / protected なデータ保持 外部に公開するメンバー
const 定数 バリデーションが必要
readonly でイミュータブルな内部状態 インターフェースの実装
volatile でスレッド間フラグ データバインディング
パフォーマンスが極めて重要な struct 内部 シリアライズ対象

原則: public フィールドは避け、プロパティを使う。 const と public static readonly は例外的にフィールドのまま公開するのが慣例です。

まとめ

  • フィールドはアクセサを持たない変数で、主に private / protected なデータ保持に使う
  • const はコンパイル時定数。高速だが、別アセンブリとの間で値が埋め込まれる点に注意
  • readonly は実行時定数。宣言時またはコンストラクタでのみ設定でき、任意の型に使える
  • static readonly は型レベルの実行時定数。const の埋め込み問題を回避しつつ不変性を保てる
  • volatile はスレッド間の可視性を保証するが、アトミック性は保証しない
  • public フィールドは避け、const と static readonly 以外はプロパティで公開するのが C# の慣例
C# .NET フィールド Const Readonly Static
← [C#] オブジェクト初期化を整理する — new() からオブジェクト初期化子・init・required・with 式まで [C#] Mutable vs Immutable — オブジェクトを「変えられる」設計と「変えられない」設計 →

Related Posts

  • [C#] Mutable vs Immutable — オブジェクトを「変えられる」設計と「変えられない」設計 Apr 24, 2026
  • [C#] Finalizable & Disposable パターン実践 — Dispose パターンの完全形 May 13, 2026
  • [C#] Disposable Objects — IDisposable / Dispose() と using 構文 May 12, 2026
  • [C#] Finalizable Objects — Finalize() の役割と使いどころ May 11, 2026

Table of Contents

  • インスタンスフィールド
    • フィールド初期化子
  • 静的フィールド(static field)
  • const(コンパイル時定数)
    • const の特徴
    • コンパイル時埋め込みの注意点
  • readonly(実行時定数)
    • readonly の特徴
    • readonly は「参照」が不変
  • const vs readonly vs static readonly
    • 使い分けの目安
  • volatile(揮発性フィールド)
    • volatile の制約
  • フィールドの命名規則
  • フィールドとプロパティの使い分け
  • まとめ

Recent Posts

  • [C#] Finalizable & Disposable パターン実践 — Dispose パターンの完全形 May 13, 2026
  • [C#] Disposable Objects — IDisposable / Dispose() と using 構文 May 12, 2026
  • [C#] Finalizable Objects — Finalize() の役割と使いどころ May 11, 2026
  • [C#] System.GC クラスを整理する — ガベージコレクションを制御するための API May 10, 2026
  • [C#] IComparable と IComparer — オブジェクトの順序比較と複数ソート戦略 May 9, 2026

Categories

  • C#63
  • .NET62
  • AWS27
  • Laravel16
  • Linux15
  • MySQL9
  • Apache8
  • PHP8
  • DynamoDB6
  • セキュリティ6
  • Nginx5
  • WordPress4
  • インフラ4
  • Hugo3
  • .NET Framework1
  • Aurora1
  • Filament1
  • Git1
  • SQS1

Tags

  • C#
  • .NET
  • AWS
  • Laravel
  • PHP
  • セキュリティ
  • MySQL
  • Linux
  • Apache
  • Code Snippet
  • DynamoDB
  • NoSQL
  • PHP-FPM
  • RDS
  • パフォーマンス
  • DoS
  • Nginx
  • Windows
  • WordPress
  • メモリ管理
  • 監視
  • 設計
  • Amazon Linux 2023
  • Docker
  • IDisposable
  • Ipset
  • Iptables
  • OPCache
  • Webサーバー
  • オブジェクト指向
  • クラス設計
  • コレクション
  • デザインパターン
  • パターンマッチング
  • 継承
  • 認可
  • Aurora
  • Blade
  • Grafana
  • Hugo
  • InfluxDB
  • Policy
  • Record
  • SSG
  • インターフェース
  • エラーハンドリング
  • カプセル化
  • ガベージコレクション
  • モニタリング
  • 例外
Powered by Hugo & Explore Theme.