A regra geral a ser seguida é que as estruturas devem ser coleções pequenas, simples (um nível) de propriedades relacionadas, que são imutáveis depois de criadas; para qualquer outra coisa, use uma classe.
C # é legal em que structs e classes não possuem diferenças explícitas na declaração além da palavra-chave de definição; Então, se você acha que precisa "atualizar" uma struct para uma classe, ou inversamente "downgrade" de uma classe para uma struct, é basicamente uma simples questão de mudar a palavra-chave (existem algumas outras dicas; structs não podem derivar de qualquer outra classe ou tipo de struct, e eles não podem definir explicitamente um construtor sem parâmetros padrão).
Eu digo "principalmente", porque a coisa mais importante a saber sobre as estruturas é que, por serem tipos de valor, tratá-las como classes (tipos de referência) pode resultar em uma dor e meia. Particularmente, tornar as propriedades de uma estrutura mutáveis pode causar um comportamento inesperado.
Por exemplo, digamos que você tenha uma classe SimpleClass com duas propriedades, A e B. Você instancia uma cópia dessa classe, inicializa A e B e, em seguida, passa a instância para outro método. Esse método modifica ainda mais A e B. De volta à função de chamada (aquela que criou a instância), os A e B de sua instância terão os valores dados a eles pelo método chamado.
Agora, você faz disso uma estrutura. As propriedades ainda são mutáveis. Você executa as mesmas operações com a mesma sintaxe de antes, mas agora os novos valores de A e B não estão na instância depois de chamar o método. O que aconteceu? Bem, sua classe agora é uma estrutura, o que significa que é um tipo de valor. Se você passar um tipo de valor para um método, o padrão (sem uma palavra-chave out ou ref) será passar "por valor"; uma cópia superficial da instância é criada para uso pelo método e, em seguida, destruída quando o método é concluído, deixando a instância inicial intacta.
Isso se torna ainda mais confuso se você tivesse um tipo de referência como membro de sua estrutura (não permitido, mas extremamente prática ruim em praticamente todos os casos); a classe não seria clonada (apenas a referência da estrutura a ela), portanto, as alterações na estrutura não afetariam o objeto original, mas as alterações na subclasse da estrutura afetariam a instância do código de chamada. Isso pode facilmente colocar estruturas mutáveis em estados muito inconsistentes que podem causar erros muito distantes de onde está o problema real.
Por esse motivo, virtualmente toda autoridade em C # diz sempre tornar suas estruturas imutáveis; permite que o consumidor especifique os valores das propriedades apenas na construção de um objeto e nunca forneça quaisquer meios para alterar os valores dessa instância. Campos Readonly, ou get-only, são a regra. Se o consumidor quiser alterar o valor, ele poderá criar um novo objeto com base nos valores do antigo, com as alterações desejadas, ou poderá chamar um método que fará o mesmo. Isso os força a tratar uma única instância de sua estrutura como um "valor" conceitual, indivisível e distinto de (mas possivelmente igualável a) todos os outros. Se eles executam uma operação em um "valor" armazenado pelo seu tipo, eles obtêm um novo "valor" que é diferente de seu valor inicial, mas ainda comparável e / ou semanticamente igualável.
Para um bom exemplo, observe o tipo DateTime. Você não pode atribuir nenhum dos campos de uma instância DateTime diretamente; você deve criar um novo ou chamar um método no existente, o que produzirá uma nova instância. Isso ocorre porque uma data e hora são um "valor", como o número 5, e uma alteração no número 5 resulta em um novo valor que não é 5. Só porque 5 + 1 = 6 não significa que 5 é agora 6 porque você adicionou 1 a ele. DateTimes funcionam da mesma maneira; 12:00 não "se torna" 12:01 se você adicionar um minuto, você recebe um novo valor 12:01 diferente das 12:00. Se esse é um estado lógico de assuntos para o seu tipo (bons exemplos conceituais que não são incorporados ao .NET são Dinheiro, Distância, Peso e outras quantidades de uma UOM onde as operações devem levar em conta todas as partes do valor), em seguida, use uma estrutura e projete-a de acordo. Na maioria dos outros casos em que os subitens de um objeto devem ser mutáveis independentemente, use uma classe.