Para demonstrar exemplos, usarei a classe Test, que possui uma propriedade BlobData que retorna um objeto do tipo Blob, que, segundo a lenda, é criado muito lentamente, e decidiu-se criá-lo preguiçosamente.
class Test
{
public Blob BlobData
{
get
{
return new Blob();
}
}
}
Verificando nulo
A opção mais simples, disponível nas primeiras versões da linguagem, é criar uma variável não inicializada e testá-la antes de retornar. Se a variável for nula, crie um objeto, atribua-o a essa variável e, em seguida, retorne-o. Em acessos repetidos, o objeto já estará criado e iremos devolvê-lo imediatamente.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
if (_blob == null)
{
_blob = new Blob();
}
return _blob;
}
}
}
Um objeto do tipo Blob é criado aqui quando a propriedade é acessada pela primeira vez. Ou não é criado, se por algum motivo o programa não precisar dele nesta sessão.
Operador ternário ?:
C # tem um operador ternário que permite testar uma condição e, se for verdadeira, retornar um valor e, se for falso, outro. Podemos usá-lo para encurtar e simplificar um pouco o código.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob == null
? _blob = new Blob()
: _blob;
}
}
}
A essência permanece a mesma. Se o objeto não foi inicializado, inicialize e retorne. Se já estiver inicializado, apenas o retornamos imediatamente.
é nulo
As situações são diferentes e nós, por exemplo, podemos encontrar uma em que a classe Blob tenha um operador == sobrecarregado. Para fazer isso, provavelmente precisamos fazer a verificação is null em vez de == null. Disponível nas versões mais recentes do idioma.
return _blob is null
? _blob = new Blob()
: _blob;
Mas é assim, uma pequena digressão.
O operador de coalescência nula ??
O operador binário ?? nos ajudará a simplificar ainda mais o código.
A essência de seu trabalho é a seguinte. Se o primeiro operando não for nulo, ele será retornado. Se o primeiro operando for nulo, o segundo será retornado.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob ?? (_blob = new Blob());
}
}
}
O segundo operando deve ser colocado entre parênteses devido à prioridade das operações.
Operador ?? =
C # 8 introduz um operador de atribuição de coalescência nula que se parece com isto ?? =
Como funciona é o seguinte. Se o primeiro operando não for nulo, ele será simplesmente retornado. Se o primeiro operando for nulo, o valor do segundo será atribuído a ele e esse valor será retornado.
class Test
{
private Blob _blob = null;
public Blob BlobData
{
get
{
return _blob ??= new Blob();
}
}
}
Isso nos permitiu encurtar o código um pouco mais.
Streams
Se houver a possibilidade de que vários threads acessem um determinado recurso ao mesmo tempo, devemos torná-lo seguro para threads. Caso contrário, pode ocorrer uma situação em que, por exemplo, ambos os threads verificarão se o objeto é nulo, o resultado será falso e, em seguida, dois objetos Blob serão criados, carregando o sistema duas vezes mais do que desejamos e, além disso, um destes os objetos serão salvos e o segundo será perdido.
class Test
{
private readonly object _lock = new object();
private Blob _blob = null;
public Blob BlobData
{
get
{
lock (_lock)
{
return _blob ?? (_blob = new Blob());
}
}
}
}
A instrução de bloqueio adquire um bloqueio mutuamente exclusivo no objeto especificado antes de executar certas instruções e, em seguida, libera o bloqueio. É equivalente a usar o System.Threading.Monitor.Enter (..., ...);
Preguiçoso <T>
.NET 4.0 introduziu a classe Lazy para esconder todo esse trabalho sujo de nossos olhos. Agora só podemos deixar uma variável local do tipo Lazy. Ao acessar sua propriedade Value, obtemos um objeto da classe Blob. Se o objeto foi criado anteriormente, ele retornará imediatamente, caso contrário, ele será criado primeiro.
class Test
{
private readonly Lazy<Blob> _lazy = new Lazy<Blob>();
public Blob BlobData
{
get
{
return _lazy.Value;
}
}
}
Como a classe Blob tem um construtor sem parâmetros, o Lazy pode criá-lo no momento certo sem quaisquer perguntas. Se precisarmos realizar algumas ações adicionais durante a criação do objeto Blob, o construtor da classe Lazy pode fazer uma referência ao Func <T>
private Lazy<Blob> _lazy = new Lazy<Blob>(() => new Blob());
Além disso, no segundo parâmetro do construtor, podemos indicar se precisamos de segurança de thread (o mesmo bloqueio).
Propriedade
Agora vamos encurtar a notação da propriedade somente leitura, visto que o C # moderno permite que você faça isso muito bem. No final, tudo se parece com isto:
class Test
{
private readonly Lazy<Blob> _lazy = new Lazy<Blob>();
public Blob BlobData => _lazy.Value;
}
LazyInitializer
Também existe uma opção para não envolver a classe em um wrapper Lazy, mas em vez disso, usar a funcionalidade LazyInitializer. Essa classe tem um método estático EnsureInitialized com um monte de sobrecargas que permitem criar qualquer coisa, incluindo thread safety e escrever código personalizado para criar um objeto, mas a essência principal é a seguinte. Verifique se o objeto não foi inicializado. Caso contrário, inicialize. Retorne um objeto. Usando esta classe, podemos reescrever nosso código assim:
class Test
{
private Blob _blob;
public Blob BlobData => LazyInitializer.EnsureInitialized(ref _blob);
}
Isso é tudo. Obrigado pela atenção.