閉じる

C# プロパティの使い方 基本とget/setを順番に解説

プロパティは、フィールドのように値へ簡単にアクセスできる文法と、メソッドのように処理や検証を挟める柔軟さを両立したC#の機能です。

本記事では、getset の基本、自動実装プロパティ、バッキングフィールドを用いた検証や計算まで、動くサンプルを交えて順番に解説します。

C# プロパティの基本

プロパティは、クラスの外側からはフィールドのように見えますが、内部では get アクセサと set アクセサという2つのメソッドを通して読み書きを制御します。

get は値の取得時に呼ばれ、set は代入時に呼ばれます。

これにより、外部からの見た目はシンプルなまま、検証やキャッシュ、値の変換といったロジックを挟めます。

フィールドとの違いとメリット

フィールドは純粋なデータの入れ物で、読み書き時に処理を挟めません。

一方プロパティは読み書きのタイミングに処理を挟めるため、安全性と拡張性が高くなります。

フィールドとは

フィールドはクラス内に直接保持される変数です。

public なフィールドは外部から直接値を入れ替えられるため、思わぬ不整合を招きやすいという欠点があります。

プロパティのメリット

プロパティはフィールドへのアクセスをラップし、検証や変換などを集中管理できます。

将来の仕様変更にも強く、APIの互換性を保ちながら内部の実装だけを差し替えられます。

フィールドとプロパティの比較

観点フィールドプロパティ
アクセス直接アクセスgetset を通じてアクセス
検証・変換原則不可可能。代入時のバリデーションや取得時の加工ができる
実装の隠蔽弱い強い。実装を後から変更しやすい
インターフェースでの定義不可可能
継承・ポリモーフィズム不可virtualoverride で可能
データバインディング弱い仕組みによっては相性が良い

使い分けの目安

同一クラス内でのみ完結し、外部公開の必要がない内部状態にはフィールドを使い、外部へ公開する状態や将来の拡張性が必要な箇所にはプロパティを使うのが基本です。

get と set の役割

get はプロパティの値を返すアクセサで、set は代入された値を受け取り内部の状態を更新します。

set 内では自動的に value というキーワードで代入値を受け取れます。

get アクセサ

get は戻り値としてプロパティの型の値を返します。

計算や加工、キャッシュの返却などに使えます。

C#
public class Temperature
{
    private double celsius;

    // 取得時に摂氏をそのまま返す
    public double Celsius
    {
        get { return celsius; }
        set { celsius = value; }
    }

    // 取得時に華氏へ変換して返す(計算を挟む)
    public double Fahrenheit
    {
        get { return celsius * 9 / 5 + 32; }
    }
}

set アクセサ

set は代入値 value を受け取り、状態を更新します。

値の検証や範囲チェックの場として使います。

C#
public class Player
{
    private int level;

    public int Level
    {
        get { return level; }
        set
        {
            if (value < 1) throw new ArgumentOutOfRangeException(nameof(value), "Level は 1 以上である必要があります。");
            level = value;
        }
    }
}

よくある注意点

set の中で誤ってプロパティ自身に代入すると無限再帰になります。

必ずバッキングフィールドへ代入してください。

また、get 内で副作用の大きい処理を行うと予想外の回数呼ばれた際に性能問題が出ます。

自動実装プロパティの使い方

自動実装プロパティは、バッキングフィールドをコンパイラに自動生成させる書き方です。

簡単なラップで十分な場合は { get; set; } の最小構文で書けます。

最小コード例と書き方

最小の書式はプロパティの型と名前、そして { get; set; } です。

命名規約としてプロパティ名は PascalCase を用います。

書式

C#
public 型名 プロパティ名 { get; set; }

サンプル

C#
public class Person
{
    // 自動実装プロパティ。バックのフィールドはコンパイラが生成
    public string Name { get; set; }
    public int Age { get; set; }
}

既定値の設定(= 初期値)

自動実装プロパティにも初期値を与えられます。

