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 ideia é 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<T>

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

public class GenericSerializer<T> 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 : O “type argument” precisa ser uma derivação de uma Base Class especificada.
where T : 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<T> 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("<?xml version=\"1.0\" encoding=\"utf-8\"?
        >", "").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.