CodeGym /Cursos /C# SELF /Compatibilidade ao alterar a estrutura de classes

Compatibilidade ao alterar a estrutura de classes

C# SELF
Nível 45, Lição 4
Disponível

1. Introdução

Imagine: você escreveu a classe Person, serializou um objeto em arquivo, e alguns meses depois decidiu que precisa, digamos, de um novo endereço de residência ou mudou o tipo de algumas propriedades. Parece rotina — mas quando você tenta carregar (desserializar) dados antigos, pode ter surpresas: algo não vai carregar, uma exceção é lançada, alguns valores ficam vazios ou até errados.

Esse comportamento é um caso típico de quebra de compatibilidade reversa. No desenvolvimento real isso acontece com mais frequência do que estudante esquece ponto e vírgula (ou seja, muito frequentemente).

Vamos ilustrar o problema com um exemplo

Considere nosso mini-projeto didático. Suponha que no momento tínhamos a seguinte classe:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Serializamos uma instância dessa classe em JSON:

Person p = new Person { Name = "Alice", Age = 35 };
string json = JsonSerializer.Serialize(p);
File.WriteAllText("person.json", json);

No arquivo obtemos:

{"Name":"Alice","Age":35}

Agora, uma semana depois, decidimos deixar o app mais moderno e adicionamos um campo de endereço:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Address { get; set; } // novo campo
}

E então tentamos carregar o arquivo antigo:

string json = File.ReadAllText("person.json");
Person p = JsonSerializer.Deserialize<Person>(json);

O que vai acontecer? O endereço no nosso objeto não vai existir: a propriedade Address ficará igual a null. Nenhum erro ocorreu. Por enquanto tudo funciona... Mas se você começar a mudar tipos, remover campos ou fazer algo realmente "interessante", os problemas podem aparecer!

2. Que tipos de mudanças existem — e como elas afetam

Mudanças na estrutura das classes afetam a serialização de formas diferentes. Vamos analisar alguns cenários típicos.

Adicionar novas propriedades

Essa é a opção menos perigosa. Dados antigos (onde essas propriedades não existiam) são desserializados tranquilamente: as novas propriedades recebem valores padrão (null para referências, 0 para int etc.).

Atenção: Se sua nova propriedade não é nullable e não tem um valor padrão "sensato", pode haver problema (especialmente com propriedades required do C# 11+).

Remover propriedades

Se você remove uma propriedade, e nos dados serializados ela ainda existe — o serializador provavelmente vai simplesmente ignorar o "excesso", e o carregamento ainda irá acontecer.

Mas isso depende do serializador usado. Por exemplo, JsonSerializer e Newtonsoft.Json são bem permissivos: eles não vão lançar exceção; já alguns serializadores antigos ou customizados podem se comportar de forma diferente.

Renomear propriedades

Aí começa a diversão. Se você só renomear a propriedade FirstName para Name, o serializador não conseguirá mapear os campos dos dados antigos para o objeto novo. Ou seja, o campo ficará vazio (null/0) e o valor antigo no arquivo será ignorado.

Mudar o tipo da propriedade

Por exemplo, antes você tinha public int Age, depois decidiu torná-lo public string Age (vai que alguém coloca "bessmertnyy" — acontece). Tentar desserializar dados antigos pode gerar erro ("Cannot convert number to string") ou a propriedade pode receber valor padrão. Tudo depende do serializador e das suas configurações de tipagem rígida.

Mudar hierarquias (herança, aninhamento)

Se você altera classes base, move propriedades para outros lugares ou, digamos, faz uma classe-encapsuladora para outra — dados serializados antigos podem ficar totalmente incompatíveis. Isso é especialmente problemático com XML e hierarquias complexas de objetos.

3. Problemas de compatibilidade

Como descobrir problemas de compatibilidade?

Muitas vezes o erro de compatibilidade não aparece imediatamente e de forma óbvia: o app só começa a se comportar "estranhamente", parte dos dados some, ou aparece uma exceção pouco informativa nos logs. Normalmente os problemas surgem quando:

  • Um usuário carrega um arquivo antigo na versão nova do programa.
  • O servidor recebe JSON/XML de um cliente "versão antiga".
  • Você integra com uma API externa que mudou de repente.

Os sintomas variam: desde erros na desserialização até campos "inesperadamente" vazios.

Impacto do serializador na compatibilidade

Nem todos os serializadores se comportam igual. Os JSON-serializers são os mais "tolerantes" a mudanças na estrutura — tanto o padrão System.Text.Json quanto o Newtonsoft.Json. Eles costumam pular propriedades desconhecidas do arquivo e não serializam de volta campos desconhecidos do objeto.

Em XML a coisa é um pouco mais rígida: se o elemento raiz ou a hierarquia mudam, podem aparecer erros.

Em formatos binários pode rolar exceção se a ordem ou tipos mudaram!

4. Como minimizar riscos? Abordagens e práticas

Aqui vão algumas abordagens que ajudam a reduzir problemas (e às vezes evitá-los por completo).

Use versões de classes e dados

Adicione um campo especial Version nos objetos serializáveis ou nos próprios arquivos. Isso permite saber com qual versão da estrutura o arquivo foi criado e decidir o que fazer ao carregar (por exemplo, aplicar upgrade nos dados).

public class PersonV2
{
    public int Version { get; set; } = 2;
    public string Name { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }
}

Use atributos de mapeamento de nomes (para serialização)

Com JSON e XML você pode explicitar como a propriedade deve ser nomeada na forma serializada. Se renomear a propriedade — mantenha o nome antigo:

public class Person
{
    [JsonPropertyName("FirstName")] // para System.Text.Json
    [JsonProperty("FirstName")]     // para Newtonsoft.Json
    public string Name { get; set; }
    public int Age { get; set; }
}

Use tipos nullable e valores padrão

Se aparecerem novos campos que nem sempre existem em dados antigos — faça-os nullable ou dê um valor padrão, assim a desserialização não quebra:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string? Address { get; set; } = "Unknown";
}

