Um Beispiele zu demonstrieren, werde ich die Test-Klasse verwenden, die eine BlobData-Eigenschaft hat, die ein Objekt vom Typ Blob zurückgibt, das der Legende nach ziemlich langsam erstellt wird, und es wurde beschlossen, es träge zu erstellen.
class Test
{
public Blob BlobData
{
get
{
return new Blob();
}
}
}
Auf Null prüfen
Die einfachste Option, die in den ersten Versionen der Sprache verfügbar ist, besteht darin, eine nicht initialisierte Variable zu erstellen und sie vor der Rückkehr auf Null zu testen. Wenn die Variable null ist, erstellen Sie ein Objekt, weisen Sie es dieser Variablen zu und geben Sie es dann zurück. Bei wiederholtem Zugriff wird das Objekt bereits erstellt und wir werden es sofort zurückgeben.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
if (_blob == null)
{
_blob = new Blob();
}
return _blob;
}
}
}
Hier wird beim ersten Zugriff auf die Eigenschaft ein Objekt vom Typ Blob erstellt. Oder es wird nicht erstellt, wenn das Programm es aus irgendeinem Grund in dieser Sitzung nicht benötigt hat.
Ternärer Operator?:
C # verfügt über einen ternären Operator, mit dem Sie eine Bedingung testen und, wenn sie wahr ist, einen Wert zurückgeben können. Wenn sie falsch ist, einen anderen. Wir können es verwenden, um den Code ein wenig zu verkürzen und zu vereinfachen.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob == null
? _blob = new Blob()
: _blob;
}
}
}
Das Wesen bleibt gleich. Wenn das Objekt nicht initialisiert ist, initialisieren Sie es und kehren Sie zurück. Wenn es bereits initialisiert ist, geben wir es sofort zurück.
ist Null
Die Situationen sind unterschiedlich und es kann beispielsweise vorkommen, dass die Blob-Klasse einen überladenen == -Operator hat. Dazu müssen wir möglicherweise die is-Null-Prüfung anstelle von == null durchführen. Verfügbar in den neuesten Versionen der Sprache.
return _blob is null
? _blob = new Blob()
: _blob;
Aber das ist so, ein kleiner Exkurs.
Der Null-Koaleszenz-Operator?
Der binäre Operator wird uns helfen, den Code noch weiter zu vereinfachen.
Das Wesentliche seiner Arbeit ist wie folgt. Wenn der erste Operand nicht null ist, wird er zurückgegeben. Wenn der erste Operand null ist, wird der zweite zurückgegeben.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob ?? (_blob = new Blob());
}
}
}
Der zweite Operand musste aufgrund der Priorität der Operationen in Klammern gesetzt werden.
Betreiber ?? =
C # 8 führt einen Null-Koaleszenz-Zuweisungsoperator ein, der so aussieht ?? =
Wie es funktioniert, ist wie folgt. Wenn der erste Operand nicht null ist, wird er einfach zurückgegeben. Wenn der erste Operand null ist, wird ihm der Wert des zweiten zugewiesen und dieser Wert zurückgegeben.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob ??= new Blob();
}
}
}
Dadurch konnten wir den Code etwas weiter verkürzen.
Streams
Wenn die Möglichkeit besteht, dass mehrere Threads gleichzeitig auf eine bestimmte Ressource zugreifen können, sollten wir sie threadsicher machen. Andernfalls kann es vorkommen, dass beide Threads das Objekt auf Null prüfen, das Ergebnis falsch ist und dann zwei Blob-Objekte erstellt werden, die das System doppelt so oft laden, wie wir wollten, und zusätzlich eines davon Objekte werden gespeichert und das zweite geht verloren.
class Test
{
private readonly object _lock = new object();
private Blob _blob = null;
public Blob BlobData
{
get
{
lock (_lock)
{
return _blob ?? (_blob = new Blob());
}
}
}
}
Die lock-Anweisung erhält eine sich gegenseitig ausschließende Sperre für das angegebene Objekt, bevor bestimmte Anweisungen ausgeführt werden, und gibt dann die Sperre frei. Dies entspricht der Verwendung von System.Threading.Monitor.Enter (..., ...).
Faul <T>
.NET 4.0 führte die Lazy-Klasse ein, um all diese schmutzige Arbeit vor unseren Augen zu verbergen. Jetzt können wir nur eine lokale Variable vom Typ Lazy belassen. Beim Zugriff auf die Value-Eigenschaft erhalten wir ein Objekt der Blob-Klasse. Wenn das Objekt zuvor erstellt wurde, wird es sofort zurückgegeben. Wenn nicht, wird es zuerst erstellt.
class Test
{
private readonly Lazy<Blob> _lazy = new Lazy<Blob>();
public Blob BlobData
{
get
{
return _lazy.Value;
}
}
}
Da die Blob-Klasse einen parameterlosen Konstruktor hat, kann Lazy ihn ohne Fragen zum richtigen Zeitpunkt erstellen. Wenn wir während der Erstellung des Blob-Objekts einige zusätzliche Aktionen ausführen müssen, kann der Konstruktor der Lazy-Klasse auf die Funktion <T> verweisen
private Lazy<Blob> _lazy = new Lazy<Blob>(() => new Blob());
Darüber hinaus können wir im zweiten Parameter des Konstruktors angeben, ob wir Thread-Sicherheit benötigen (dieselbe Sperre).
Eigentum
Lassen Sie uns nun die schreibgeschützte Eigenschaft reduzieren, da Sie mit modernem C # dies gut tun können. Am Ende sieht alles so aus:
class Test
{
private readonly Lazy<Blob> _lazy = new Lazy<Blob>();
public Blob BlobData => _lazy.Value;
}
LazyInitializer
Es gibt auch die Option, die Klasse nicht in einen Lazy-Wrapper zu verpacken, sondern stattdessen die LazyInitializer-Funktionalität zu verwenden. Diese Klasse verfügt über eine statische Methode EnsureInitialized mit einer Reihe von Überladungen, mit denen Sie alles erstellen können, einschließlich Thread-Sicherheit und Schreiben von benutzerdefiniertem Code zum Erstellen eines Objekts. Die Hauptmethode lautet jedoch wie folgt. Überprüfen Sie, ob das Objekt nicht initialisiert ist. Wenn nicht, initialisieren Sie. Ein Objekt zurückgeben. Mit dieser Klasse können wir unseren Code folgendermaßen umschreiben:
class Test
{
private Blob _blob;
public Blob BlobData => LazyInitializer.EnsureInitialized(ref _blob);
}
Das ist alles. Vielen Dank für Ihre Aufmerksamkeit.