.NET Reactive Framework

A Microsoft trabalha atualmente em um projeto chamado de .NET Reactive Framework (Rx). Em uma linguagem como o C#, este framework ajudará, ou melhor, mudará a forma com a qual escrevemos código para lidar com eventos e também com a programação assíncrona, que tem as características de reagirem quando algo acontece, por exemplo, quando o evento Click ocorre dentro da classe Button, um método é disparado para que a nossa aplicação reaja a ele; na programação assíncrona, utilizamos delegates para especificarmos callbacks, ou seja, quando o processo assíncrono finalizar, dispare o método “XPTO”.

Quando dizemos que teremos uma outra forma de trabalhar depois deste framework, é porque ele permitirá o que chamamos de LINQ To Events. Isso quer dizer que poderemos, de forma declarativa, definir uma sequência/combinação de eventos que estamos interessados, e quando ele(s) acontecer(em), nós seremos notificados. Antes de visualizar códigos que mostram isso, vamos recapitular como o Linq To Objects trabalha: basicamente tudo é baseado nas interfaces IEnumerable<T> e IEnumerator<T>, que utilizam o conceito de “pull” (puxar) para extrair os dados, iterando por alguma fonte de dados de forma síncrona.

Erik Meijer, que é o inventor do Rx Framework, descobriu que toda sequência que trabalha no formato “pull”, também pode trabalhar no formato “push” (empurrar), ou seja, são matematicamente denominadas como “Dual”. Para suportar a programação reativa, não podemos continuar utilizando as mesmas interfaces, justamente porque elas bloqueiam até que um próximo resultado esteja disponível. Sendo assim, a Microsoft introduziu duas novas interfaces: IObservable<T> e IObserver<T>. Através destas interfaces, você assinará uma coleção/tarefa qualquer, e quando uma nova informação estiver disponível, você será notificado, reagindo à este item.

Já vi comentários que isso será parte integrante ao .NET Framework 4.0, mas os tipos necessários para fazer isso tudo funcionar, estão contidos no assembly System.Reactive.dll, que vem com o Toolkit do Silverlight 3.0. A primeira interface, IObservable<T>, possui apenas um método chamado Subscribe. Este método recebe como parâmetro a instância de uma classe que implementa a interface IObserver<T>, que possui três métodos autoexplicativos: OnCompleted, OnError e OnNext. Em um exemplo simples, vamos percorrer uma coleção de números inteiros através desta nova forma. O primeiro passo é criar o observer:

public class VerificadorDeItens : IObserver<int>
{
    public void OnCompleted()
    {
        Console.WriteLine(“Acabou”);
    }

    public void OnError(Exception exception)
    {
        Console.WriteLine(“Erro”);
    }

    public void OnNext(int value)
    {
        Console.WriteLine(value);
    }
}

Depois dele devidamente criado, precisamos criar o “observable”. Ao invés de fazermos isso explicitamente, a Microsoft também disponibilizou uma classe chamada Observable, que está dentro namespace System.Linq. Essa classe fornece uma série de métodos de extensão, para transformar grande parte dos operadores LINQ em “observables”. Um desses métodos, ToObservable, é responsável por transformar um IEnumerable<T> em um IObservable<T>. Sendo assim, para percorrer um array de números inteiros no formato “push”, fazemos:

int[] ids = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
ids.ToObservable().Subscribe(new VerificadorDeItens());

O que vimos acima, é somente um exemplo da diferença entre os modelos “pull” e “push”, mas o mais interessante é a capacidade de manipular, ou melhor, de reagir à eventos. Ao invés de criar os tratadores (event handlers) tradicionais, você poderá “assinar” esse evento, e quando ele acontecer, você será notificado. Veja o exemplo abaixo:

Observable
    .FromEvent<EventArgs>(this.button1, “Click”)
    .Subscribe(() => MessageBox.Show(“Ocorreu o evento Click”));

Um ponto importante aqui é que o método Subscribe retorna a instância de uma classe que implementa a interface IDisposable, que poderá ser utilizada mais tarde, quando não quisermos mais ser notificados. Abaixo temos o mesmo exemplo, só que se descartando as notificações quando não precisamos mais delas, e para isso, tudo o que precisamos fazer é invocar o método Dispose:

IDisposable dispose =
    Observable
        .FromEvent<EventArgs>(this.button1, “Click”)
        .Subscribe(() => MessageBox.Show(“Ocorreu o evento Click”));

//faz algo aqui, e quando não estiver mais
//interessado nas notificações, invoque o
//método Dispose.
dispose.Dispose();

