C#のgetアクセサーのみのプロパティを使用する際に気をつけるべきこと


TL;DR

getアクセサーのみ持つプロパティに対して、コンストラクターで値を設定するか、getterで値を設定するかによって取得する値が変わる場合があります。

// プロパティに対して
public string Foo { get; }

// コンストラクターで値を設定
public ClassName()
{
    Ticks = Datetime.Now.Ticks.ToString();
}

// getterで値を設定
public string Ticks => Datetime.Now.Ticks.ToString();

どういうことか?

プロパティをインターフェースや基底クラスで定義して、派生クラスで具体的な値を設定したいという場合があるかと思います。

interface SampleInterface
{
    public string Ticks { get; }
}

これを継承して実装します。

getterで設定

// SampleInterfaceを派生クラスで実装する
class DerivedClass : SampleInterface
{
    public string Ticks => DateTime.Now.Ticks.ToString();
}

これを使って値を取得してみます。

static void Main(string[] args)
{
    SampleInterface instance = new DerivedClass();
    Console.WriteLine(instance.Ticks); // 637832891548362530
    Console.WriteLine(instance.Ticks); // 637832891548915630:上と値が違う
}

get => 処理のように実装した場合は、値を取得しようとするごとにメソッドのように実行されます。
そのため、毎回処理が実行され、DateTimeのTicksを取得しようとした場合には別の値になったり、
=> new Class()のようにインスタンスを返す場合は、毎回違うインスタンスが返ってくることになります。

コンストラクターで設定

今度はコンストラクターで値を設定します。

class DerivedClass : SampleInterface
{
    public string Ticks { get; }

    public DerivedClass()
    {
        Ticks = DateTime.Now.Ticks.ToString();
    }
}
static void Main(string[] args)
{
    SampleInterface instance = new DerivedClass();
    Console.WriteLine(instance.Ticks); // 637832896477867260
    Console.WriteLine(instance.Ticks); // 637832896477867260:上と値が同じ
}

コンストラクターで値を設定した場合は、値が一意に定まります。
そのため、この場合はコンストラクターで設定した時点でのTicksが表示されますし、
Value = new Class();などのように、参照型の値をコンストラクターで入れていた場合は毎回同じ参照のインスタンスが返ってきます。

つまり、同じgetterのみのプロパティだとしても、実装の方法によって返ってくる値が異なる場合が出てきます。


@Zuishin さんから補足情報いただきました。
下記の方法ではコンストラクターでの初期化よりも前に実行されるため、この方法でも値が一意に定まります。

public string Ticks { get; } = Datetime.Now.Ticks.ToString();

コンストラクターの引数で処理を変える必要がない場合はこちらも有効ですね。

パフォーマンスへの影響

getter処理に実装した場合、毎回その処理が実行されて値が返ってきます。
何かしらのクラスをnewして返す場合では、newは比較的コストが高いため、
getter内でnewして返す実装は、コンストラクターで初期値としてnewしている場合に比べてパフォーマンスが落ちます。

おわりに

自分用メモとして書きました。
当たり前といえば当たり前なのですが、2つの実装の違いを理解しておかないと不具合に繋がりかねないので気をつけましょう!