Quando você usa uma estrutura em vez de uma classe? [fechadas]

160

Quais são as suas regras básicas para quando usar structs vs. classes? Estou pensando na definição de C # desses termos, mas se sua linguagem tiver conceitos semelhantes, também gostaria de ouvir sua opinião.

Eu costumo usar classes para quase tudo, e uso structs apenas quando algo é muito simplista e deve ser um tipo de valor, como um PhoneNumber ou algo parecido. Mas isso parece ser um uso relativamente menor e espero que haja casos de uso mais interessantes.

    
por RationalGeek 12.07.2011 / 18:22
fonte

5 respostas

151

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.

    
por 12.07.2011 / 19:16
fonte
11

A resposta: "Usar uma estrutura para construções de dados puras e uma classe para objetos com operações" é definitivamente IMO incorreta. Se uma struct contém um grande número de propriedades, então uma classe é quase sempre mais apropriada. A Microsoft costuma dizer, do ponto de vista da eficiência, se o seu tipo é maior do que 16 bytes, deve ser uma classe.

link

    
por 11.12.2012 / 11:20
fonte
9

A diferença mais importante entre um class e um struct é o que acontece na seguinte situação:

  Thing thing1 = new Thing();
  thing1.somePropertyOrField = 5;
  Thing thing2 = thing1;
  thing2.somePropertyOrField = 9;

Qual deve ser o efeito da última declaração em thing1.somePropertyOrField ? Se Thing a struct e somePropertyOrField forem um campo público exposto, os objetos thing1 e thing2 serão "separados" um do outro, portanto, a última instrução não afetará thing1 . Se Thing for uma classe, thing1 e thing2 serão anexados um ao outro e, portanto, a última instrução gravará thing1.somePropertyOrField . Deve-se usar uma estrutura nos casos em que a semântica anterior faria mais sentido e deveria usar uma classe nos casos em que a última semântica faria mais sentido.

Note que enquanto algumas pessoas aconselham que o desejo de fazer algo mutável é um argumento em favor de que seja a classe, eu sugiro que o inverso é verdadeiro: se algo que existe com a finalidade de guardar alguns dados será mutável, e se não for óbvio se as instâncias estão anexadas a alguma coisa, a coisa deve ser uma struct (provavelmente com campos expostos), de modo a tornar claro que as instâncias não estão anexadas a qualquer outra coisa.

Por exemplo, considere as declarações:

  Person somePerson = myPeople.GetPerson("123-45-6789");
  somePerson.Name = "Mary Johnson"; // Had been "Mary Smith"

A segunda afirmação alterará as informações armazenadas em myPeople ? Se Person é uma estrutura de campo exposto, não será e o fato de que não será uma consequência óbvia de ser uma estrutura de campo exposto ; Se Person é uma estrutura e se deseja atualizar myPeople , obviamente seria necessário fazer algo como myPeople.UpdatePerson("123-45-6789", somePerson) . Se Person é uma classe, no entanto, pode ser muito mais difícil determinar se o código acima nunca atualizaria o conteúdo de MyPeople , sempre o atualizaria ou, às vezes, o atualizaria.

No que diz respeito à noção de que as estruturas devem ser "imutáveis", eu discordo em geral. Existem casos de uso válidos para estruturas "imutáveis" (em que invariantes são aplicadas em um construtor), mas requerem que uma estrutura inteira seja reescrita toda vez que qualquer parte das mudanças for estranha, perdida e mais propensa a causar erros do que simplesmente expor campos diretamente. Por exemplo, considere um PhoneNumber struct cujos campos incluam, entre outros, AreaCode e Exchange e suponha que um tenha List<PhoneNumber> . O efeito do seguinte deve ser bem claro:

  for (int i=0; i < myList.Count; i++)
  {
    PhoneNumber theNumber = myList[i];
    if (theNumber.AreaCode == "312")
    {
      string newExchange = "";
      if (new312to708Exchanges.TryGetValue(theNumber.Exchange), out newExchange)
      {
        theNumber.AreaCode = "708";
        theNumber.Exchange = newExchange;
        myList[i] = theNumber;
      }
    }
  }

Observe que nada no código acima conhece ou se preocupa com os campos em PhoneNumber que não sejam AreaCode e Exchange . Se PhoneNumber fosse uma estrutura chamada "imutável", seria necessário que ela fornecesse um método withXX para cada campo, o que retornaria uma nova instância de estrutura que continha o valor passado no campo indicado, ou então seria necessário que o código, como o acima, conhecesse todos os campos da estrutura. Não é exatamente atraente.

BTW, existem pelo menos dois casos em que as estruturas podem razoavelmente conter referências a tipos mutáveis.

  1. A semântica da estrutura indica que ela está armazenando a identidade do objeto em questão, e não como um atalho para manter as propriedades do objeto. Por exemplo, um 'KeyValuePair' manteria as identidades de certos botões, mas não seria esperado que contivesse uma informação persistente sobre as posições desses botões, estados de destaque, etc.
  2. A estrutura sabe que ela contém a única referência a esse objeto, ninguém mais obterá uma referência e quaisquer mutações que jamais serão executadas nesse objeto serão feitas antes que uma referência a ele seja armazenada em qualquer lugar.

No cenário anterior, a IDENTIDADE do objeto será imutável; no segundo, a classe do objeto aninhado pode não impingir a imutabilidade, mas a estrutura que contém a referência será.

    
por 06.07.2012 / 23:59
fonte
6

Eu costumo usar estruturas apenas quando há um método necessário, fazendo com que uma classe pareça muito "pesada". Assim, às vezes eu trato estruturas como objetos leves. CUIDADO: isso nem sempre funciona e nem sempre é a melhor coisa a fazer, então realmente depende da situação!

RECURSOS EXTRA

Para obter explicações mais formais, o MSDN diz: link Este link verifica o que eu mencionei acima, estruturas só devem ser usadas para abrigar uma pequena quantidade de dados.

    
por 12.07.2011 / 18:25
fonte
2

Use uma estrutura para construções de dados puras e uma classe para objetos com operações.

Se a sua estrutura de dados não precisar de controle de acesso e não tiver operações especiais além de get / set, use uma struct. Isso torna óbvio que toda essa estrutura é um recipiente para dados.

Se sua estrutura tiver operações que modifiquem a estrutura de maneira mais complexa, use uma classe. Uma classe também pode interagir com outras classes através de argumentos para métodos.

Pense nisso como a diferença entre um tijolo e um avião. Um tijolo é uma estrutura; tem comprimento, largura e altura. Não pode fazer muito. Um avião pode ter várias operações que o transformam de alguma forma, como mover superfícies de controle e alterar o empuxo do motor. Seria melhor como uma aula.

    
por 12.07.2011 / 18:49
fonte

Tags