例を示すために、Blob型のオブジェクトを返すBlobDataプロパティを持つTestクラスを使用します。これは、凡例によれば、作成がかなり遅く、遅延して作成することにしました。
class Test
{
public Blob BlobData
{
get
{
return new Blob();
}
}
}
nullをチェックしています
言語の最初のバージョンから利用できる最も簡単なオプションは、初期化されていない変数を作成し、戻る前にnullをテストすることです。変数がnullの場合は、オブジェクトを作成してこの変数に割り当ててから返します。繰り返しアクセスすると、オブジェクトはすでに作成されており、すぐに返されます。
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
if (_blob == null)
{
_blob = new Blob();
}
return _blob;
}
}
}
タイプBlobのオブジェクトは、プロパティが最初にアクセスされたときにここに作成されます。または、何らかの理由でプログラムがこのセッションでそれを必要としなかった場合は、作成されません。
三元演算子?:
C#には、条件をテストし、それがtrueの場合は1つの値を返し、falseの場合は別の値を返すことができる3値演算子があります。これを使用して、コードを少し短くして単純化することができます。
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob == null
? _blob = new Blob()
: _blob;
}
}
}
本質は同じままです。オブジェクトが初期化されていない場合は、初期化して戻ります。すでに初期化されている場合は、すぐに返します。
無効です
状況は異なり、たとえば、Blobクラスにオーバーロードされた==演算子がある状況に遭遇する可能性があります。これを行うには、おそらく== nullの代わりにisnullチェックを実行する必要があります。言語の最新バージョンで利用できます。
return _blob is null
? _blob = new Blob()
: _blob;
しかし、これはそうです、小さな逸脱。
ヌル合体演算子??
バイナリ演算子??は、コードをさらに単純化するのに役立ちます。
彼の作品の本質は次のとおりです。最初のオペランドがnullでない場合は、それが返されます。最初のオペランドがnullの場合、2番目のオペランドが返されます。
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob ?? (_blob = new Blob());
}
}
}
操作の優先順位のため、2番目のオペランドは括弧で囲む必要がありました。
演算子?? =
C#8では、次のようなnull合体割り当て演算子が導入されています?? =動作
方法は次のとおりです。最初のオペランドがnullでない場合は、単に返されます。最初のオペランドがnullの場合、2番目の値がそれに割り当てられ、この値が返されます。
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob ??= new Blob();
}
}
}
これにより、コードをもう少し短くすることができました。
ストリーム
複数のスレッドが特定のリソースに一度にアクセスできる可能性がある場合は、スレッドセーフにする必要があります。そうしないと、たとえば、両方のスレッドがオブジェクトのnullをチェックし、結果がfalseになり、2つのBlobオブジェクトが作成され、システムに必要な2倍の負荷がかかり、さらにこれらの1つが読み込まれるという状況が発生する可能性があります。オブジェクトは保存され、2番目は失われます。
class Test
{
private readonly object _lock = new object();
private Blob _blob = null;
public Blob BlobData
{
get
{
lock (_lock)
{
return _blob ?? (_blob = new Blob());
}
}
}
}
lockステートメントは、特定のステートメントを実行する前に、指定されたオブジェクトに対する相互に排他的なロックを取得してから、ロックを解放します。System.Threading.Monitor.Enter(...、...);を使用するのと同じです。
怠惰な<T>
.NET 4.0では、このような汚い作業をすべて目から隠すためにLazyクラスが導入されました。これで、Lazyタイプのローカル変数のみを残すことができます。そのValueプロパティにアクセスすると、Blobクラスのオブジェクトを取得します。オブジェクトが以前に作成された場合はすぐに返され、そうでない場合は最初に作成されます。
class Test
{
private readonly Lazy<Blob> _lazy = new Lazy<Blob>();
public Blob BlobData
{
get
{
return _lazy.Value;
}
}
}
Blobにはパラメーターのないコンストラクターがあるため、Lazyは適切なタイミングでそれを作成できます。質問はありません。Blobオブジェクトの作成中に追加のアクションを実行する必要がある場合、LazyクラスのコンストラクターはFunc <T>への参照を取得できます。
private Lazy<Blob> _lazy = new Lazy<Blob>(() => new Blob());
さらに、コンストラクターの2番目のパラメーターで、スレッドの安全性(同じロック)が必要かどうかを示すことができます。
プロパティ
現代のC#ではこれをうまく行うことができるので、今度は読み取り専用プロパティを減らしましょう。結局、それはすべて次のようになります。
class Test
{
private readonly Lazy<Blob> _lazy = new Lazy<Blob>();
public Blob BlobData => _lazy.Value;
}
LazyInitializer
クラスをLazyラッパーでラップせず、代わりにLazyInitializer機能を使用するオプションもあります。このクラスには、スレッドの安全性やオブジェクトを作成するためのカスタムコードの記述など、あらゆるものを作成できる一連のオーバーロードを備えた1つの静的メソッドEnsureInitializedがありますが、その主な本質は次のとおりです。オブジェクトが初期化されていないか確認してください。そうでない場合は、初期化します。オブジェクトを返します。このクラスを使用して、次のようにコードを書き直すことができます。
class Test
{
private Blob _blob;
public Blob BlobData => LazyInitializer.EnsureInitialized(ref _blob);
}
それで全部です。清聴ありがとうございました。