Tudo isso visa, principalmente, a capacidade que teremos que efetuar queries LINQ em eventos, que como falei acima, permite vincular à eventos, e na medida com que eles forem sendo disparados, você será notificado de forma assíncrona. Para isso, vamos imaginar que temos duas classes diferentes, onde cada uma delas possui um evento específico, e um método que executa alguma tarefa, e quando necessário, invoca o respectivo evento. Abaixo temos as duas classes que utilizaremos como exemplo. Repare que não há nada de especial:

public class Classe1
{
    public event EventHandler MeuEvento;

    public void Executar()
    {
        if (this.MeuEvento != null)
            this.MeuEvento(this, EventArgs.Empty);
    }
}

public class Classe2
{
    public event EventHandler MeuOutroEvento;

    public void Executar()
    {
        if (this.MeuOutroEvento != null)
            this.MeuOutroEvento(this, EventArgs.Empty);
    }
}

Com essas classes criadas, vamos então utilizar o LINQ para interagir com os eventos:

Classe1 c1 = new Classe1();
Classe2 c2 = new Classe2();

IObservable<Event<EventArgs>> obsC1 =
    Observable.FromEvent<EventArgs>(c1, “MeuEvento”);

IObservable<Event<EventArgs>> obsC2 =
    Observable.FromEvent<EventArgs>(c2, “MeuOutroEvento”);

var q =
    from in obsC1
    from y in obsC2
    select new Unit();

q.Subscribe(() => MessageBox.Show(“Os eventos aconteceram!”));

c1.Executar();
c2.Executar();

Depois de instanciado a classe, utilizamos o método FromEvent, que dado o objeto e o nome do evento, cria um “observable” para o evento especificado. Com eles criados, o próximo passo é escrever a query LINQ, onde aninharemos os dois eventos na mesma query. Assim como toda query LINQ To Objects retorna um IEnumerable<T>, LINQ To Events retorna um IObservable<T>. Com isso, podemos utilizar o método Subscribe, para sermos notificados quando ambos eventos acontecerem. Assim que código executa o método “Executar” de “c1” e “c2”, a mensagem será exibida. Se comentarmos qualquer um dos métodos “Executar”, a mensagem não será disparada, pois de acordo com a query, nós estamos interessados em sermos notificados somente quando ambos eventos acontecerem.

Além de tudo isso, o .NET Reactive Framework ainda facilita muito a programação assíncrona. Sempre quando precisamos executar alguma tarefa em uma thread isolada, precisamos criar delegates, iniciar a tarefa através do método BeginInvoke, apontar (também via delegate) para o método de callback e, finalmente, recuperar o resultado utilizando o método EndInvoke. Isso é extremamente trabalhoso, e a complexidade aumenta ainda mais quando não se conhece exatamente como isso funciona. Suponhamos que temos a seguinte tarefa ser realizada:

public int Tarefa(int tempo)
{
    //Tarefa Custosa

    tempo = tempo * 1000;
    Thread.Sleep(tempo);
    return tempo;
}

Ao invés de criar toda aquela parafernalha para invocar e receber o resultado assincronamente, utilizando este novo framework, tudo o que precisamos fazer é:

new Func<int, int>(Tarefa)
    .ToAsync()
    .Invoke(3)
    .Subscribe((tempo) => { MessageBox.Show(“Tempo Esperado: ” + tempo.ToString()); });

No código acima, instanciamos o delegate Func<TResult, T> (que é bem conhecido), especificando que ele receberá e retornará um número inteiro. A partir daí, entra em cena o método de extensão chamado ToAsync (que foi aplicado aos delegates), que retorna uma versão assíncrona do mesmo delegate, e como já vimos acima, utilizamos o método Subscribe para criar o “observable”, que nos trará o resultado do processo, isso quando estamos interessados nele.

Esses são apenas alguns – pequenos – exemplos de alguns cenários que podemos utilizar esta novo framework, mas já podemos notar o quanto ganhamos em certas situações, como é o caso da programação assíncrona. Há muito ainda o que se explorar e, provavelmente, isso tende a evoluir cada vez mais, tornando assim, a programação, ou melhor, o C#, cada vez mais funcional.

Adicionando Membros em Tempo de Execução

Como sabemos, o C# 4.0 (que virá junto com o Visual Studio .NET 2010) já trará alguns aspectos de linguagens dinâmicas, que permitem a avaliação/checagem de um membro somente em tempo de execução. Para suportar esta funcionalidade, um novo “tipo” foi criado, conhecido como “dynamic”. Ao declarar uma variável do tipo “dynamic”, a checagem de existência de um determinado membro não acontecerá estaticamente, ou seja, independentemente se ele exista ou não, você somente saberá isso em tempo de execução.

Com este tipo especial, você somente conseguirá acessar os membros que, eventualmente, já existam. Mas e como você pode proceder, se quiser construir um tipo dinamicamente? É aqui que entra em cena a classe ExpandoObject (namespace System.Dynamic). Ao instanciar essa classe, você poderá criar tipos dinamicamente, definindo novas propriedades e métodos para este tipo. Para que isso funcione adequadamente, você precisará atribuir a instância desta classe à uma variável do tipo “dynamic”, caso contrário, como vimos acima, a checagem será efetuada durante a compilação, o que não permitirá a compilação. O código abaixo ilustra a utilização desta nova classe:

dynamic obj = new ExpandoObject();
obj.Nome = “Israel”;
obj.Endereco = new ExpandoObject();
obj.Endereco.Cidade = “Valinhos”;
obj.AlgumMetodo = new Action<string>(s => Console.WriteLine(s));

Console.WriteLine(obj.Endereco.Cidade);
obj.AlgumMetodo(“Teste”);

Como podemos notar, instanciamos a classe ExpandoObject para uma variável definida como dinâmica. Depois disso, quando queremos criar “sub-tipos”, basta instanciar uma nova classe ExpandoObject para ele, e seguir criando os novos membros a partir dali. Métodos também podem ser criados, e você pode recorrer a algum delegate já existente para definir a ação a ser executada quando ele for invocado.

Na verdade, esses membros não são incluídos dentro da classe ExpandoObject. A medida que você vai criando estes membros, ele irá armazenando em um dicionário, e quando requisitado, extrai e o executa. Essa funcionalidade é disponibilizada através da Interface IDynamicMetaObjectProvider. Acredito que a performance deva ser mais lenta do que o binding estático, mas é um preço que se deve pagar quando quiser interoperar com linguagens dinâmicas que, além da interoperabilidade com o mundo COM é, ao meu ver, os principais cenários para códigos deste tipo.

Movendo tipos entre Assemblies

Muitas vezes, quando estamos desenvolvendo algum componente, geralmente colocamos todos os tipos (classes, estruturas, interfaces, etc.) dentro do mesmo assembly, sem nos preocuparmos com possíveis reutilizações de tipos que, futuramente, possam acontecer. Uma vez que temos esse componente pronto, o referenciamos em diversas aplicações, que farão uso dos tipos que o mesmo fornece.

Mais tarde, você começa a notar a necessidade de precisar fazer uso destes tipos em outros assemblies que você venha a criar. Sim, referenciar esse primeiro assembly, que contém todos os tipos, resolveria o problema, mas você pode estar referenciando assemblies com tipos que, na verdade, não deveriam estar a disposição de certas aplicações.

Refatorar isso, levando esses tipos para outros assemblies é uma tarefa relativamente fácil, pois basta você tirar de um e colocar noutro. O problema é manter a compatibilidade com as aplicações que já usam esses tipos que serão movidos. Imagine que você tenha um componente chamado Componente1.dll com uma classe chamada DalHelper. Uma aplicação Windows (App.exe) referencia esse assembly e faz uso da classe DalHelper. E como falmos acima, mais tarde você achou necessário mover a essa classe para um outro assembly (Componente2.dll), que ficará mais coerente e facilitará a reutilização.

Se você remover a classe DalHelper, as aplicações que dependem daquela classe dentro do assembly Componente1.dll, irão quebrar. É importante dizer que a idéia aqui é você fazer essa alteração (mover o(s) tipo(s) entre assemblies), sem precisar redistribuir/recompilar as aplicações que já utilizam o Componente1.dll. E para resolver isso, utilizamos o atributo TypeForwardedToAttribute (namespace System.Runtime.CompilerServices), disponível a partir do .NET Framework 2.0. Quando aplicado este atributo em um assembly, ele redirecionará a requisição de um determinado tipo para algum outro lugar, outro assembly. Para ilustrarmos, imagine a seguinte situação:

[Componente1.dll]
namespace Common
{
    public class DalHelper
    {
        public string Execute() { }
    }
}

[App.exe]
Common.DalHelper dal = new Common.DalHelper();
MessageBox.Show(dal.Execute());

O que vamos fazer agora é criar um outro assembly (Componente2.dll), e mover a classe DalHelper para dentro ele, removendo essa classe do Componente1.dll. O código abaixo exibe a estrutura do assembly recém criado.

[Componente2.dll]
namespace Common
{
    public class DalHelper
    {
        public string Execute() { }
    }
}

Isso não basta, e quebrará as referências que existem para essa classe. No Componente1.dll ainda precisamos, explicitamente, direcionar a requisição desta classe, utilizando o atributo TypeForwardedToAttribute. Lembre-se que devemos referenciar o Componente2.dll no Componente1.dll. O código abaixo mostra como utilizar esse atributo, e note que a classe DalHelper que está referenciada está, agora, no Componente2.dll.

[assembly: TypeForwardedTo(typeof(Common.DalHelper))]

Para aquelas aplicações que referenciam o DalHelper do Compenente1.dll, ao enviar o Componente1.dll e Componente2.dll, você pode simplesmente substituir o Componente1.dll que não quebrará o binding, já que quando a requisição para a classe DalHelper chegar à ele, ele sabe onde estará esse tipo, e encaminhará para ele, e como podemos perceber, as aplicações continuam confiando apenas no Componente1.dll.

Podemos perceber que isso ajuda imensamente para refatorar o código e, principalmente, manter a compatibilidade com as aplicações que já fazem uso desses tipos. Novas aplicações que precisarem da classe DalHelper, tudo o que elas precisam fazer é referenciar apenas o assembly Componente2.dll. Mais fácil do que isso é tentar prever essa reusabilidade, criando os tipos separadamente antes mesmo de fazer a distribuição inicial.

Métodos Sincronizados

Quando criamos uma classe e dentro dela colocamos métodos, estes podem ser acessados por múltiplas threads ao mesmo tempo. Caso você queira prevenir isso, ou seja, permitir que somente uma única thread por vez o acesse, você pode fazer uso de métodos sincronizados. Tudo o que você precisa fazer para isso funcionar, é decorar o método com o atributo MethodImplAttribute (namespace System.Runtime.CompilerServices), definindo em seu construtor a opção Synchronized, fornecida pelo enumerador MethodImplOptions, assim como é mostrado abaixo:

public class Processo
{
    [MethodImpl(MethodImplOptions.Synchronized)]
    public void Metodo()
    {
        //…
    }
}

Com isso, você terá a garantia de que somente uma thread por vez o executará, enquanto as outras que, eventualmente, cheguem a este mesmo método, ficarão em uma fila, esperando com que a thread atual finalize o seu trabalho.

Apesar de “proteger” as informações, você limitará o acesso à sua classe/tipo. Quando este atributo é aplicado à um método de instância, ele efetuará o bloqueio da instância como um todo (lock(this) { }); já se aplicá-lo em um método estático, ele bloqueará o tipo (lock(typeof(Processo)) { }). A implementação é fácil, mas você será prejudicado com os efeitos colaterais, pois terá maior contenção. Para melhorar o throughput, o ideal é você utilizar alguma técnica de locking que o próprio .NET Framework fornece, para assim refinar o bloqueio da sua classe/tipo.

Método Zip

Um novo método (de extensão) foi adicionado à classe Enumerable na versão 4.0 do .NET, chamado de Zip. Dado duas coleções/arrays, esse método tem a finalidade de aplicar uma espécie de “zíper” entre eles, ou seja, agrupando os elementos correntes de cada coleção, onde o resultado é a combinação entre esses dois elementos. Para exemplificar, considere os dois arrays de inteiros:

int[] pares = new int[] { 0, 2, 4, 6, 8 };
int[] impares = new int[] { 1, 3, 5, 7, 9 };

foreach
(var item in impares.Zip(pares, (i, p) => string.Format(“{0}, {1}, “, p, i)))
    Console.Write(item);

O resultado ficará: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. O processamento se encerra quando não for encontrado um elemento “correspondente” na outra coleção.

Persistência de dados em aplicações Silverlight

Grande parte das aplicações Web, independentemente de qual tecnologia ela foi criada, tem a necessidade de manter informações por um determinado tempo. Essas informações, na maioria das vezes, refletem alguma configuração definida pelo cliente, que customiza alguma seção do aplicativo, e quer manter isso em algum local para não precisar refazê-la mais tarde.

Quando estamos falando em uma aplicação desenvolvida em ASP.NET, você utiliza algumas funcionalidades definidas pela tecnologia, tais como Session, Caching, Profile, ViewState ou Cookies, para atingir esse objetivo. A escolha entre qual utilizar, vai depender do escopo, performance e nível de segurança. Esses detalhes devem ser cuidadosamente analisados, tentando equilibrar cada um dos pontos mencionados.

Aplicações construídas em Silverlight também tem a mesma necessidade, precisando, de alguma forma, armazenar informações para serem utilizadas posteriormente. Como sabemos, o Silverlight possui uma versão do .NET Framework exclusiva, trazendo várias APIs para o gerenciamento de estado. Como o aplicativo desenvolvido em Silverlight está rodando no navegador, você não tem acesso à variáveis de sessão, Caching ou Profile, já que estão acessíveis apenas pelo ASP.NET e do lado do servidor. Devido a isso, a Microsoft disponibilizou no Silverlight o Isolated Storage. Esse recurso não é exclusidade do Silverlight, e já está disponível no .NET Framework desde a sua primeira versão, e foi reescrito para o Silverlight, ganhando alguns novos conceitos.

Com o Isolated Storage podemos montar uma estrutura de arquivos para a aplicação, armazenando qualquer tipo de informação, desde um simples arquivo texto até um arquivo mais complexo (como a serialização de um objeto em formato binário/Xml). Do ponto de vista do desenvolvedor, temos uma estrutura para armazenamento das informações bem próximo ao que é o sistema de arquivos. O Isolated Storage é como os cookies (armazenar do lado do cliente), mas ao invés de armazenar somente chave/valor (strings), é um sistema de arquivos virtual, dando muito mais flexibilidade e segurança.

Apesar de uma aplicação Silverlight também ser acessada e executada através de um navegador, ela diferente bastante de uma aplicação Web tradicional. Ao contrário dessas aplicações tradicionais, que são executadas no servidor e apenas o HTML correspondente é enviado ao cliente, aplicações Silverlight são consideradas parcialmente confiáveis, e rodam dentro do navegador em um ambiente conhecido como “sandboxing”, que protegem o computador do cliente de aplicações maliciosas, evitando que aplicações Silverlight acessem recursos da máquina, compromentendo-a.

Antes de trabalharmos com o Isolated Storage, precisamos conhecer como funciona o nível de isolamento que ele fornece. O isolamento permitirá definirmos o escopo de acesso de uma determinada informação. O Silverlight possui dois níveis de isolamento: usuário + aplicação ou usuário + site. O primeiro deles irá isolar as informações pelo usuário dentro da aplicação (XAP), estando acessíveis somente a partir da mesma. Já a segunda opção, irá isolar as informações pelo usuário dentro do site, e todas as aplicações Silverlight que estão acessíveis a partir do mesmo site (domínio), irão compartilhar a informação de acordo com o usuário. É importante ficar claro que cada usuário tem dois locais de armazenamento, sendo um deles exclusivo para uma determinada aplicação, enquanto o outro pode ser acessado de qualquer aplicação, desde que esteja dentro daquele mesmo site.

O local de armazenamento e leitura dos arquivos estão condicionados ao usuário que está acessando a aplicação. Isso quer dizer que o Silverlight somente poderá acessar (para leitura ou gravação) o diretório local, que estará localizado debaixo do perfil do usuário atual. Esse caminho poderá variar de acordo com o sistema operacional, onde no Windows Vista está em C:Users<Usuario>AppDataLocaLowMicrosoftSilverlightis e no Windows XP em C:Docuemnts and Settings<Usuario>Local SettingsApplication DataMicrosoftSilverlightis. É importante dizer que você não precisa se preocupar com o endereço, pois ao utilizar o Isolated Storage, ele se encarregará de salvar ou ler as informações do local correto, não permitindo que uma aplicação maliciosa acesse, de forma arbitrária, o sistema de arquivos da máquina cliente, comprometendo a segurança.

Depois da parte teórica, vamos analisar a API que temos disponível para manipular o Isolated Storage. Basicamente temos quatro classes a nossa disposição, estando todas elas debaixo do namespace System.IO.IsolatedStorage, mas separadas em dois assemblies diferentes, sendo o mscorlib.dll e o System.Windows.dll.

A primeira classe que temos é a IsolatedStorageFile. A finalidade desta classe é representar uma das areas disponíveis no Isolated Storage para um determinado usuário, abstraindo o sistema de arquivos virtual que ele nos fornece. Essa classe não pode ser instanciada diretamente; ao invés disso, ela possui dois métodos estáticos chamados GetUserStoreForApplication e GetUserStoreForSite, quais retornam instâncias da mesma, com as respectivas áreas de armazenamento. A partir da instância desta classe, podemos criar, excluir ou enumerar diretórios e arquivos. Para efetuar essas tarefas, essa classe fornece métodos autoexplicativos, tais como: CreateDirectory, DeleteDirectory, GetFilesNames, GetDirectoryNames, DeleteFile e CreateFile.

Os métodos para recuperar arquivos ou diretórios pode, opcionalmente, receber uma string, representando um critério de busca, especificando um subdiretório, ou até mesmo utilizar o caracter “*”, para refinar ainda mais a busca. O método CreateFile cria o arquivo desejado e retorna uma instância da classe IsolatedStorageFileStream, representando o arquivo recém-criado, para que você consiga gravar ou ler as informações do mesmo. O exemplo abaixo ilustra como proceder para criar e ler um arquivo dentro do repositório de uma aplicação específica:

//Criando o Arquivo
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
    using (IsolatedStorageFileStream stream = store.CreateFile(“Teste.txt”))
        using (StreamWriter sw = new StreamWriter(stream))
            sw.WriteLine(“Teste”);

//Lendo o Arquivo
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
    using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(“Teste.txt”, FileMode.Open, store))
        using (StreamReader sr = new StreamReader(stream))
            MessageBox.Show(sr.ReadToEnd());

Observação: Os exemplos acima não estão contemplando o tratamento de erros. Todas as operações que se faz com o Isolated Storage, você deve envolver em um bloco try/catch, monitorando por possíveis exceções do tipo IsolatedStorageException. Um cenário onde ela pode ser lançada, é quando o usuário desabilita o acesso ao Isolated Storage ou atinge a cota máxima.

Cotas

Imagine que uma determinada aplicação possui um código malicioso, que cria arquivos sem nenhum controle. Em breve, não haverá mais espaço disponível no disco do cliente, que foi todo tomado pela aplicação. Felizmente o Isolated Storage disponibiliza o conceito de cotas. As cotas são um recurso bastante interessante, garantindo que a quantidade de informações geradas pela aplicação não ultrapasse um limite imposto pelo Silverlight. O Silverlight utiliza o conceito de grupos para gerenciar as cotas. Com isso, aplicações oriundas de um mesmo site, compartilham a mesma cota.

Por padrão temos 1MB de espaço disponível para cada grupo. Com uma aplicação Silverlight rodando no navegador, ao clicar com o botão direito, você tem a opção Silverlight Configuration. Ao abrir a janela, haverá uma aba chamada Application Storage. Lá temos a relação dos sites que estão fazendo uso deste recurso, exibindo o tamanho atual e o máximo permitido. Essa tela de configuração ainda permite você gerenciar o Isolated Storage, como excluí-los ou até mesmo desabilitá-lo. A imagem abaixo exibe essas configurações:

A partir da instância da classe IsolatedStorageFile temos acesso à alguns membros, que permitem manipular as cotas. O primeiro deles é a propriedade AvailableFreeSpace, que como o próprio nome diz, retorna a quantidade de espaço disponível para aquela “store”. Ainda temos a propriedade Quota, que representa a quantidade máxima permitida para aquela “store”.

A idéia do Isolated Storage é armazenar poucas informações, e justamente por isso, que 1MB é um valor razoável. Mas e se precisarmos de um tamanho maior para armazenar as informações que estão sendo geradas pelas aplicações? É neste momento que entra em cena o método IncreateQuotaTo. Este método recebe como parâmetro a nova cota, retornando um valor boleano indicando se a alteração foi ou não realizada com sucesso. Isso se deve ao fato de que somente podemos aumentar a capacidade de armazenamento do Isolated Storage, com o consentimento do usuário. Ao chamar esse método, uma mensagem aparece na aplicação (como mostrado na figura abaixo), informando o usuário se ele permite ou não esse crescimento.

O código abaixo ilustra como podemos proceder para efetuar o aumento da capacidade do Isolated Storage para 10MB, utilizando os membros que vimos acima:

int space = 1024 * 1024 * 10;

using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
    if (store.AvailableFreeSpace < space)
        if (store.IncreaseQuotaTo(space))
            MessageBox.Show(“Cota aumentada com sucesso.”);

Persistindo Objetos

Em algumas situações podemos querer armazenar instâncias de classes que a aplicação manipula, para mais tarde recuperá-las e dar prosseguimento ao que usuário estava fazendo, evitando que ele refaça essas tarefas. Para atingir esse objetivo, basta serializarmos o objeto em formato binário ou Xml com os formatadores que já conhecemos, em conjunto com o Stream gerado pelo Isolated Storage.

Como isso será uma tarefa comum, a Microsoft facilitou a vida dos desenvolvedores, criando uma classe chamada de IsolatedStorageSettings (a única que está contida no Assembly System.Windows.dll). Essa classe nada mais é do que um dicionário de dados, onde a chave é do tipo string e o valor do tipo object, ou seja, podemos armazenar a instância de qualquer objeto do .NET (desde que ele seja serializável).

Essa classe não pode ser instanciada diretamente pela aplicação. Ao invés disso, você captura as instâncias predefinidas a partir de duas propriedades estáticas que ela mesma fornece: ApplicationSettings e SiteSettings. Escolher qual delas usar, irá depender do nível de compartilhamento das informações que você desejar, assim como já vimos acima.