プロパティ初期化子を使う方法と、コンストラクタで設定する方法があります。

プロパティ初期化子で設定する

宣言時に = 初期値; を書きます。

参照型の既定値は null、値型の既定値は型ごとのデフォルト値ですが、初期化子で明示しておくと null 参照を避けやすくなります。

C#
public class Settings
{
    public int RetryCount { get; set; } = 3;                  // 値型は 0 が既定値だが 3 に変更
    public string Endpoint { get; set; } = "https://api.example.com";
    public string ApiKey { get; set; } = string.Empty;        // 参照型の既定値は null。空文字で初期化
    public bool Enabled { get; set; }                         // 既定値は false のまま
}

コンストラクタで初期化する

初期値の計算や引数に基づいた設定が必要な場合はコンストラクタで行います。

コンストラクタ経由なら、バリデーションを set に任せつつ安全に初期化できます。

C#
public class Person
{
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }

    // 引数つきコンストラクタで初期化
    public Person(string name, int age)
    {
        Name = name; // set を通る
        Age = age;   // set を通る
    }
}

値型と参照型では既定値が異なるため、必要に応じて明示的に初期化しておくとバグを防げます。

プロパティにロジックを入れる

検証や変換などのロジックを入れたい場合は、バッキングフィールドを自分で用意してプロパティを記述します。

これにより、set で検証し、get で計算結果を返すといった柔軟な振る舞いが実装できます。

バッキングフィールドの作り方

まず private なフィールドを用意し、getset でそのフィールドを参照します。

定義手順

  1. クラス内に private フィールドを宣言する。
  2. 同じ型の public プロパティを宣言し、get でフィールドを返し、set でフィールドに代入する。

典型パターン

C#
public class Product
{
    private decimal price; // バッキングフィールド

    public decimal Price
    {
        get { return price; }
        set { price = value; } // 注意: プロパティ名ではなくフィールドへ代入する
    }
}

set で検証・get で計算する

検証ロジックや計算ロジックをアクセサに分担させます。

set での検証

set では代入値 value をチェックし、不正な場合は例外を投げたり、丸め込みを行ったりします。

C#
public class User
{
    private int age;

    public int Age
    {
        get => age;
        set
        {
            if (value < 0 || value > 150)
            {
                throw new ArgumentOutOfRangeException(nameof(value), "Age は 0〜150 の範囲で指定してください。");
            }
            age = value;
        }
    }
}

get で計算する

get では他のフィールドやプロパティを使って値をその場で組み立てられます。

内部状態を重複して持たず、一貫性を保てる利点があります。

C#
public class Person
{
    private string firstName = string.Empty;
    private string lastName = string.Empty;

    public string FirstName
    {
        get => firstName;
        set => firstName = value?.Trim() ?? string.Empty; // set で軽く整形
    }

    public string LastName
    {
        get => lastName;
        set => lastName = value?.Trim() ?? string.Empty;
    }

    // 取得時に計算して返す
    public string FullName
    {
        get
        {
            var space = (firstName.Length > 0 && lastName.Length > 0) ? " " : string.Empty;
            return $"{lastName}{space}{firstName}";
        }
    }
}

プロパティ内部では同名プロパティではなくフィールドを直接参照することで、不要な再帰呼び出しを避けられます。

手順で確認:C# プロパティの使い方

ここまでのポイントをひとつのプログラムで確認します。

クラスにプロパティを定義し、インスタンス化して getset の動きを観察します。

クラスにプロパティを定義する

ここでは、生徒情報を表す Student クラスを定義します。

NameScore は自動実装プロパティ、Age は検証つきプロパティ、Profile は取得時に計算するプロパティです。

コンストラクタで初期化も行います。

コード

C#
using System;

public class Student
{
    // 自動実装プロパティ
    public string Name { get; set; } = string.Empty;

