いつLazy <T>を使用すればよいですか?


327

私はこの記事を見つけましたLazyC#4.0での怠惰– Lazy

レイジーオブジェクトを使用して最高のパフォーマンスを実現するためのベストプラクティスは何ですか?誰かが実際のアプリケーションでの実用的な使用を私に指摘できますか?つまり、いつ使用すればよいですか。


42
置き換え:get { if (foo == null) foo = new Foo(); return foo; }。そして、それを使用する可能性のある場所は無数にあります...
Kirk Woll

57
はスレッドget { if (foo == null) foo = new Foo(); return foo; }セーフではありませんがLazy<T>、デフォルトではスレッドセーフです。
マシュー

23
MSDNから:重要:遅延初期化はスレッドセーフですが、作成後のオブジェクトは保護されません。型がスレッドセーフでない限り、オブジェクトにアクセスする前にロックする必要があります。
Pedro.The.Kid 2014年

回答:


237

通常は、実際に使用するときに初めてインスタンス化するときに使用します。これにより、常にコストが発生する代わりに、必要な場合/必要になるまで作成コストが遅延します。

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


121
なぜ常にLazyを使用しないのですか?
TruthOf42 2013年

44
最初の使用時にコストが発生し、ロックのオーバーヘッドが発生する可能性があります(そうでない場合はスレッドの安全性が犠牲になります)。したがって、慎重に選択し、必要でない限り使用しないでください。
James Michael Hare 2013年

3
ジェームズは、「そして建設の費用は取るに足らないこと」について詳しく説明していただけませんか?私の場合、クラスには19のプロパティがあり、ほとんどの場合、2つまたは3つだけを見る必要があります。したがって、を使用して各プロパティを実装することを検討していますLazy<T>。ただし、各プロパティを作成するために、かなり簡単な線形補間(または双線形補間)を行っていますが、コストがかかります。(私が行って自分で実験することを提案するつもりですか?)
Ben

3
ジェームズ、私自身のアドバイスを受けて、私は自分の実験をしました。私の投稿を参照してください
2014年

17
高スループット、低レイテンシシステムでのユーザーレイテンシを防ぐために、システムの「起動中」にすべてを初期化/インスタンス化したい場合があります。これは、「常に」Lazyを使用しない多くの理由の1つにすぎません。
デリック

126

シングルトンの使用は避けるべきですが、必要な場合は、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;
}

38
今私は、私はそれらを避けるべき理由を学ぶ必要がある... D:私は、私はそれらを使用しているときは、シングルトンを利用するのは避けるべき読ん嫌いD
バートカリスト

24
マイクロソフトがサンプルでシングルトンの使用を停止した場合は、シングルトンの使用を停止します。
eaglei22

4
私はシングルトンを避ける必要があるという考えに反対する傾向があります。依存性注入のパラダイムに従う場合、どちらの方法でも問題ありません。理想的には、すべての依存関係は一度だけ作成する必要があります。これにより、高負荷シナリオでのGCへの圧力が軽減されます。したがって、クラス自体からシングルトンにすることは問題ありません。最新のDIコンテナー(すべてではないにしても)のほとんどは、どちらの方法でもそれを処理できます。
Lee Grissom

1
そのようなシングルトンパターンを使用する必要はありません。代わりに、任意のdiコンテナーを使用して、クラスをシングルトンに構成します。コンテナーがオーバーヘッドを処理します。
VivekDev 2017年

すべてに目的があり、シングルトンが適切なアプローチである状況とそうでない状況があります:)。
Hawkzey

86

偉大な実世界の遅延ロードが便利になる場所の例は、Entity FrameworkのとNHibernateのようORMの(オブジェクト関係マッパー)です。

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

多くの場合、すべての顧客を調べ、顧客の名前と電話番号を取得して電話をかけます。これは非常にすばやく簡単なタスクですが、顧客を作成するたびに自動的に実行され、複雑な結合を行って何千もの注文を返す場合を想像してください。最悪なのは、注文を使用することさえないため、リソースを完全に浪費することです。

Orderプロパティが遅延の場合、実際に必要でない限り顧客のすべての注文を取得しないので、これは遅延読み込みに最適な場所です。Orderプロパティが辛抱強くスリープしているときに、必要なときに備えて、名前と電話番号のみを取得するCustomerオブジェクトを列挙できます。


34
このような遅延読み込みは通常ORMにすでに組み込まれているため、悪い例です。Lazy <T>値をPOCOに追加して遅延読み込みを開始するのではなく、ORM固有の方法を使用してそれを行う必要があります。
Dynalon 2013年

