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

174

Quais são as suas regras práticas para quando usar estruturas versus classes? Estou pensando na definição de C # desses termos, mas se o seu idioma tiver conceitos semelhantes, também gostaria de ouvir sua opinião.

Costumo usar classes para quase tudo e usar estruturas 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 pequeno e espero que haja casos de uso mais interessantes.

RationalGeek
fonte
4
Possível duplicata: stackoverflow.com/questions/6267957/struct-vs-class
FrustratedWithFormsDesigner
7
@ Frustrated Não é possível marcar uma pergunta como uma duplicata de algo em outro site, embora certamente esteja relacionada.
Adam Lear
@ Anna Lear: Eu sei, votei para migrar para não fechar como duplicado. Uma vez migrado, ele pode ser fechado como duplicado corretamente .
FrustratedWithFormsDesigner
5
@ Frustrado, acho que não precisa ser migrado.
Adam Lear
15
Parece uma pergunta muito mais adequada para P.SE vs. SO. Foi por isso que perguntei aqui.
RationalGeek

Respostas:

169

A regra geral a seguir é que as estruturas devem ser coleções pequenas e simples (de um nível) de propriedades relacionadas, imutáveis ​​após a criação; para qualquer outra coisa, use uma classe.

C # é bom, pois estruturas e classes não têm diferenças explícitas na declaração além da palavra-chave de definição; portanto, se você sentir que precisa "atualizar" uma estrutura para uma classe ou, inversamente, "fazer o downgrade" de uma classe para uma estrutura, é uma questão simples de alterar a palavra-chave (existem algumas outras dicas; as estruturas não podem derivar de qualquer outra classe ou tipo de estrutura, e eles não podem definir explicitamente um construtor sem parâmetros padrão).

Eu digo "principalmente", porque o mais importante a saber sobre estruturas é que, por serem tipos de valor, tratá-las como classes (tipos de referência) pode acabar meio doloroso. Particularmente, tornar mutáveis ​​as propriedades de uma estrutura pode causar 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 passa a instância para outro método. Esse método modifica ainda mais A e B. De volta à função de chamada (a que criou a instância), os A e B da 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) é 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ê tiver um tipo de referência como membro de sua estrutura (não é permitido, mas é uma prática extremamente ruim em praticamente todos os casos); a classe não seria clonada (apenas a referência da estrutura), portanto, as alterações na estrutura não afetariam o objeto original, mas as alterações na subclasse da estrutura afetarão a instância a partir do código de chamada. Isso pode facilmente colocar estruturas mutáveis ​​em estados muito inconsistentes que podem causar erros muito longe de onde está o problema real.

Por esse motivo, praticamente toda autoridade em C # diz sempre tornar suas estruturas imutáveis; permita que o consumidor especifique os valores das propriedades apenas na construção de um objeto e nunca forneça meios para alterar os valores dessa instância. Campos somente leitura, ou propriedades de obtenção apenas, são a regra. Se o consumidor deseja alterar o valor, ele pode criar um novo objeto com base nos valores do antigo, com as alterações que deseja, ou pode chamar um método que fará o mesmo. Isso os obriga a tratar uma única instância de sua estrutura como um "valor" conceitual, indivisível e distinto de (mas possivelmente equivalente a) todos os outros. Se eles executarem uma operação em um "valor" armazenado por seu tipo, obterão um novo "valor" diferente do valor inicial,

Para um bom exemplo, veja 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 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, em vez disso, obtém um novo valor 12:01 diferente de 12:00. Se esse é um estado lógico 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 em que as operações devem levar em consideração todas as partes do valor), 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.

KeithS
fonte
1
Boa resposta! Eu aponto um lê-lo para 'Domain Driven Development' metodologia e livro, que parece algo relacionado à sua opinião sobre por que um deve usar classes ou estruturas com base no qual o conceito que você realmente tentando implementar
Maxim Popravko
1
Apenas um pequeno detalhe que vale a pena mencionar: você não pode definir seu próprio construtor padrão em uma estrutura. Você tem que se contentar com aquele que inicializa tudo com seu valor padrão. Geralmente, isso é muito pequeno, especialmente nas classes que são boas candidatas a serem uma estrutura. Mas isso significa que alterar uma classe para uma estrutura pode implicar mais do que apenas alterar uma palavra-chave.
Laurent Bourgault-Roy
1
@ LaurentBourgault-Roy - Verdade. Existem outras dicas também; estruturas não podem ter uma hierarquia de herança, por exemplo. Eles podem implementar interfaces, mas não podem derivar de nada além de System.ValueType (implicitamente por serem estruturas).
Keiths
Como desenvolvedor JavaScript, tenho que perguntar duas coisas. Como os literais de objetos são diferentes dos usos típicos de estruturas e por que é importante que elas sejam imutáveis?
Erik Reppen
1
@ErikReppen: Em resposta a ambas as suas perguntas: Quando uma estrutura é passada para uma função, é passada por valor. Com uma classe, você está passando uma referência à classe (ainda por valor). Eric Lippert discute por que isso é um problema: blogs.msdn.com/b/ericlippert/archive/2008/05/14/… . O parágrafo final do KeithS também cobre essas questões.
21712 Brian
14

