Desenvolvimento - C#

Novas classes para inicialização de objetos

A Microsoft está disponibilizando no .NET Framework 4.0, uma classe genérica chamada Lazy (namespace System). Basicamente, a finalidade desta classe é postergar, ao máximo, a criação do teu objeto.

por Israel Aéce



Quando desenvolvemos algum tipo de aplicação ou componente, é muito comum encontrarmos dentro do nossocódigo, classes que são extremamente custosas, ou melhor, que possuem um grande overheadna inicialização, como por exemplo, efetuam acesso à IO, cálculos complexos, etc. Dependendo da situação, instanciamos essas classes (pagando o alto preço da inicialização) e não utilizamos, já que, eventualmente, o teu sistema não precisará dela naquele momento.

Para melhorar isso, a Microsoft está disponibilizando no .NET Framework 4.0, uma classe genérica chamadaLazy<T> (namespace System). Basicamente, a finalidade desta classe é postergar, ao máximo, a criação do teu objeto, ou seja, isso somente acontecerá quando você realmente precisar dele. Por ser uma classe genérica e o parâmetro T não ter qualquer restrição, você pode definir T como qualquer tipo. Essa classe será um wrapper para o teu objeto custoso, efetuando a criação do mesmo somente quando for requisitado.

Ao instanciar a classe Lazy<T>, você tem algumas opções que variam de acordo com o overload do construtor que utiliza. Em um dos construtores, há um parâmetro boleano, que determina se a inicialização será ou nãothread-safe. Se a instância da classe Lazy<T> pode ser acessada por um ambiente multi-threading, então definir este valor como True (que é o padrão), evitará problemas conhecidos, talcomo asraces conditions. Com isso,a primeira thread que entrar, incializará o objeto, e as threads subsequentes compartilharão o mesmo objeto, que já está criado. Mas se ambiente multi-threading não é o cenário, então definir esse parâmetro como False evitará processamentos extras, que são desnecessários neste caso.

Há também um overload do construtor, que recebe como parâmetro a instância de um delegate do tipo Func<T>. Esse delegate é referido como uma"factory", ou seja, apontará para um método responsável por criar o objeto quando for solicitado, nos permitindo inicializá-lo de acordo com uma regra específica. Quando esse parâmetro não é informado, a classe Lazy<T> irá instanciar o tipo através do método CreateInstance da classe Activator, obrigando o tipo definido em T, a ter um construtor público sem parâmetros, caso contrário, uma exceção será disparada.

Além dos construtores, essa classe ainda expõe, publicamente, duas propriedades de somente leitura: IsValueCreated e Value. A primeira delas, retorna um valor boleano indicando se o objeto já foi ou não criado. Já a segunda, é a propriedade que utilizamos para extrair o objeto que foir criado e está sendo gerenciado pelo wrapper. É dentro desta propriedade que há toda a regra utilizada para determinar se o objeto já foi criado. E como vimos acima, caso ele ainda não tenha sido, invocará o método privado LazyInitValue, e me retornará a instância. Chamadas subsequentes, da mesma thread ou não, nãoentrarão mais neste método, reutilizando a instância criada. O código abaixo exibe um exemplo da utilização desta classe:

public class NotaFiscal
{
public int Codigo { get; set; }
public DateTime Data { get; set; }

private Lazy<List<Item>> _itens;

public NotaFiscal(int codigo)
{
this.Codigo =codigo;
this._itens =
new Lazy<List<Item>>(() => DataHelper.RecuperarItensDaNotaFiscal(this.Codigo));
}

public IEnumerable<Item> Itens
{
get
{
return this._itens.Value;
}
}
}