Tratamento de evento "campo desconhecido"

No Newtonsoft.Json você pode se inscrever para tratar campos "estranhos" via settings, para por exemplo logar uma situação potencialmente perigosa.

var settings = new JsonSerializerSettings
{
    MissingMemberHandling = MissingMemberHandling.Error
};
try
{
    var person = JsonConvert.DeserializeObject<Person>(json, settings);
}
catch (JsonSerializationException ex)
{
    Console.WriteLine("Não foi possível desserializar: " + ex.Message);
}

Migração de dados

Se as mudanças são significativas, é mais sensato prever uma etapa de migração: por exemplo, carregar os dados na "estrutura antiga" e então convertê-los para a nova:

// Suponha que PersonV1 era sem address
public class PersonV1 { public string Name; public int Age; }

// Novo — com address
public class PersonV2 { public string Name; public int Age; public string Address; }

// Migração:
string oldJson = File.ReadAllText("person.json");
PersonV1 oldPerson = JsonSerializer.Deserialize<PersonV1>(oldJson);

PersonV2 migrated = new PersonV2
{
    Name = oldPerson.Name,
    Age = oldPerson.Age,
    Address = "Unknown"
};

5. Casos complexos e erros inesperados

Invariância do campo e propriedades required

No C# 11+ surgiram propriedades required. Agora, se um campo é marcado como required, a desserialização pode lançar erro se esse campo estiver ausente nos dados:

public class Person
{
    public string Name { get; set; }
    [JsonPropertyName("Age")]
    public required int Age { get; set; }
    public string Address { get; set; }
}

Se nos dados antigos o campo Age estiver ausente — vai ocorrer uma exceção de incompatibilidade de estrutura.

Mudança de tipo: int string

// Era:
public class Record { public int Count; }
// Virou:
public class Record { public string Count; }

Se nos dados estiver "Count":42, a desserialização para string pode funcionar (conversão esperta), mas no sentido inverso — pode lançar exceção.

Remoção da classe base

Se o objeto serializado tinha herança, e você mudou a hierarquia — desserializar arquivos antigos pode gerar erro, às vezes "silencioso", às vezes explícito.

6. Erros típicos ao trabalhar com compatibilidade

Erro #1: mudar propriedades sem pensar.
Renomear ou trocar o tipo das propriedades sem levar em conta os dados já serializados leva à perda de informação na desserialização.

Erro #2: esquecer nullable para novos campos.
Novas propriedades devem ser nullable ou ter valores padrão sensatos.

Erro #3: não testar compatibilidade reversa.
Mudou a classe — teste que arquivos/ dados antigos ainda carregam corretamente.

Erro #4: misturar atributos de bibliotecas diferentes.
Não use JsonPropertyName e JsonProperty simultaneamente na mesma propriedade.

2
Tarefa
C# SELF, nível 45, lição 4
Bloqueado
Tratamento da ausência de campos durante a desserialização
Tratamento da ausência de campos durante a desserialização
1
Pesquisa/teste
Configuração de serialização, nível 45, lição 4
Indisponível
Configuração de serialização
Configuração da serialização de objetos
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION