Desenvolvimento - C#
Novas classes para inicialização de objetos
A Microsoft está disponibilizando no .NET Framework 4.0, uma classe genérica chamada Lazy
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));
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.