「C# オートプロパティの裏側 ― コンパイラが生成する バッキングフィールド を覗いてみよう」

1. はじめに

C# では オートプロパティ({ get; set; } とだけ書くプロパティ)を使うだけで、

‐ 値の保持用フィールド

‐ get / set アクセサ

をまるごとコンパイラに任せられます。

しかし実行時には当然どこかに値を保存する領域が必要です。その“陰の主役”が バッキングフィールド (backing field) です。

本記事では SharpLab で生成 IL を確認したスクリーンショットを題材に、バッキングフィールドの仕組みと実践的な活用ポイントを整理します。


2. オートプロパティとバッキングフィールドとは

用語役割コード記述実際に存在する場所
オートプロパティ値への公開窓口
(public string FirstName { get; set; })
開発者が書くMSIL 変換時に get_FirstName / set_FirstName メソッドへ
バッキングフィールド実データを保持するプライベート変数書かなくても コンパイラが自動生成private string <FirstName>k__BackingField;

ポイント

  • 命名規則: <プロパティ名>k__BackingField。k__ は “compiler-generated” の目印
  • アクセス修飾子: private(直接触れさせないことでカプセル化を担保)
  • 属性付与: [CompilerGenerated] など、デバッグ時に不要表示を抑える属性が自動で付く

3. SharpLab で裏側を確認する

  1. 左ペインに下記コードを貼り付け
class Person
{
    // 名
    public string FirstName { get; set; }
}
  1. 右ペイン「IL」または「C# (decompiled)」を選択
  2. 生成されたコードを確認

➜ 左側:シンプルなソースコード

➜ 右側:バッキングフィールドを含む生成コード

が並列で比較ますのでイメージしてみましょう。

キャプチャ例では以下のように展開されています(主要部のみ抜粋)。

[CompilerGenerated]
private string <FirstName>k__BackingField;

public string FirstName
{
    [CompilerGenerated]
    get { return <FirstName>k__BackingField; }
    [CompilerGenerated]
    set { <FirstName>k__BackingField = value; }
}

4. バッキングフィールドを「自前で」書くケース

目的自動生成 (オートプロパティ)明示的フィールド
単純な値の格納◎ 手軽△ 冗長
バリデーション△ set の中に追加記述が必要◎ フィールドは private、プロパティでチェック
Lazy-Load / キャッシュ保持△ 複雑◎ フィールドを null → 初回アクセスで生成
参照渡し (ref/out) が必要✕ プロパティは使えない◎ フィールドを直接渡せる

Tips

  1. C# 9 以降は init アクセサでイミュータブルパターンも簡潔に書ける
  2. record 型は “with-式” でコピー/変更を内部で安全に行うため、バッキングフィールドも自動生成されます

5. メリット・デメリットまとめ

オートプロパティ + 自動バッキングフィールド

  • ✅ 可読性・記述量の削減
  • ✅ DI(依存性注入)フレンドリー
  • ❌ 複雑なロジックを追加すると行数が一気に増え、かえって読みにくくなる

明示的バッキングフィールド

  • ✅ カスタムロジック(バリデーション、通知、遅延評価)を柔軟に実装
  • ✅ ref/out で直接参照を渡せる
  • ❌ フィールドとプロパティが分かれ、初心者には冗長に見えやすい

6. サンプルコード:バリデーション付きプロパティ

class Person
{
    private string _firstName = "";

    /// <summary>
    /// 名。空文字や数字のみを禁止するバリデーション例
    /// </summary>
    public string FirstName
    {
        get => _firstName;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("FirstName は空にできません");
            if (value.Any(char.IsDigit))
                throw new ArgumentException("FirstName に数字は含められません");
            _firstName = value;
        }
    }
}

7. まとめ

  • バッキングフィールドは「プロパティの裏でデータを保持する実体」
  • オートプロパティにより 99% の日常コードは自動生成で OK
  • ただし バリデーション/Lazy Load/mutable 構造体の扱い 等では明示的にフィールドを使うと可読性・保守性が向上
  • SharpLab など IL ビューアを活用すると「コンパイラがどんなコードを吐いたか」を即確認でき、学習にもデバッグにも役立つ

参考

  • SharpLab — C#/IL/ASM 変換をリアルタイムで確認できるオンラインツール
  • Microsoft Docs — Auto-Implemented Properties (C# Programming Guide)
  • 「Effective C# 7.0」 Item 10 — Prefer Auto-Properties
訪問数 4 回, 今日の訪問数 2回

C#,プロパティ

Posted by hidepon