web-dev-qa-db-ja.com

いつLazy <T>を使うべきですか?

私はLazyについてのこの記事を見つけました: C#4.0のLaziness - Lazy

Lazyオブジェクトを使って最高のパフォーマンスを得るためのベストプラクティスは何ですか?誰かが私に実際のアプリケーションでの実用的な使い方を教えてもらえますか?言い換えれば、いつ使うべきですか?

289
danyolgiax

実際に初めて使用するときに何かをインスタンス化したいときに、通常それを使用します。これは、常にコストを発生させるのではなく、必要な場合/必要なときまでそれを作成するコストを遅らせます。

通常、これは、オブジェクトが使用される場合と使用されない場合があり、その作成コストが重要ではない場合に適しています。

208

シングルトンを使用しないようにする必要がありますが、必要であれば、Lazy<T>を使用すると、遅延のあるスレッドセーフなシングルトンを簡単に実装できます。

public sealed class Singleton
{
    // Because Singleton's constructor is private, we must explicitly
    // give the Lazy<Singleton> a delegate for creating the Singleton.
    static readonly Lazy<Singleton> instanceHolder =
        new Lazy<Singleton>(() => new Singleton());

    Singleton()
    {
        // Explicit private constructor to prevent default public constructor.
        ...
    }

    public static Singleton Instance => instanceHolder.Value;
}
110
Matthew

遅延読み込みが便利になる優れた実世界の例は、Entity FrameworkやNHibernateなどのORM(Object Relation Mappers)を使用することです。

Name、PhoneNumber、およびOrde​​rsのプロパティを持つエンティティCustomerがあるとします。 NameとPhoneNumberは通常の文字列ですが、Ordersは顧客がこれまでに行ったすべての注文のリストを返すナビゲーションプロパティです。

あなたはしばしばあなたのすべての顧客を調べて、それらを呼び出すために彼らの名前と電話番号を入手したいと思うかもしれません。これは非常に迅速で簡単な作業ですが、顧客を作成するたびに自動的に処理が進み、何千もの注文を返すために複雑な結合を行ったとします。 最悪の部分はあなたも注文を使用するつもりはないということですので、それはリソースの完全な無駄です!

Orderプロパティが遅延している場合、実際にそれらが必要でない限り、すべての顧客の注文を取得することはできないため、これは遅延ロードに最適な場所です。 Orderプロパティが辛抱強く眠っている間に、CustomerオブジェクトがそれらのNameとPhone Numberだけを取得することを列挙することができ、あなたがそれを必要なときに備えています。

76
Despertar

私はLazy<T>プロパティを使用して自分のコードのパフォーマンスを向上させることを検討しています(そして、それについてもう少し学ぶ)。私はそれをいつ使うべきかについての答えを求めてここに来ましたが、どこに行っても次のようなフレーズがあるようです:

遅延初期化を使用して、特にプログラムの存続期間中にそのような作成または実行が発生しない可能性がある場合に、大規模またはリソース集約型オブジェクトの作成、またはリソース集約型タスクの実行を延期します。

from MSDN Lazy <T>クラス

どこに線を引くかわからないので、少し混乱しています。たとえば、線形補間は非常に高速な計算と考えていますが、必要ない場合は、遅延初期化を行うことで回避することができ、価値がありますか?

結局、私は自分のテストを試すことにし、ここで結果を共有すると思いました。残念ながら、私はこの種のテストの専門家ではありませんので、改善を提案するコメントを喜んで受け取ります。

説明

私の場合、Lazy Propertiesが多くの補間(ほとんどは未使用)を行うコードの一部を改善するのに役立つかどうかに特に興味があったので、3つのアプローチを比較するテストを作成しました。

アプローチごとに20個のテストプロパティ(tプロパティと呼びます)を持つ個別のテストクラスを作成しました。

  • GetInterpクラス:tプロパティが取得されるたびに線形補間を実行します。
  • InitInterp Class:コンストラクターでそれぞれの線形補間を実行して、t-propertiesを初期化します。 getはdoubleを返します。
  • InitLazyクラス:t-プロパティをLazyプロパティとして設定し、プロパティが最初に取得されたときに線形補間が1回実行されるようにします。後続のgetは、すでに計算されたdoubleを返すだけです。

テスト結果はミリ秒単位で測定され、50のインスタンス化または20のプロパティ取得の平均です。その後、各テストを5回実行しました。