56
@Dynaこの例は、ORMの組み込み遅延読み込みを参照しています。これは、遅延読み込みの有用性を明確かつ単純な方法で例示しているためです。
デスパタール2013年

エンティティフレームワークを利用している場合、独自の遅延を強制する必要がありますか?または、EFはあなたのためにそれをしますか?
Zapnologica 2014年

7
@Zapnologica EFは、デフォルトでこのすべてを行います。実際、イージーローディング(レイジーローディングの反対)が必要な場合は、を使用してEFを明示的に通知する必要がありますDb.Customers.Include("Orders")。これにより、Customer.Ordersプロパティが最初に使用されたときではなく、その時点で順序結合が実行されます。遅延読み込みは、DbContextを介して無効にすることもできます。
Despertar 2014年

2
実際、これは良い例です。Dapperのようなものを使用するときにこの機能を追加したい場合があるからです。
tbone

41

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

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

以下からのMSDNのLazy <T>クラス

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

結局私は自分のテストを試すことにし、結果をここで共有したいと思いました。残念ながら、私は実際にはこの種のテストを行うことの専門家ではないので、改善を提案するコメントをいただければ幸いです。

説明

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

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

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

テスト結果はmsで測定され、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何も実行しないため、期待どおりにインスタンス化するのが最も高速です。遅延プロパティを設定する際のオーバーヘッドが線形補間計算よりも速いことを示唆するInitLazyよりも、インスタンス化の方InitInterpが高速です。ただし、InitInterp20個の線形補間(tプロパティを設定するため)を実行する必要があるため、ここでは少し混乱していますが、インスタンス化(テスト1)には0.09ミリ秒しかGetInterpかかりません。初回(テスト2)、0.1ミリ秒で2回目(テスト3)。

最初のプロパティを取得するのにInitLazy比べGetInterpて約2倍の時間がかかりますInitInterpが、インスタンス化中にプロパティにデータが入力されるため、最速です。(少なくとも、それは本来実行されるべきことでしたが、インスタンス化の結果が単一の線形補間よりもはるかに速いのはなぜですか?正確にこれらの補間を実行しているのはいつですか?)

残念ながら、私のテストでは自動コード最適化が行われているようです。それは取るべきGetInterp財産、それは二回目と同様に最初の時間を取得するために同じ時間を、それはより速くより2倍として表示されます。この最適化は他のクラスにも影響を与えているようです。それらはすべてテスト3にほぼ同じ時間を費やしているためです。しかし、このような最適化は、私自身の製品コードでも行われる可能性があり、これも重要な考慮事項となる場合があります。

結論

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


26
出力を再現するためにテストコードを投稿する必要があるかもしれません。コードを知らなければ、何も提案するのは難しいでしょう
WiiMaxx

1
主なトレードオフは、メモリ使用量(レイジー)とCPU使用量(レイジーではない)の間だと思います。lazyいくつかの余分な簿記を行う必要があるためInitLazy、他のソリューションよりも多くのメモリを使用します。また、アクセスごとにパフォーマンスにわずかな影響が出る可能性がありますが、すでに値があるかどうかを確認します。巧妙なトリックはそのオーバーヘッドを取り除くことができますが、ILでの特別なサポートが必要になります。(Haskellはこれをすべての遅延値を関数呼び出しにすることで行います。値が生成されると、毎回その値を返す関数に置き換えられます。)
jpaugh

14

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; }
    }
}

レイジーが生まれる前は、次のようにしていたでしょう。

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

6
私は常にIoCコンテナーを使用します。
Jowen、2015

1
このためにIoCコンテナーを検討することに強く同意します。ただし、単純な遅延初期化オブジェクトシングルトンが必要な場合は、これがスレッドセーフである必要がない場合は、Ifを使用して手動で実行することを検討してください。
Thulani Chivandikwa

12

MSDNから:

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

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


-2

遅延読み込みアーキテクチャを理解するには、この例を見る必要があります

private readonly Lazy<List<int>> list = new Lazy<List<int>>(() =>
{
    List<int> configList = new List<int>(Thread.CurrentThread.ManagedThreadId);
    return configList;
});
public void Execute()
{
    list.Value.Add(0);
    if (list.IsValueCreated)
    {
        list.Value.Add(1);
        list.Value.Add(2);

        foreach (var item in list.Value)
        {
            Console.WriteLine(item);
        }
    }
    else
    {
        Console.WriteLine("Value not created");
    }
}

->出力-> 0 1 2

しかし、このコードが「list.Value.Add(0);」と書かない場合

出力->値は作成されません

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.