    // set で検証するプロパティ(バッキングフィールドあり)
    private int age;
    public int Age
    {
        get => age;
        set
        {
            if (value < 6 || value > 120)
            {
                throw new ArgumentOutOfRangeException(nameof(value), "Age は 6〜120 の範囲で指定してください。");
            }
            age = value;
        }
    }

    // 自動実装プロパティに既定値
    public int Score { get; set; } = 50;

    // 取得時に計算するプロパティ
    public string Profile
    {
        get => $"{Name} さんは {Age} 歳で、スコアは {Score} 点です。";
    }

    // コンストラクタで初期化
    public Student(string name, int age, int score = 50)
    {
        Name = name;  // set を通る
        Age = age;    // set を通る(検証される)
        Score = score;
    }
}

インスタンス化して get/set を使う

作成したクラスを使ってインスタンスを生成し、プロパティを読み書きして挙動を確認します。

例外が投げられるケースも試します。

実行用コード

C#
using System;

public class Program
{
    public static void Main()
    {
        // 正常な初期化
        var s1 = new Student("Taro", 12, 88);
        Console.WriteLine(s1.Profile); // get で計算された文字列を表示

        // set を通じて値を更新
        s1.Score = 92;                 // 自動実装プロパティの書き込み
        Console.WriteLine($"更新後スコア: {s1.Score}");
        Console.WriteLine(s1.Profile); // 計算結果も更新される

        // 既定値が効くケース
        var s2 = new Student("Hanako", 10); // score を省略し既定値 50
        Console.WriteLine(s2.Profile);

        // 不正値の代入で検証が働くケース
        try
        {
            s2.Age = 4; // 例外: 範囲外
        }
        catch (ArgumentOutOfRangeException ex)
        {
            Console.WriteLine($"検証エラー: {ex.Message}");
        }

        // 整形入りの set の例: 空白を含む名前を整える
        s2.Name = "  Hanako  ";
        Console.WriteLine($"整形後の名前: '{s2.Name}'");
    }
}

// 先ほど定義した Student クラスを同一ファイル内に置くか、別ファイルで参照してください。
public class Student
{
    public string Name { get; set; } = string.Empty;

    private int age;
    public int Age
    {
        get => age;
        set
        {
            if (value < 6 || value > 120)
            {
                throw new ArgumentOutOfRangeException(nameof(value), "Age は 6〜120 の範囲で指定してください。");
            }
            age = value;
        }
    }

    public int Score { get; set; } = 50;

    public string Profile => $"{Name} さんは {Age} 歳で、スコアは {Score} 点です。";

    public Student(string name, int age, int score = 50)
    {
        Name = name;
        Age = age;
        Score = score;
    }
}
実行結果
Taro さんは 12 歳で、スコアは 88 点です。
更新後スコア: 92
Taro さんは 12 歳で、スコアは 92 点です。
Hanako さんは 10 歳で、スコアは 50 点です。
検証エラー: Age は 6〜120 の範囲で指定してください。 (Parameter 'value')
整形後の名前: '  Hanako  '

上の例では、Profileget のたびに最新状態から文字列を組み立てるため、Score を更新するとすぐに反映されます。

Ageset 内で検証され、範囲外なら例外が発生します。

まとめ

プロパティは、外部からはフィールドのように扱えつつ、内部で getset によって読み書きの挙動を細かく制御できる強力な仕組みです。

まずは自動実装プロパティでシンプルに始め、必要になった時点でバッキングフィールドを導入し、set での検証や get での計算を加える流れが実践的です。

初期値はプロパティ初期化子かコンストラクタで明示し、プロパティ内部ではフィールドを直接操作して無限再帰を避けることを心がけてください。

プロパティを上手に使い分けることで、読みやすく安全で拡張しやすいコードに近づけます。

この記事を書いた人
エーテリア編集部
エーテリア編集部

C#の入門記事を中心に、開発環境の準備からオブジェクト指向の基本まで、順を追って解説しています。ゲーム開発や業務アプリを目指す人にも役立ちます。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!