C# - Persistindo uma coleção entre sessões da aplicação


 Hoje vamos recordar como persistir dados de uma coleção entre sessões de uma aplicação na linguagem C#.

Existem muitas maneiras de persistir dados em uma aplicação e isso vai depender do ambiente, do tipo da aplicação e do que você deseja e precisa executar.

Vamos supor que você tem uma coleção como ArrayList, List<T>, Hashtable ou Dictionary<T, U> no qual você está armazenando informações do aplicativo como dados de configurações, preferências do usuário, etc. e você precisa usar esta informação em outras partes da aplicação ou mesmo quando a aplicação for reiniciada.

Como persistir os dados das coleções de forma que quando a aplicação for encerrada os dados estejam preservados e prontos para serem usados na reinicialização da aplicação ?

Uma técnica simples e que pode ser usada é realizar a serialização dos dados.

Já ouviu falar em serialização ?

O que é serializar ? Como podemos fazer isto na plataforma .NET ?

De forma genérica a serialização é uma técnica usada para persistir objetos , ou seja :  gravar objetos em disco , fazer a transmissão remota de objetos via rede , armazenar os objetos em um banco de dados e/ou arquivos (binários , xml, etc.)

Serializar nada mais é do que colocar os valores que o objeto está utilizando juntamente com suas propriedades de uma forma que fique em série (sequencial) . Fazendo isto estamos tornando o objeto Serializable, e, tornando um objeto Serializable, estamos atribuindo essa qualidade a ele, e dando privilégios para que o mesmo possa ser gravado em disco ou enviado por rede.

A serialização é o processo de armazenar um objeto , incluindo todos os atributos públicos e privados para um stream.

Se você faz a serialização naturalmente vai querer fazer o processo inverso - desserialização , que seria restaurar os atributos de um objeto gravado em um stream. (Este stream pode ser um arquivo binário , xml , etc.)

Nota: Tradução de stream - fluxo (no caso de dados) . Melhor deixar stream mesmo.

Assim, podemos usar o recurso Generics da linguagem C# e criar uma classe que faça a serialização e o processo inverso.

Abaixo temos um código bem simples que pode ser usado para serializar e desserializar objetos:

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace CShp_PersisteData
{
    public class Serializar
    {
        public static void ParaArquivo<T>(T obj, string arquivo)
        {
            try
            {
                using (FileStream fileStream = File.Create(arquivo))
                {
                    BinaryFormatter binSerializer = new BinaryFormatter();
                    binSerializer.Serialize(fileStream, obj);
                }
            }
            catch (System.Exception)
            {
               throw;
            }
        }

        public static T DoArquivo<T>(string arquivo)
        {
            try
            {
                T obj = default(T);
                using (FileStream fileStream = File.OpenRead(arquivo))
                {
                    BinaryFormatter binSerializer = new BinaryFormatter();
                    obj = (T)binSerializer.Deserialize(fileStream);
                }
                return obj;
            }
            catch (System.Exception)
            {
                throw;
            }
        }
    }
}

Neste código temos que:

O parâmetro arquivo refere-se ao nome do arquivo e o método ParaArquivo<T> aceita um objeto e tenta serializá-lo em um arquivo usando o método Serialize da classe BinaryFormat.

Por outro lado, o método DoArquivo<T> remove o objeto serializado do arquivo.

Abaixo temos um exemplo de uso desta classe onde serializamos e a seguir desserializamos os dados de um ArrayList e um HashTable :

using System;
using System.Collections;
namespace CShp_PersisteData
{
    class Program
    {
        static void Main(string[] args)
        {
            ArrayList referencias = new ArrayList() { "macoratti@yahoo.com", "macoratti.net"};

            Hashtable enderecos = new Hashtable();
            enderecos.Add("Macoratti", "Rua Projetada 100");
            enderecos.Add("JcmSoft", "Av. Lusitânia , 200");
            foreach (object O in referencias)
                Console.WriteLine(O.ToString());
            Console.WriteLine("###  Serializando dados  ###");
            Serializar.ParaArquivo<ArrayList>(referencias, "referencias.data");
            Serializar.ParaArquivo<Hashtable>(enderecos, "enderecos.data");
            
            ArrayList minhasReferencias = new ArrayList();
            Hashtable meusEnderecos = new Hashtable();
            minhasReferencias = Serializar.DoArquivo<ArrayList>("referencias.data");
            Console.WriteLine("###  Desserializando dados  ###\n");
            Console.WriteLine("Minhas Referências");
            foreach (object O in minhasReferencias)
                Console.WriteLine(O.ToString());
            meusEnderecos = Serializar.DoArquivo<Hashtable>("enderecos.data");
           Console.WriteLine("\nMeus Endereços");
           foreach (DictionaryEntry endereco in meusEnderecos)
                   Console.WriteLine($"{endereco.Key} - {endereco.Value}");
            Console.ReadLine();
        }
    }
}

Abaixo o resultado obtido:

No exemplo estamos serializando os dados da coleção para o disco mas podemos também serializar e desserializar para um armazenamento remoto usando um MemoryStream() conforme a implementação abaixo:

       public static byte[] ParaMemoria<T>(T obj)
        {
            using (MemoryStream memStream = new MemoryStream())
            {
                BinaryFormatter binSerializer = new BinaryFormatter();
                binSerializer.Serialize(memStream, obj);
                return memStream.ToArray();
            }
        }

        public static T DaMemoria<T>(byte[] serializedObj)
        {
            T obj = default(T);
            using (MemoryStream memStream = new MemoryStream(serializedObj))
            {
                BinaryFormatter binSerializer = new BinaryFormatter();
                obj = (T)binSerializer.Deserialize(memStream);
            }
            return obj;
        }

Você deve considerar a utilização deste recurso levando em considerações as novas versões que seu projeto poderá ter planejando uma estratégia para garantir que os tipos que você serializa não sejam alterados.

Pegue o projeto aqui: CShp_PersisteData.zip

"Porque a palavra de Deus é viva e eficaz, e mais penetrante do que espada alguma de dois gumes, e penetra até à divisão da alma e do espírito, e das juntas e medulas, e é apta para discernir os pensamentos e intenções do coração."
Hebreus 4:12

Referências:


José Carlos Macoratti