良い方法か悪い方法か? ゲッターでオブジェクトを初期化する 質問する

良い方法か悪い方法か? ゲッターでオブジェクトを初期化する 質問する

どうやら私には奇妙な癖があるようです...少なくとも同僚によると。私たちは一緒に小さなプロジェクトに取り組んでいます。私がクラスを書いた方法は次のとおりです (簡略化した例):

[Serializable()]
public class Foo
{
    public Foo()
    { }

    private Bar _bar;

    public Bar Bar
    {
        get
        {
            if (_bar == null)
                _bar = new Bar();

            return _bar;
        }
        set { _bar = value; }
    }
}

つまり、基本的には、ゲッターが呼び出され、フィールドがまだ null である場合にのみ、フィールドを初期化します。これにより、どこでも使用されていないプロパティを初期化しないことで、オーバーロードが軽減されると考えました。

ETA: 私がこれをした理由は、私のクラスには別のクラスのインスタンスを返すプロパティがいくつかあり、そのプロパティもさらに他のクラスのプロパティを持っている、などです。トップクラスのコンストラクタを呼び出すと、その後、これらのすべてのクラスのコンストラクタがすべて呼び出されますが、いつも必要なものはすべて揃っています。

個人的な好み以外に、この慣行に対して何か異論はありますか?

更新: この質問に関して、私はさまざまな意見を検討しましたが、私は受け入れられた回答を維持します。しかし、今ではこの概念をより深く理解し、いつそれを使用するか、いつ使用しないかを判断できるようになりました。

短所:

  • スレッドの安全性の問題
  • 渡された値が null の場合、「setter」要求に従わない
  • マイクロ最適化
  • 例外処理はコンストラクタ内で行う必要がある
  • クラスのコードでnullをチェックする必要がある

長所:

  • マイクロ最適化
  • プロパティは null を返さない
  • 「重い」オブジェクトの読み込みを遅らせるか回避する

欠点のほとんどは現在のライブラリには当てはまりませんが、「マイクロ最適化」が実際に何かを最適化しているかどうかを確認するにはテストする必要があります。

最後の更新:

わかりました。答えを変えました。私の最初の質問は、これが良い習慣かどうかでした。そして、今はそれが良い習慣ではないと確信しています。現在のコードの一部ではまだ使用するかもしれませんが、無条件ではなく、常に使用することは絶対にありません。そのため、この習慣は捨てて、使用する前によく考えることにします。皆さん、ありがとうございます!

ベストアンサー1

ここで示しているのは、「遅延初期化」の単純な実装です。

短い答え:

遅延初期化の使用無条件に良いアイデアではありません。良い面もありますが、この解決策が及ぼす影響を考慮する必要があります。

背景と説明:

具体的な実装:
まず具体的なサンプルと、なぜその実装が単純だと考えるのかを見てみましょう。

  1. それは驚き最小の原則 (POLS). プロパティに値が割り当てられると、その値が返されることが期待されます。実装では、次の場合にはそうではありませんnull:

    foo.Bar = null;
    Assert.Null(foo.Bar); // This will fail
    
  2. これによって、かなりのスレッドの問題が発生します。foo.Bar異なるスレッドでの の 2 つの呼び出し元は、潜在的に の 2 つの異なるインスタンスを取得しBar、そのうちの 1 つはインスタンスに接続されていない状態になりますFoo。そのインスタンスに加えられた変更は、Bar暗黙的に失われます。
    これは、POLS 違反の別のケースです。プロパティの格納された値のみにアクセスする場合、スレッドセーフであることが期待されます。クラスはスレッドセーフではないと主張することもできますが (プロパティのゲッターを含む)、これは通常のケースではないため、これを適切に文書化する必要があります。さらに、この問題の導入は不要です。これについては、後ほど説明します。

一般的に:
さて、遅延初期化全般について見てみましょう。
遅延初期化は通常、オブジェクトの構築を遅らせるために使用されます。構築に長い時間がかかったり、大量のメモリを消費したりするもの一度完全に構築されると、
遅延初期化を使用する理由は非常に有効です。

しかし、このようなプロパティには通常セッターがないので、上で指摘した最初の問題は解消されます。
さらに、スレッドセーフな実装が使用されます - 次のようなものLazy<T>- 2 番目の問題を回避するため。

遅延プロパティの実装においてこれら 2 つの点を考慮した場合でも、次の点がこのパターンの一般的な問題です。

  1. オブジェクトの構築が失敗し、プロパティゲッターから例外が発生する場合があります。これはPOLSのもう一つの違反であり、回避する必要があります。プロパティに関するセクション「クラス ライブラリ開発の設計ガイドライン」では、プロパティ ゲッターは例外をスローしてはならないと明示的に述べられています。

    プロパティ ゲッターから例外をスローすることは避けてください。

    プロパティ ゲッターは、前提条件のない単純な操作である必要があります。ゲッターが例外をスローする可能性がある場合は、プロパティをメソッドとして再設計することを検討してください。

  2. コンパイラによる自動最適化、つまりインライン展開と分岐予測が損なわれます。Bill Kの回答詳しい説明については。

これらの点の結論は次のようになります。
遅延実装された各プロパティについては、これらの点を考慮する必要があります。
つまり、これはケースごとの決定であり、一般的なベスト プラクティスとして採用することはできません。

このパターンは適切な場面ではあるが、クラスを実装する際の一般的なベストプラクティスではない。無条件に使用すべきではない。上記の理由によります。


このセクションでは、遅延初期化を無条件で使用することの根拠として他の人が提示したいくつかの点について説明します。

  1. 連載:
    EricJ はコメントの中で次のように述べています:

    シリアル化される可能性のあるオブジェクトは、デシリアライズされるときにコンストラクタが呼び出されません (シリアライザによって異なりますが、一般的なものの多くはこのように動作します)。コンストラクタに初期化コードを配置すると、デシリアライズに追加のサポートを提供する必要があります。このパターンは、その特別なコーディングを回避します。

    この議論にはいくつかの問題があります。

    1. ほとんどのオブジェクトはシリアル化されません。必要のないときに何らかのサポートを追加することは、ヤグニ
    2. クラスがシリアル化をサポートする必要がある場合、一見シリアル化とは何の関係もない回避策なしでシリアル化を有効にする方法が存在します。
  2. マイクロ最適化: あなたの主張は、誰かが実際にアクセスしたときにのみオブジェクトを構築したいということです。つまり、実際にはメモリ使用量の最適化について話していることになります。
    私は次の理由からこの主張に同意しません。

    1. ほとんどの場合、メモリ内のオブジェクトが少し増えても何の影響もありません。現代のコンピュータには十分なメモリがあります。プロファイラによって実際に問題が確認されない限り、これは未熟な最適化そして、それに反対する十分な理由もあります。
    2. この種の最適化が正当化される場合もあることは承知しています。しかし、このような場合でも、遅延初期化は正しい解決策ではないようです。遅延初期化に反対する理由が 2 つあります。

      1. 遅延初期化はパフォーマンスに悪影響を及ぼす可能性があります。 おそらくわずかですが、Bill の回答が示すように、その影響は一見して想像するよりも大きいです。 したがって、このアプローチは基本的にパフォーマンスとメモリをトレードオフします。
      2. クラスの一部のみを使用するのが一般的なユースケースである設計の場合、これは設計自体に問題があることを示唆しています。問題のクラスには、おそらく複数の責任があります。解決策としては、クラスをより焦点を絞った複数のクラスに分割することです。

おすすめ記事