LazyInitializerクラスとLazy<T>クラスの違いは何ですか?どちらもオンデマンドでのみオブジェクトを初期化することを私は知っています。それぞれをいつ使用する必要がありますか?
7 に答える
あなたがまだこれを調べているかどうかはわかりませんが、両方Lazy<T>
とLazyInitializer.EnsureInitialized<T>()
最近の詳細を掘り下げる必要があったので、私の発見を共有する必要があると思いました.
まず、いくつかの数字。両方のアプローチを使用して、1,000 万の値のバッチで両方の方法を使用してベンチマークを実行し、インスタンス化、最初の値アクセス、および後続の値アクセスのメモリ使用量をテストし、タイミングをGC.GetTotalMemory(true)
取得しました。Stopwatch
Lazy<T> Memory Use: 320,000,000 bytes (32B/instance)
EnsureInitialized<T>() Memory Use: N/A
Lazy<T> Instantiation Time: 622.01 ms
EnsureInitialized<T>() Inst. Time: N/A
Lazy<T> First Access: 1,373.50 ms
EnsureInitialized<T>() First Access: 72.94 ms
Lazy<T> Subsequent Accesses: 18.51 ms
EnsureInitialized<T>() Subsequent: 13.75 ms
(デフォルトで採用されているのと同じスレッド セーフ アプローチのように見える で使用LazyThreadSafetyMode.PublicationOnly
しました。)Lazy<T>'s
LazyInitializer
ご覧のとおり、テストを何らかの方法で台無しにしない限り (決して問題外ではありません!)、これらの状況LazyInitializer
では、ほぼすべての定量化可能な方法で が優れています。メモリやインスタンス化のオーバーヘッドがなく、値の作成と取得の両方が高速です。
では、なぜ使用したいのでしょうLazy<T>
か? まず、これらは私の x64 システムでのテスト結果であり、他の状況では異なる結果が得られる可能性があります。
Lazy<T>
また、コードをより明確で簡潔にすることもできます。 return myLazy.Value;
よりずっと友好的ですreturn LazyInitializer.EnsureInitialized(ref myValue, () => GetValue(foo));
さらに、Lazy<T>
値型を扱っている場合、または正当にnull
. ではLazyInitializer
、値が初期化されているかどうかを追跡するために 2 番目のブール フィールドを使用する必要があり、コードの明瞭性の問題が悪化します。 Lazy<T>
より厳密なスレッドセーフが必要な場合は、使用も簡単です。
そして、大まかに言えば、オーバーヘッドのほとんどは、おそらく多くのアプリケーションにとって無視できるものです (常にではありませんが、私がこれを調べ始めた理由は、何百万もの非常に小さな遅延ロードされた値を含むアプリケーションで作業していたからです)。 、および のインスタンスあたり 32 バイトのオーバーヘッドは、Lazy<T>
実際には不便になり始めていました)。
最終的には、アプリケーションが非常にメモリ集約的でない限り、通常は個人的な好みの問題になると思います。null 以外の参照型の場合、個人的にLazyInitializer.EnsureInitialized<T>()
はより洗練されたアプローチだと思いますが、コードの明快さの議論も掘り下げることができます。
Lazy<T>
( MSDN ) は、ファクトリ メソッド ( ) を保持し、プロパティ ゲッターがアクセスされたときにそれを呼び出すことT
によって、オンデマンドのインスタンスを作成できる汎用ラッパーです。T
Func<T>
Value
LazyInitializer
- 静的メソッドのセットを持つ静的クラス。これは、特定の型インスタンスをインスタンス化できるActivator.CreateInstance() (リフレクション) を使用する単なるヘルパーです。ローカルのプライベート フィールドを保持せず、プロパティを公開しないため、メモリ使用量のオーバーヘッドはありません。
両方のクラスがFunc<T>
インスタンス ファクトリとして使用することに注意してください。
MSDNは、LazyInitializer
クラスについて簡単に説明します。
これらのルーチンは、専用の遅延初期化インスタンスを割り当てる必要をなくし、代わりに参照を使用して、アクセス時にターゲットが確実に初期化されるようにします。
PS: インスタンスが既に初期化されているかどうかをチェックする興味深い方法を見つけましLazyIntiializer
た。渡された参照を と比較するだけdefault(T)
です。
private static T EnsureInitializedCore<T>(ref T target, Func<T> valueFactory)
where T : class
{
T t = valueFactory();
if (t == null)
{
throw new InvalidOperationException(Environment.GetResourceString("Lazy_StaticInit_InvalidOperation"));
}
Interlocked.CompareExchange<T>(ref target, t, default(T));
return target;
}
私には奇妙に思えますが、実際のチェックの前に毎回新しいインスタンスを作成します:
T t = valueFactory();
// ... and only then does check
他の回答者が言うように、
Lazy<T>
通常、よりクリーンなコードを提供します。アクセスするすべての場所で初期化し
x = new Lazy<T>(_ => new ...)
て使用するだけです。x.Value
複数のスレッドが初期化されていないオブジェクトの
Value
プロパティに同時にアクセスする場合に、初期化と例外を処理するためのさまざまな定義済みオプションを許可します。Lazy<T>
LazyInitializer
スペースを節約し、場合によっては時間も節約
Lazy<T>
します。宣言する変数ごとに新しいオブジェクトを初期化する必要はありません。使用時間まで初期化パラメータの提供を遅らせることができます:
LazyInitializer.EnsureInitialized(ref x, () => new X(initParameters))
結論として、スペース (およびおそらく時間) が限られている場合、または宣言時にすべての初期化パラメーターを指定できない場合にのみ使用する必要があります。LazyInitializer
個人的Lazy<T>
には、よりクリーンなコードが得られ、初期化の例外を自分で明示的に処理する必要がないため、可能な限り好みます。
LazyInitializer
遅延初期化されたオブジェクトごとにクラスを作成するオーバーヘッドなしで、遅延初期化機能を使用できます。
提供できるメリットは次のとおりですLazyInitializer
。
使用によって作成されるオーバーヘッドLazy<T>
が状況に対して多すぎるかどうかは、独自の要件に依存します。
Lazy Initializing のドキュメントでは、かなり明確に説明されています。遅延初期化を参照してください。つまり、使用するすべてのインスタンスに対してLazy<T>
新しいクラス (構築されたジェネリック) を作成し、デカルT
するすべてのインスタンスに対してそのクラスの新しいインスタンスを作成します (基になるものが初期化されていない場合でも) 。の静的メソッドを使用すると、コーディングがより複雑になる可能性がありますが、ラッパー インスタンスのオーバーヘッドを回避できます。T
T
LazyIntializer
Lazy<T>
`LazyInitializer` of an object means its object creation is deferred until it is ued first.
この形式のオブジェクトは、パフォーマンスを向上させ、メモリの浪費を減らすために作成されます。
遅延初期化型を定義するには、クラス Lazy<T>
の (ジェネリック形式)を使用しますLazyInitializer
E.g:
Lazy<Orders> _orders = new Lazy<Orders>();
詳細な参考資料: