Pour illustrer des exemples, j'utiliserai la classe Test, qui a une propriété BlobData qui renvoie un objet du type Blob, qui, selon la légende, est créé assez lentement, et il a été décidé de le créer paresseusement.
class Test
{
public Blob BlobData
{
get
{
return new Blob();
}
}
}
Vérification de null
L'option la plus simple, disponible à partir des premières versions du langage, est de créer une variable non initialisée et de la tester pour la valeur null avant de la renvoyer. Si la variable est nulle, créez un objet et affectez-le à cette variable, puis renvoyez-le. En cas d'accès répété, l'objet sera déjà créé et nous le retournerons immédiatement.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
if (_blob == null)
{
_blob = new Blob();
}
return _blob;
}
}
}
Un objet de type Blob est créé ici lors du premier accès à la propriété. Ou il n'est pas créé si, pour une raison quelconque, le programme n'en a pas eu besoin dans cette session.
Opérateur ternaire?:
C # a un opérateur ternaire qui vous permet de tester une condition et, si elle est vraie, de renvoyer une valeur, et si elle est fausse, une autre. Nous pouvons l'utiliser pour raccourcir et simplifier un peu le code.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob == null
? _blob = new Blob()
: _blob;
}
}
}
L'essence reste la même. Si l'objet n'est pas initialisé, initialisez et retournez. S'il est déjà initialisé, nous le renvoyons simplement immédiatement.
est nul
Les situations sont différentes et nous pouvons, par exemple, en rencontrer une dans laquelle la classe Blob a un opérateur == surchargé. Pour ce faire, nous devrons probablement effectuer la vérification is null au lieu de == null. Disponible dans les dernières versions de la langue.
return _blob is null
? _blob = new Blob()
: _blob;
Mais c'est ainsi, une petite digression.
L'opérateur de fusion nul ??
L'opérateur binaire ?? va nous aider à simplifier encore plus le code.
L'essence de son travail est la suivante. Si le premier opérande n'est pas nul, il est renvoyé. Si le premier opérande est nul, le second est renvoyé.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob ?? (_blob = new Blob());
}
}
}
Le deuxième opérande a dû être placé entre parenthèses en raison de la priorité des opérations.
Opérateur ?? =
C # 8 introduit un opérateur d'assignation à fusion nulle qui ressemble à ceci ?? =
Comment cela fonctionne est comme suit. Si le premier opérande n'est pas nul, il est simplement renvoyé. Si le premier opérande est nul, la valeur du second lui est affectée et cette valeur est renvoyée.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob ??= new Blob();
}
}
}
Cela nous a permis de raccourcir un peu plus le code.
Ruisseaux
S'il y a une possibilité que plusieurs threads puissent accéder à une ressource donnée à la fois, nous devrions la rendre sûre pour les threads. Sinon, une situation peut se produire où, par exemple, les deux threads vérifieront l'objet pour null, le résultat sera faux, puis deux objets Blob seront créés, chargeant le système deux fois plus que nous le voulions, et en plus, l'un de ces les objets seront enregistrés et le second sera perdu.
class Test
{
private readonly object _lock = new object();
private Blob _blob = null;
public Blob BlobData
{
get
{
lock (_lock)
{
return _blob ?? (_blob = new Blob());
}
}
}
}
L'instruction lock acquiert un verrou mutuellement exclusif sur l'objet spécifié avant d'exécuter certaines instructions, puis libère le verrou. Cela équivaut à utiliser System.Threading.Monitor.Enter (..., ...);
Paresseux <T>
.NET 4.0 a introduit la classe Lazy pour cacher tout ce sale boulot à nos yeux. Désormais, nous ne pouvons laisser qu'une variable locale de type Lazy. Lors de l'accès à sa propriété Value, nous obtenons un objet de la classe Blob. Si l'objet a été créé plus tôt, il reviendra immédiatement, sinon, il sera créé en premier.
class Test
{
private readonly Lazy<Blob> _lazy = new Lazy<Blob>();
public Blob BlobData
{
get
{
return _lazy.Value;
}
}
}
Étant donné que la classe Blob a un constructeur sans paramètre, Lazy peut le créer au bon moment sans aucune question. Si nous devons effectuer des actions supplémentaires lors de la création de l'objet Blob, le constructeur de la classe Lazy peut prendre une référence au Func <T>
private Lazy<Blob> _lazy = new Lazy<Blob>(() => new Blob());
De plus, dans le deuxième paramètre du constructeur, nous pouvons indiquer si nous avons besoin de la sécurité des threads (le même verrou).
Propriété
Maintenant, nous allons raccourcir la notation de la propriété readonly, car le C # moderne vous permet de le faire correctement. En fin de compte, tout ressemble à ceci:
class Test
{
private readonly Lazy<Blob> _lazy = new Lazy<Blob>();
public Blob BlobData => _lazy.Value;
}
LazyInitializer
Il existe également une option pour ne pas envelopper la classe dans un wrapper Lazy, mais utiliser à la place la fonctionnalité LazyInitializer. Cette classe a une méthode statique EnsureInitialized avec un tas de surcharges qui vous permettent de créer n'importe quoi, y compris la sécurité des threads et l'écriture de code personnalisé pour créer un objet, mais dont l'essence principale est la suivante. Vérifiez si l'objet n'est pas initialisé. Sinon, initialisez. Renvoie un objet. En utilisant cette classe, nous pouvons réécrire notre code comme ceci:
class Test
{
private Blob _blob;
public Blob BlobData => LazyInitializer.EnsureInitialized(ref _blob);
}
C'est tout. Merci de votre attention.