プロパティ フィールドのようなメソッドのような

基本文法

プロパティ

プログラミング言語においてプロパティという用語は多義的に使われますが、C#言語においてはアクセッサーの機能を持つ文法です。

Java言語やC++言語といった他のメジャーなプログラミング言語にはない機能です。
クラスが持つ状態を表すフィールドをカプセル化するための記述が簡単になります。
使う方からはフィールド(変数)のように扱え、実装する側からはメソッド(関数)のように扱えます。
インライン展開されるのでメソッドを呼ぶようなオーバーヘッドがない利点もあります。
(プロパティ内の処理が複雑な場合など、必ずしもインライン展開される訳でもない)

実装例

// フィールドを公開
public string Name;
これをプロパティを使わずにカプセル化するには、フィールドを非公開にし、取得用と設定用のメソッドを用意します。
// フィールドを非公開化
private string  _name;
// 取得用と設定用のメソッドを定義
public string GetName() => _name;
public void SetName(string name) => _name = name;

これをプロパティを使って定義すると以下のようになります
なお、取得用のgetと設定用のsetとsetの中の変数名であるvalueは予約語になります



private string  _name;

public string Name
{
    get => _name;
    set => _name = value; 
}

Visual Studioによる簡単な記述法

Visual Studio上でフィールド定義の変数名を選択してコンテキストメニューを開き、「クイックアクションとリファクタリング」→「フィールドのカプセル化」を選択すると展開してもらえます。

自動プロパティ

上記のような定義を大量に記述するのは大変ですが、簡易に記述することができます。
コンパイル時に上の実装例にように展開されます。



// 上の定義と同義
public string Name { get; set; }

異なるアクセス指定の方法

setterとgetterとで異なるアクセスレベルを指定可能です
// 他クラスからは値の取得は可能だが変更は自クラス内からのみ可能
public string Name { get; private set; }

自動プロパティの初期化子



// C#6.0から導入された文法
public string Name { get; set; } = string.Empty;

Getterのみの自動プロパティ


// C#6.0から導入 初期化子またはコンストラクタでのみ値の設定が可能
public string Company { get;} = "hogehoge株式会社";

// 以下のような記述も可能
public string Address => "東京都千代田区";

Getterによる状態変更

下記の例では年齢を参照する毎に老いていくことになる。副作用あるコードは要注意。
public class Person
{
    private int _age;

    public int Age  => _age++; 
}

カプセル化の利点

他クラスからのアクセスを制御でき、入力値チェックや値変更のイベント惹起など処理を介在させることが可能となります。 下記の例では年齢にマイナス値が入ることはありません。
<pre class="wp-block-syntaxhighlighter-code">
    public class Person
    {
        private int _age;
        public int Age 
        {
            get => _age; 
            set
            {
                // 入力値のチェック
                if(value < 0)
                    throw new ArgumentOutOfRangeException();

                _age = value;
            }
        }
    }</pre>

プロパティの値変更時に処理を実行する

   public class Person
    {
        private int _age;
        public int Age 
        {
            get => _age; 
            set
            {
                if(_age != value)
                {
                    _age = value;

                    // メソッド呼び出し
                    DoSomethingWhenAgeChanged(_age);
                }
            }
        }

        private void DoSomethingWhenAgeChanged(int age)
        {
            Console.WriteLine($"age changed to {age}");
        }
    }
上記コードでも一応は問題ないのですが、プロパティの目的であるアクセッサの責務からは外れているとの議論はあります。 このような場合はプロパティではなく通常のメソッドを用意した方がよいかもしれません。

    public class Person
    {
        private int _age;
        // プロパティはGetterのみ
        public int Age => _age;

        // 値の設定はメソッドで行う
        public void SetAge(int age)
        {
            if (_age != age)
            {
                _age = age;

                // メソッド呼び出し
                DoSomethingWhenAgeChanged(_age);
            }
        }

        private void DoSomethingWhenAgeChanged(int age)
        {
            Console.WriteLine($"age changed to {age}");
        }
    }
または、プロパティの値変更イベントを介して処理を実行する方法もあります。

    // INotifyPropertyChangedインタフェースを実現(継承)
    public class Person: System.ComponentModel.INotifyPropertyChanged
    {
        // INotifyPropertyChangedのメンバ
        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

        private int _age;

        public int Age
        {
            get => _age;
            set
            {
                if (_age != value)
                {
                    _age = value;

                    // プロパティ変更のイベントを惹起
                    PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(Age)));
                }
            }
        }

        public Person()
        {
            // イベントを購読
            PropertyChanged += (_, e) =>
            {
                if (e.PropertyName == nameof(Age))
                    DoSomethingWhenAgeChanged(Age);
            };
        }

        private void DoSomethingWhenAgeChanged(int age)
        {
            Console.WriteLine($"age changed to {age}");
        }
    }