Como podemos perceber no código acima, a instância da classe representa uma Nota Fiscal. Muitas vezes carregamos a Nota Fiscal completa, incluindo seus respectivos itens, mas nem sempre eles são utilizados. Ao invés de carregar esses itens na criação da Nota Fiscal, iremos postergar essa tarefa, recorrendo a classe Lazy<T>. Como podemos ter vários itens, então o argumento T será definido como List<Item>. No construtor da classe Nota Fiscal, instanciamos a classe Lazy<List<Item>>, definindo em seu construtor, o método responsável por carregar os itens da Nota Fiscal. A propriedade que expõe os itens da Nota Fiscal, quando solicitada, recorre a propriedade Value do objeto _itens, que como vimos acima, é neste momento que o método (factory)RecuperarItensDaNotaFiscal será disparado.

Além da classe Lazy<T>, ainda temos a classe ThreadLocal<T> (namespace System.Threading). Assim como a anterior, não há nenhuma restrição quanto ao argumento T, ou seja, podemos definir qualquer tipo. A finalidade desta classe, é sanar alguns comportamentos de campos estáticos quando utilizados em conjuto com o atributo ThreadStaticAttribute. Ao aplicar esse atributo, cada thread terá a sua própria cópia do valor, mesmo que ele seja declarado como estático (static). O problema que ocorre ao utilizar esse atributo, é quando temos um campo que já é automaticamente inicializado, como por exemplo:

public class Teste
{
[ThreadStatic]
publicstatic int Numero = 4;
}

A inicialização de todo membro estático ocorre apenas uma única vez, mesmo quando temos este atributo aplicado. Para ilustrar isso, vamos criar três threads diferentes, onde cada uma delas escreverá o valor do membro Numero:

new Thread(() => Console.WriteLine(Teste.Numero)).Start();
new Thread(() => Console.WriteLine(Teste.Numero)).Start();
new Thread(() => Console.WriteLine(Teste.Numero)).Start();

E o resultado é: 4, 0 e0. A classe ThreadLocal<T> vai conseguir lidar com isso, ou seja, permitirá especificar um delegate de inicialização, que será invocado sempre que o valor for requisitado por uma nova thread. Ao invés da inicialização acontecer uma única vez, ele sempre rodará quando solicitado, e sempre trazendo a cópia da informação para dentro da thread corrente, não compartilhando o mesmo, assim como já acontecia anteriormente. Com essa nova classe, podemos reescrever o exemplo da seguinte forma:

public class Teste
{
public static ThreadLocal<int> Numero = new ThreadLocal<int>(() => 4);
}

new Thread(() => Console.WriteLine(Teste.Numero.Value)).Start();
new Thread(() => Console.WriteLine(Teste.Numero.Value)).Start();
new Thread(() => Console.WriteLine(Teste.Numero.Value)).Start();

E agora, como já era de se esperar, temos como resultado: 4, 4 e 4. É importante dizer que se nada for informado no construtor desta classe, ela sempre construirá o tipo com o seu valor padrão.

Para finalizar, temos a classe estática LazyInitializer, que possui apenas um único método público: EnsureInitialized. Neste caso, ao invés de definir todos os membros como Lazy<T>, podemos recorrer a este método para inicializá-los individualmente, quando você achar necessário, sem a necessidade do wrapper. O código abaixo ilustra a utilização desta técnica, mas repare que informamos o objeto que será abastecido e o método (factory) que o construirá.

public IEnumerable<Item> Itens
{
get
{
if (this._itens == null)
LazyInitializer.EnsureInitialized(ref _itens, () => DataHelper.RecuperarItensDaNotaFiscal(this.Codigo));

return this._itens;
}
}

Conclusão: Criar aplicações multi-threading é bem simples, mas o grande problema sempre é a sincronização delas. Classes como essas que vimos aqui, auxilia bastante neste caso, tirando em algum pontos, a responsabilidade do usuário em gerenciar isso, podendo ele se preocupar cada vez mais com as regras de negócio. Essas classes que vimos aqui,estarão disponíveis a partir da versão4.0 do.NET Framework, que já trará também uma grande API para suportar o desenvolvimento de código paralelo.

Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.