Para manipular esse dicionário, podemos utilizar os conhecidos métodos Add e o indexer, que dado um chave retorna o respectivo valor. Se informar uma chave inexistente para o indexer, uma exceção será disparada. Para evitar isso, essa mesma classe fornece um método chamado TryGetValue, que dado a chave e um parâmetro de saída representando o valor correspondente, retorna um valor boleano indicando se foi ou não possível recuperar o valor. Esse procedimento é mostrado através do código a seguir:

IsolatedStorageSettings store = IsolatedStorageSettings.ApplicationSettings;
store.Add(“Cliente”, new Cliente() { Email = “ia@israelaece.com”, Nome = “Israel Aece” });

Cliente cliente = null;

if (store.TryGetValue<Cliente>(“Cliente”, out cliente))
    MessageBox.Show(cliente.Nome);

Conclusão: Vimos no decorrer deste artigo os benefícios e facilidades que temos ao trabalhar com ele, mas devemos levar em consideração que essas informações ficam armazenadas no disco, e alguém pode encontrar os arquivos gerados e manipulá-los, comprometendo a aplicação. Justamente por isso, nunca armazene informações sensíveis dentro dele, ou de qualquer arquivo no disco, a menos que esteja devidamente protegido.

PersistenciaDeDadosEmSL.zip (63.76 kb)

Mudança na API de Globalização

Uma novidade que estará presente no .NET Framework 4.0 é a mudança no mecanismo de recuperação das culturas. Até a versão anterior, utilizamos o método estático GetCultures da classe CultureInfo. Esse método retorna um array de objetos do tipo CultureInfo, onde cada elemento representa uma cultura específica. Todas as informações são retornadas a partir de um repositório que está contido no .NET Framework.

A partir do .NET Framework 4.0, a Microsoft efetuou uma mudança, onde não haverá mais essa “tabela interna”, que era responsável por armazenar essas informações. Quando você estiver rodando uma aplicação em cima do Windows 7, as culturas serão extraídas diretamente do sistema operacional. Já quando rodar em versões anteriores ao Windows 7, as informações carregadas serão as mesmas do repositório interno do .NET Framework.

Com essa nova mudança, as opções WindowsOnlyCultures e FrameworkCultures do enumerador CultureTypes estão obsoletas. Utilizar o WindowsOnlyCultures não retornará nenhuma informação, enquanto o FrameworkCultures retornará os mesmos resultados do .NET Framework 2.0.

Capacidade do StringBuilder

A classe StringBuilder é largamente utilizada por aplicações .NET, e está presente desde a primeira versão do .NET Framework, debaixo do namespace System.Text. Como todos sabemos, essa classe representa uma string mutável, ou seja, ela pode sofrer inserções de novas strings, removê-las ou até mesmo a substituição de caracteres, de forma mais mais simples e performática quando comparada à classe String.

Imagine um cenário onde você tem um laço de repetição e que vai adicionando “linhas” de N caracteres no objeto StringBuilder. Algo mais ou menos como vemos abaixo:

StringBuilder sb = new StringBuilder();
string linha = new string(‘x’, 400);

for (int i = 1; i < 21; i++)
    sb.AppendLine(linha);

Em princípio, tudo funciona muito bem, mas ainda há muito o que melhorarmos. Ao acrescentar caracteres dentro dele, o StringBuilder verifica se o array interno que armazena os caracteres está tentando aumentar além de sua capacidade. Se sim, automaticamente dobrará o tamanho dessa capacidade, alocando um novo array e copiando os caracteres anteriores para este novo e, consequentemente, o array original será descartado, e é justamente esse aumento dinâmico que prejudica a performance.

A classe StringBuilder define uma propriedade chamada Capactiy, que podemos especificar/sugerir a quantidade inicial de memória alocada pela instância corrente. A capacidade pode ser diminuida, mas não pode ser menor que o valor especificado pela propriedade Length, e também não pode ser maior que o valor especificado pela propriedade MaxCapacity. Se executarmos o código que vimos acima e analisarmos a propriedade Capacity, ela retornará 12800, enquanto estamos realmente utilizando 402 caracteres * 20 linhas, que totaliza 8040.

Se você conseguir antecipar a quantidade de caracteres que o StringBuilder deverá ter, irá tirar um melhor proveito dele. Para isso, você pode fazer o uso de um overload do construtor que já nos permite especificar/sugerir a capacidade inicial. Com isso, a única mudança que temos no código acima é a primeira linha:

StringBuilder sb = new StringBuilder(8040);

