Olá a todos.

No artigo anterior, vimos uma breve descrição sobre o que é e para que serve Serialização. Construímos também nossa classe modelo que será utilizada para testarmos nossa ferramenta.

Agora iniciaremos a codificação de nossa ferramenta. No projeto “Utils” (ver artigo anterior, onde criamos nossa solução no Visual Studio) crie uma nova classe de nome “GenericSerializer.cs”.  A idéia é ter uma ferramenta que possa serializar qualquer tipo de objeto criado por nós, ou seja, objetos que serão instâncias de nossas classes. Seguindo esse pensamento, a classe “GenericSerializer.cs” deverá ser capaz de ler em tempo de execução e até em tempo de design, qualquer tipo de objeto que definirmos. Para isso utilizamos os chamados “Parameters Type”, que são usados em definições de classes genéricas. Abaixo segue a definição de nossa classe:

 
public class GenericSerializer

Quando definimos uma classe genérica, podemos aplicar restrições (Constraint) aos tipos que a classe receberá como argumento. Exemplo:

 
public class GenericSerializer where T : new()

Aqui nós restringimos o uso de nossa classe para instâncias de classes que possuem um construtor público vazio, ou seja, sem parâmetros. Exemplo

 
public Cliente() 
{ 

}

Quando um código cliente tenta usar a classe genérica usando um tipo que não é permitido pela Constraint definida, o resultado é um erro em tempo de compilação.

Abaixo segue uma tabela com todos os tipos de Constraints que podemos utilizar na construção de classes genéricas:

Constraint

Descrição

where T: struct

O “Type Argument” precisa ser um “Value Type”. Qualquer “Value Type” pode ser especificado, exceto o tipo “Nullable”..

where T : class

O “type argument” precisa ser um “reference type”- Classe, interface, delegate, ou array type.

where T : new()

O “type argument” precisa ter um construtor vazio público. Quando usado com outras Constraints, essa constraint precisa ser declarada por ultimo.

where T : <base class name>

O “type argument” precisa ser uma derivação de uma Base Class especificada.

where T : <interface name>

O “type argument” precisa ser uma Interface ou implementer uma. Múltiplas “interface constraints” podem ser especificadas. Essa Constraint pode também ser genérica.

where T : U

O “type argument” fornecido para “T”, precisa ser ou deriver o argumento fornecido por “U”.

 

Agora que aprendemos um pouco sobre “Type Parameters”, vamos definir nossa classe genérica:

 
public class CustomSerializer here T : new()
{

}

O método que iremos codificar nesta parte do artigo será o responsável por serializar nossos objetos, ou seja, persistir um objeto numa string representando um XML. O objeto principal deste método é o “XmlSerializer”. Ele contém o método de serialização pronto para usar, Como nós já definimos o Type Parameter que a classe receberá, o método em questão apenas receberá como parâmetro um objeto do tipo definido na declaração da classe. Abaixo segue o código completo do método. Posteriormente iremos explicar os pormenores.

 

  public string GenSerializer(T Obj)
        {
            XmlSerializer customSerializer = null;
            MemoryStream ms1 = null;
            XmlTextWriter xmlWriter = null;
            XmlDocument xmlDoc = null;
            XmlNodeList emptyNodes = null;
            MemoryStream ms2 = null;
            string ret = "";
            try
            {
                //definição do tipo de objeto que o XmlSerializer trabalhará.
                customSerializer = new XmlSerializer(typeof(T));

                //instanciando um MemoryStrem que receberá o resultado da serialização
                using (ms1 = new MemoryStream())
                {
                    xmlWriter = new XmlTextWriter(ms1, Encoding.UTF8);
                    customSerializer.Serialize(xmlWriter, Obj);

                    //instanciando um outro MemoryStream que será utilizado para criar em tempo de execução um XmlDocument
                    using (ms2 = (MemoryStream)xmlWriter.BaseStream)
                    {
                        ms2.Position = 0;
                        xmlDoc = new XmlDocument();
                        xmlDoc.Load(ms2);

                        //Verifica se houve algum erro na serialização que possa fazer  que o XmlDocument esteja nulo ou vazio
                        if (xmlDoc != null && xmlDoc.HasChildNodes)
                        {
                            //Esse bloco de comando remove nodes vazios do XmlDocument
                            emptyNodes = xmlDoc.SelectNodes(@"//*[not(node())]");
                            if (emptyNodes.Count > 0)
                            {
                                for (int i = emptyNodes.Count - 1; i >= 0; i--)
                                {
                                    emptyNodes[i].ParentNode.RemoveChild(emptyNodes[i]);
                                }
                            }

                            //bloco que retira alguns nodes indesejados
                            ret = xmlDoc.InnerXml;
                            ret = ret.Replace(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", "").Trim();
                            ret = ret.Replace(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"", "").Trim();
                            ret = ret.Replace("", "").Trim();


                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                customSerializer = null;
                ms1 = null;
                xmlWriter = null;
                xmlDoc = null;
                emptyNodes = null;
                ms2 = null;
            }
            return ret;
        }


O resultado da serialização será uma string representando um XML. Portanto o retorno do método é uma string. Inicialmente instanciamos um objeto XmlSerializer passando o Parameter Type definido na declaração da classe, depois utilizamos um XmlTextWriter e um MemoryStream, para conseguir utilizar o Método “Serialize” do XmlSerializer. Posteriormente Criamos outro MemoryStrem para criarmos na memória um XmlDocument que receberá o resultado da serialização. Depois com o XmlDocument preenchido, capturamos a propriedade InnerXml, que retorna uma string representando o XML. Para finalizar o método exclui nodes vazios do XML (se houver) assim como outros nodes que não necessitamos.

Vale ressaltar que todo MemoryStream criado precisa ser fechado. Por isso utilizamos o bloco “Using” que fecha automaticamente o MemoryStream, quando o runtime chega no final do bloco Using.

Com isso chegamos ao final desta parte do artigo. Na próxima parte criaremos o método genérico de desserialização e testaremos nossa ferramenta.

Obrigado e até lá.