うに.log

作業した内容のまとめとか読んだ本のまとめとか。間違っていることがあったらツッコミとかがもらえるといいなぁという願望のもと、とりあえずやったこと、調べたこと、理解していることしていないことをだらだら書いていく。勉強用のブログ。

C# - プロパティについて

プロパティ

プロパティとは

オブジェクト指向でよく使われる概念であるところのカプセル化の実装を補助するための機能です。

プロパティを使わない実装

プロパティを使わない場合は以下のように定型的な記述が非常に多いコードを書く必要があります。

private string capsuledValue;
public string GetCapsuledValue()
{
    return capsuledValue;
}

private string SetCapsuledValue(string value)
{
    capsuledValue = value;
}

C# 1.x

プロパティは C# の初期から使えていました。が、初期段階ではアクセスレベルがプロパティ単位でしか指定できなかったため、setter は関数かするなりフィールドを直接触るなりしないとダメで setter が存在している意味がかなり薄く、利便性もそれほど高くありませんでした。

先程のコードを置き換えてみます。

private string capsuledValue;
public string CapsuledValue
{
    get { return capsuledValue; }
}

private string SetCapsuledValue(string value)
{
    capsuledValue = value;
}

あまり変わりませんね。

ちなみに getter と setter はただの関数なので、関数でできることはプロパティでも大抵可能です。

private string sample;
public string Sample
{
    get
    {
        // 値を返す前にしたい処理色々
        return sample;
    }
    set
    {
        // 値を入れる前にしたい処理色々
        sample = value;
        // 値を入れた後にしたい処理色々
    }
}

C# 2.0

これが 2.0 になると getter と setter それぞれに異なるアクセスレベルを指定できるようになったため、有用性が増し、非常に頻繁に利用されるようになりました。

private string capsuledValue;
public CapsuledValue
{
    get { return capsuledValue; }
    private set { capsuledValue = value; }
}

このように getter と setter の関数を自前で実装する必要がなくなりかなりシンプルになりました。

3.0

C# 3.0 ではこれがさらに省略できるようになり、変数への値の出し入れをするだけのプロパティであれば フィールドを getter と setter の中身を記述しなくても良くなりました。

public CapsuledValue { get; private set; }

非常にシンプルで大変使い勝手が良くなりました。

Model オブジェクトだと値の出し入れだけできれば良いケースが大半なため、この記法は非常に多用されます。

6

get-only プロパティ

C# 6 ではさらに省略することが可能となり、コンストラクタからの set しか受け付けない、 get-only プロパティを定義することができるようになりました。

public class Sample
{
    public string CapsuledValue { get; }
    public Sample(string value)
    {
        CapsuledValue = value;
    }
}

これによってさらにシンプルな記述ができるようになった上にイミュータブル%20%E3%81%AA,%E3%82%92%E5%A4%89%E3%81%88%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%82%8B%E3%80%82)なプロパティを作成することができるようになりました。

コンストラクタでしか設定できない、といえば readonly です。
そして get-only プロパティは以下のような実装に展開されています。

public class Sample
{
    private readonly string capsuledValue;
    public string CapsuledValue
    {
        get { return capsuledValue; }
    }
    public Sample(string value)
    {
        capsuledValue = value;
    }
}

実際に readonly なフィールドが作られています。

で、これ私の理解が足りないんだと思いますが、public readonly なフィールドを定義した場合との使い所の違いが今ひとつわかっていません。

public に公開されるところは全てプロパティにしてしまえって考え方で、その一貫性を保つため、という事であればまぁわかるんですけど。*1

他に何か理由があればコメントなりなんなりで教えていただけると嬉しいです。

プロパティ初期化子

プロパティを通常のフィールドのように初期値を設定できるようになりました。

これまでは以下のように対応していました。

public class Sample
{
    private string value1 = "value1";
    public string Value1
    {
        get { return value1; }
        set { value1 = value; }
    }
    public string Value2 { get; private set; }

    public Sample()
    {
        Value2 = "value2";
    }
}

プロパティ初期化子が使えるようになると以下のように記述できます。

public class Sample
{
    public string Value1 { get; private set; } = "value1";
    public string Value2 { get; private set; } = "value2";
}

expression-bodied なプロパティ

日本語では式形式のメンバーと呼ばれていますが、個人的には expression-bodied の方が馴染みがあるのでこちらで。*2

C# 6 ではget のみの場合に限り、ラムダ式でプロパティを定期できるようになりました。

具体的には以下のような記述になります。

private string sample;
public string Sample => sample.Replace("hoge", "sample");

get と記述する必要がなくなり、よりシンプルに書けるようになりました。

ezpression-bodied はプロパティに限らず、関数にも利用できるので文脈に応じてケースバイケースで使い分けるようにしましょう。

プロパティは比較的単純に get する処理系、関数は何がしか意味を持って処理をする処理系、の場合、と使い分けると良いと思います。

7

C# 7 で expression-bodied なプロパティが拡張され、setter でも利用可能になりました。できることは変わらず、ラムダ式ワンライナーで書ける範囲のことを記述できます。

private string sample;
public Sample
{
    get => sample:
    set => sample = value.Replace("hoge", "sample");
}

*1:実際 C# は public に公開されるものはほぼ全てプロパティになっています

*2:プレビュー段階から触ったりしていると日本語名を知らずに終わることが多々あるここ最近の.NET界隈です。