テスト1の結果:インスタンス化(50インスタンス化の平均)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.005668 0.005722 0.006704 0.006652 0.005572 0.0060636 6.72
InitInterp 0.08481  0.084908 0.099328 0.098626 0.083774 0.0902892 100.00
InitLazy   0.058436 0.05891  0.068046 0.068108 0.060648 0.0628296 69.59

テスト2の結果:最初の取得(平均20のプロパティ取得)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.263    0.268725 0.31373  0.263745 0.279675 0.277775 54.38
InitInterp 0.16316  0.161845 0.18675  0.163535 0.173625 0.169783 33.24
InitLazy   0.46932  0.55299  0.54726  0.47878  0.505635 0.510797 100.00

テスト3の結果:2番目の取得(平均20のプロパティ取得)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.08184  0.129325 0.112035 0.097575 0.098695 0.103894 85.30
InitInterp 0.102755 0.128865 0.111335 0.10137  0.106045 0.110074 90.37
InitLazy   0.19603  0.105715 0.107975 0.10034  0.098935 0.121799 100.00

観察

GetInterpは何も実行しないため、予想どおりインスタンス化が最も高速です。 InitLazyInitInterpよりもインスタンス化が高速で、遅延プロパティを設定する際のオーバーヘッドが線形補間計算よりも高速であることを示唆しています。ただし、InitInterpは20個の線形補間を実行する必要があるため(tプロパティを設定するため)、ここで少し混乱していますが、GetInterpと比較して、インスタンス化(テスト1)に0.09ミリ秒しかかかりません1回目の線形補間を1回だけ行うには0.28ミリ秒(テスト2)、2回目には0.1ミリ秒(テスト3)かかります。

InitLazyは、プロパティを最初に取得するのにGetInterpのほぼ2倍の時間がかかりますが、InitInterpはインスタンス化中にプロパティを設定するため、最速です。 (少なくともそれがすべきでしたが、なぜそれが単一の線形補間よりもインスタンス化の結果がそれほど速くなったのですか?正確にいつこれらの補間を行うのですか?)

残念ながら、私のテストではいくつかの自動コード最適化が行われているようです。 2回目と同じように、プロパティを取得するのにGetInterp同じ時間がかかりますが、2倍以上速く表示されます。これらの最適化はすべてテスト3にほぼ同じ時間を要するため、他のクラスにも影響を与えているように見えます。しかし、このような最適化は私自身のプロダクションコードでも行われる可能性があります。

結論

一部の結果は予想どおりですが、おそらくコードの最適化による非常に興味深い予期しない結果もいくつかあります。コンストラクターで多くの作業を行っているように見えるクラスであっても、インスタンス化の結果は、doubleプロパティを取得するのに比べて、非常に迅速に作成できることを示しています。この分野の専門家はより徹底的にコメントして調査することができますが、私の個人的な感覚では、このテストをもう一度行う必要があります。しかし、私はInitInterpが進むべき道であると期待しています。

38
Ben

Mathewが投稿した例を見てみましょう

public sealed class Singleton
{
    // Because Singleton's constructor is private, we must explicitly
    // give the Lazy<Singleton> a delegate for creating the Singleton.
    private static readonly Lazy<Singleton> instanceHolder =
        new Lazy<Singleton>(() => new Singleton());

    private Singleton()
    {
        ...
    }

    public static Singleton Instance
    {
        get { return instanceHolder.Value; }
    }
}

lazyが生まれる前に、私たちはこのようにしていたでしょう。

private static object lockingObject = new object();
public static LazySample InstanceCreation()
{
    if(lazilyInitObject == null)
    {
         lock (lockingObject)
         {
              if(lazilyInitObject == null)
              {
                   lazilyInitObject = new LazySample ();
              }
         }
    }
    return lazilyInitObject ;
}
14

MSDNから:

特にプログラムの有効期間中にそのような作成または実行が行われない可能性がある場合は、Lazyのインスタンスを使用して、大きなオブジェクトまたはリソースを大量に消費するオブジェクトの作成またはリソースを大量に使用するタスクの実行を延期します。

James Michael Hareの答えに加えて、Lazyはあなたの値のスレッドセーフな初期化を提供します。 LazyThreadSafetyMode 列挙このクラスのさまざまな種類のスレッドセーフティモードを説明するMSDNのエントリをご覧ください。

12
Vasea