A resposta: "Use uma estrutura para construções de dados puros e uma classe para objetos com operações" é definitivamente IMO errado. Se uma estrutura possui um grande número de propriedades, uma classe é quase sempre mais apropriada. A Microsoft costuma dizer que, do ponto de vista da eficiência, se seu tipo for maior que 16 bytes, deve ser uma classe.

https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes

bytedev
fonte
1
Absolutamente. Eu votaria se pudesse. Não entendo de onde surgiu a ideia de que estruturas são dados e objetos não. Uma estrutura em C # é apenas um mecanismo para alocar na pilha. Cópias de toda a estrutura são passadas ao invés de apenas uma cópia de um ponteiro de objeto.
mike30
2
@mike - estruturas C # não são necessariamente alocadas na pilha.
20912 Lee
1
@mike A idéia "estruturas são para dados" provavelmente vem do hábito adquirido por muitos anos de programação em C. C não possui classes e suas estruturas são contêineres somente de dados.
precisa
Obviamente que há pelo menos 3 programadores C (GOT upvoted) depois de ler a resposta de Michael K ;-)
bytedev
10

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

  Coisa coisa 1 = nova coisa ();
  thing1.somePropertyOrField = 5;
  Coisa coisa2 = coisa1;
  thing2.somePropertyOrField = 9;

Qual deve ser o efeito da última declaração thing1.somePropertyOrField? Se Thinguma estrutura, e somePropertyOrFieldé um campo público exposto, os objetos thing1e thing2será "isolada" uma da outra, de modo que esta última afirmação não afetará thing1. Se Thingé uma classe, então thing1e thing2serão anexados um ao outro e, portanto, a última declaração será escrita 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 semântica anterior faria mais sentido.

Observe que, enquanto algumas pessoas aconselham que o desejo de tornar algo mutável é um argumento a favor de ser da classe, eu sugeriria o contrário: se algo que existe com o objetivo de armazenar 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 estrutura (provavelmente com campos expostos), para deixar claro que as instâncias não estão anexadas a qualquer outra coisa.

Por exemplo, considere as instruções:

  Pessoa somePerson = myPeople.GetPerson ("123-45-6789");
  somePerson.Name = "Mary Johnson"; // Tinha sido "Mary Smith"

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

No que diz respeito à noção de que estruturas devem ser "imutáveis", discordo em geral. Existem casos de uso válidos para estruturas "imutáveis" (onde invariantes são impingidos em um construtor), mas exigindo que uma estrutura inteira seja reescrita sempre que qualquer parte dela for alterada, desajeitada e mais propensa a causar bugs do que simplesmente expor campos diretamente. Por exemplo, considere uma PhoneNumberestrutura cujos campos incluam entre outros AreaCodee Exchange, e suponha que um possua a 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;
        minhaLista [i] = oNúmero;
      }
    }
  }

Observe que nada no código acima sabe ou se importa com nenhum campo PhoneNumberalém de AreaCodee Exchange. Se PhoneNumberfosse uma estrutura chamada "imutável", seria necessário fornecer um withXXmétodo para cada campo, que retornasse uma nova instância de estrutura que contivesse o valor passado no campo indicado, ou seria necessário para código como o acima saber sobre todos os campos na estrutura. Não é exatamente atraente.

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

  1. A semântica da estrutura indica que 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` conteria as identidades de determinados botões, mas não seria esperado que mantivessem informações persistentes sobre as posições desses botões, estados de destaque etc.
  2. O struct sabe que ele possui a única referência a esse objeto, ninguém mais receberá uma referência e quaisquer mutações que serão executadas nesse objeto terão sido 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 impor a imutabilidade, mas a estrutura que mantém a referência o fará.

supercat
fonte
7

Eu costumo usar estruturas somente 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, por isso realmente depende da situação!

RECURSOS EXTRA

Para uma explicação mais formal, o MSDN diz: http://msdn.microsoft.com/en-us/library/ms229017.aspx Este link verifica o que mencionei acima, as estruturas devem ser usadas apenas para armazenar uma pequena quantidade de dados.

rrazd
fonte
5
Perguntas em outro site (mesmo se o site estiver com estouro de pilha ) não contam como duplicatas. Expanda sua resposta para incluir uma resposta real e não apenas links para outros lugares. Se os links mudarem, sua resposta, como está escrita agora, será inútil e gostamos de preservar as informações e tornar os programadores o mais independentes possível. Obrigado!
Adam Lear
2
@ Anna Lear Obrigado por me informar, manterá isso em mente a partir de agora!
21911 rrazd
2
-1 "Eu costumo usar estruturas apenas quando há um método necessário, fazendo com que uma classe pareça muito" pesada "." ... pesado? O que isso realmente significa? ... e você está realmente dizendo que apenas porque existe um método, provavelmente deve ser uma estrutura?
Bydevev
2

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

Se sua estrutura de dados não precisar de controle de acesso e não possuir operações especiais além de obter / definir, use uma estrutura. Isso torna óbvio que tudo o que essa estrutura é é um contêiner para dados.

Se sua estrutura possui operações que modificam 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 modificam de alguma forma, como mover superfícies de controle e alterar a pressão do motor. Seria melhor como classe.

Michael K
fonte
2
"Use uma estrutura para construções de dados puros e uma classe para objetos com operações" em C # isso não faz sentido. Veja minha resposta abaixo.
23814 bydtev
1
"Use uma estrutura para construções de dados puros e uma classe para objetos com operações." Isto é verdade para C / C ++, C # não
taktak004