É importante dizer que especificar a capacidade não quer dizer que quando você atingir este número, você terá uma exceção. Neste caso, internamente ele fará cálculos para poder redimensionar a classe, e acomodar esses novos caracteres. Se desejar impor uma quantidade máxima, então você pode recorrer à propriedade MaxCapacity, que impedirá adicionar mais caracteres do que o valor nela especificado.

Enumerando arquivos e diretórios

Algumas classes que estão contidas no namespace System.IO trabalham com arrays de strings. Para citar alguns exemplos, temos as classes Directory e DirectoryInfo, que fornecem dois métodos chamados GetDirectories e GetFiles. Ambos os métodos retornam um array de strings, representando os sub-diretórios ou os arquivos que constam dentro de um determinado diretório.

Além disso, temos também a classe estática File, que possui os métodos ReadAllLines e WriteAllLines, utilizados para ler e escrever linhas em um arquivo, respectivamente. O primeiro método retorna um array de strings, e o segundo recebe também um array de strings.

Para os métodos que retornam array de strings, é necessário criar, alocar a memória e, finalmente, preenchê-lo. Somente depois de todo esse processo, é que iremos conseguir acessar os elementos. Em cenários onde você tem poucos arquivos/sub-diretórios, você não deverá ter maiores problemas com performance. E enquanto estiver manipulando arquivos pequenos, você também não notará um baixo desempenho, causado pelo overhead dos arrays.

A performance começa a ser degradada a partir do momento que você está extraindo informações a partir de um diretório (local ou remoto), com muitos objetos. Já no caso da manipulação do conteúdo do arquivo, podemos sofrer quando há muitas informações a serem lidas e/ou escritas. Visando amenizar esses problemas, a Microsoft inseriu novos métodos na BCL do .NET 4.0.

Agora as classes Directory e DirectoryInfo passam a ter dois novos métodos EnumerateDirectories e EnumerateFiles, que ao invés de trabalhar com arrays de strings como os métodos citados acima, passam a retornar classes que implementam a interface IEnumerable<string>. Já no caso dos métodos ReadAllLines e WriteAllLines, apenas um novo overload foi criado para suportar também o IEnumerable<string>. Todos esses métodos removem a necessidade de criar e alocar um array potencialmente grande, trabalhando sob demanda, e permitindo o acesso imediato, sem a necessidade de aguardar que o array seja criado, carregado e retornado.

Interfaces Explícitas e o WCF

O C# e o VB.NET possibilitam a implementação explícita de interfaces. Esse tipo de implementação quer dizer que ao implementar a interface em uma classe, a assinatura do membro (propriedade, evento, método, etc.) levará o “nome completo”. Para exemplificar, note o exemplo abaixo, que exibe as duas implementações (implítica e explicíta):

class Data : ILog
{
    public void WriteMessage(ILogger logger, string msg) { }
}

class IO : ILog
{
    void ILog.WriteMessage(ILogger logger, string msg) { }
}

Note que na segunda implementação, o método WriteMessage está prefixado com o nome da interface onde ele foi definido, que neste caso é ILog. A finalidade deste tipo “diferenciado” de implementação é reduzir possíveis conflitos de nomenclatura que possa haver e, principalmente, esconder a implementação da “visão” pública do tipo onde ela foi implementada. É importante dizer que este tipo de implementação não proibe o cliente de acessar os métodos; basta apenas fazer um cast da instância da classe para a interface, que o método WriteMessage estará acessível.

Esse tipo de implementação também é útil ao trabalharmos com o WCF. Quando estamos utilizando algum recurso de extensibilidade, é muito comum implementarmos as interfaces predefinidas por ele, como por exemplo IServiceBehavior, IOperationBehavior ou IExtesion<T>, para acoplar um código customizado durante a execução do serviço. Além disso, quando desejamos customizar a serialização de um tipo, também devemos implementar algumas interfaces (ISerializable ou IXmlSerializable) fornecidas pelo .NET Framework. Quando implementadas, essas interfaces são utilizadas exclusivamente pelo runtime do WCF/.NET, e em um primeiro momento, os métodos que elas expõem não serão acessados diretamente pelo desenvolvedor.

Um exemplo mais concreto disso é a classe ServiceMetadataBehavior, que implementa de forma explítica a interface IServiceBehavior. Se estivermos trabalhando com a configuração imperativa,  ao instanciar essa classe, apenas iremos visualizar as propriedades que ela disponibiliza para configuração dos metadados do serviço, e não os métodos que a interface expõe, tornando o tipo bem menos poluído e de fácil entendimento, não misturando o que deve ser acessado pelo desenvolvedor e o que deve ser acessado pelo runtime.