Quando devo usar uma estrutura em vez de uma classe?

302

O MSDN diz que você deve usar estruturas quando precisar de objetos leves. Existem outros cenários em que uma estrutura é preferível a uma classe?

Algumas pessoas podem ter esquecido que:

  1. estruturas podem ter métodos.
  2. estruturas não podem ser herdadas.

Eu entendo as diferenças técnicas entre estruturas e classes, apenas não tenho uma boa ideia de quando usar uma estrutura.

Esteban Araya
fonte
Apenas um lembrete - o que a maioria das pessoas costuma esquecer neste contexto é que nas estruturas C # também podem ter métodos.
petr k.

Respostas:

295

O MSDN tem a resposta: Escolhendo entre classes e estruturas .

Basicamente, essa página fornece uma lista de verificação de 4 itens e diz para usar uma classe, a menos que seu tipo atenda a todos os critérios.

Não defina uma estrutura, a menos que o tipo tenha todas as seguintes características:

  • Representa logicamente um valor único, semelhante aos tipos primitivos (número inteiro, duplo e assim por diante).
  • Tem um tamanho de instância menor que 16 bytes.
  • É imutável.
  • Não precisará ser embalado com freqüência.
OwenP
fonte
1
Talvez eu esteja perdendo algo óbvio, mas não entendo bem o motivo por trás da parte "imutável". Por que isso é necessário? Alguém poderia explicar isso?
Tamas Czinege 22/01/09
3
Eles provavelmente recomendaram isso porque, se a estrutura for imutável, não importará que ela tenha semântica de valor em vez de semântica de referência. A distinção importa apenas se você alterar o objeto / estrutura após fazer uma cópia.
Stephen C. Steel
@DrJokepu: Em algumas situações, o sistema fará uma cópia temporária de uma estrutura e permitirá que essa cópia seja passada por referência ao código que a altera; como a cópia temporária será descartada, a alteração será perdida. Esse problema é especialmente grave se uma estrutura tiver métodos que a modifiquem. Eu discordo enfaticamente da noção de que a mutabilidade é uma razão para tornar algo uma classe, pois - apesar de algumas deficiências no c # e no vb.net, as estruturas mutáveis ​​fornecem semântica útil que não pode ser alcançada de nenhuma outra maneira; não há razão semântica para preferir uma estrutura imutável a uma classe.
supercat
4
@Chuu: Ao projetar o compilador JIT, a Microsoft decidiu otimizar o código para copiar estruturas com 16 bytes ou menos; isso significa que copiar uma estrutura de 17 bytes pode ser significativamente mais lento do que copiar uma estrutura de 16 bytes. Não vejo nenhum motivo específico para esperar que a Microsoft estenda essas otimizações para estruturas maiores, mas é importante observar que, embora estruturas de 17 bytes possam ser mais lentas para copiar do que estruturas de 16 bytes, há muitos casos em que estruturas grandes podem ser mais eficientes do que objetos de classe grande e onde a vantagem relativa de estruturas cresce com o tamanho da estrutura.
Supercat
3
@Chuu: A aplicação dos mesmos padrões de uso a grandes estruturas, como seria possível às classes, pode resultar em código ineficiente, mas a solução adequada geralmente não é substituir as estruturas por classes, mas sim usá-las com mais eficiência; mais notavelmente, deve-se evitar passar ou retornar estruturas por valor. Passe-os como refparâmetros sempre que for razoável. Passar uma estrutura com 4.000 campos como parâmetro ref para um método que altera um será mais barato do que passar uma estrutura com 4 campos por valor para um método que retorna a versão modificada.
Supercat
53

Estou surpreso por não ter lido nenhuma das respostas anteriores, que considero o aspecto mais crucial:

Eu uso estruturas quando quero um tipo sem identidade. Por exemplo, um ponto 3D:

public struct ThreeDimensionalPoint
{
    public readonly int X, Y, Z;
    public ThreeDimensionalPoint(int x, int y, int z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    public override string ToString()
    {
        return "(X=" + this.X + ", Y=" + this.Y + ", Z=" + this.Z + ")";
    }

    public override int GetHashCode()
    {
        return (this.X + 2) ^ (this.Y + 2) ^ (this.Z + 2);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is ThreeDimensionalPoint))
            return false;
        ThreeDimensionalPoint other = (ThreeDimensionalPoint)obj;
        return this == other;
    }

    public static bool operator ==(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
    {
        return p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z;
    }

    public static bool operator !=(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
    {
        return !(p1 == p2);
    }
}

Se você tem duas instâncias dessa estrutura, não se importa se elas são uma única peça de dados na memória ou duas. Você apenas se preocupa com os valores que eles possuem.

Andrei Rînea
fonte
4
Pouco comentado, mas por que você lança uma ArgumentException quando obj não é ThreeDimensionalPoint? Você não deveria simplesmente retornar falso nesse caso?
Svish
4
Está correto, eu estava muito ansioso. return falseé o que deveria estar lá, corrigindo agora.
Andrei Rînea
Um motivo interessante para usar uma estrutura. Eu criei classes com GetHashCode e Equals definidas de forma semelhante ao que você mostra aqui, mas sempre tive que ter cuidado para não alterar essas instâncias se as usasse como chaves de dicionário. Provavelmente teria sido mais seguro se eu os tivesse definido como estruturas. (Porque então a chave seria uma cópia dos campos no momento a estrutura tornou-se uma chave de dicionário , então chave permaneceria inalterada se eu mais tarde mudou o original.)
ToolmakerSteve
No seu exemplo, tudo bem, porque você tem apenas 12 bytes, mas lembre-se de que, se houver muitos campos nessa estrutura que ultrapassam 16 bytes, considere usar uma classe e substituir os métodos GetHashCode e Equals.
Daniel Botero Correa
Um tipo de valor em DDD não significa que você deve necessariamente usar um tipo de valor em C #
Darragh
26

Bill Wagner tem um capítulo sobre isso em seu livro "c # eficaz" ( http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660 ). Ele conclui usando o seguinte princípio:

  1. A principal responsabilidade do tipo de armazenamento de dados?
  2. Sua interface pública é definida inteiramente por propriedades que acessam ou modificam seus membros de dados?
  3. Tem certeza de que seu tipo nunca terá subclasses?
  4. Tem certeza de que seu tipo nunca será tratado polimorficamente?

Se você responder 'yes' a todas as 4 perguntas: use uma struct. Caso contrário, use uma classe.

Bart Gijssens
fonte
1
então ... objetos de transferência de dados (DTOs) devem ser estruturas?
Cruizer
1
Eu diria que sim, se atender aos 4 critérios descritos acima. Por que um objeto de transferência de dados precisaria ser tratado de uma maneira específica?
Bart Gijssens
2
@cruizer depende da sua situação. Em um projeto, tínhamos campos de auditoria comuns em nossos DTOs e, portanto, escrevemos um DTO básico do qual outros herdaram.
Andrew Grothe
1
Todos, exceto (2) parecem excelentes princípios. Precisaria ver seu raciocínio para saber exatamente o que ele quer dizer com (2) e por quê.
precisa
1
@ ToolmakerSteve: Você terá que ler o livro para isso. Não pense que é justo copiar / colar grandes partes de um livro.
Bart Gijssens 03/04/19
15

Use uma estrutura quando desejar a semântica do tipo valor em vez do tipo de referência. As estruturas são copiadas por valor, portanto, tenha cuidado!

Veja também perguntas anteriores, por exemplo

Qual a diferença entre struct e classe no .NET?

Simon Steele
fonte
12

Eu usaria structs quando:

  1. um objeto deve ser somente leitura (toda vez que você passa / atribui uma estrutura, ele é copiado). Objetos somente leitura são ótimos quando se trata de processamento multithread, pois na maioria dos casos não exigem bloqueio.

  2. um objeto é pequeno e tem vida curta. Nesse caso, há uma boa chance de o objeto ser alocado na pilha, o que é muito mais eficiente do que colocá-lo no heap gerenciado. Além disso, a memória alocada pelo objeto será liberada assim que sair do seu escopo. Em outras palavras, é menos trabalhoso para o Garbage Collector e a memória é usada com mais eficiência.

Pawel Pabich
fonte
10

Use uma classe se:

  • Sua identidade é importante. As estruturas são copiadas implicitamente quando são passadas por valor para um método.
  • Ele terá um grande espaço de memória.
  • Seus campos precisam de inicializadores.
  • Você precisa herdar de uma classe base.
  • Você precisa de comportamento polimórfico;

Use uma estrutura se:

  • Ele atuará como um tipo primitivo (int, long, byte etc.).
  • Ele deve ter um pequeno espaço de memória.
  • Você está chamando um método P / Invoke que requer que uma estrutura seja transmitida por valor.
  • Você precisa reduzir o impacto da coleta de lixo no desempenho do aplicativo.
  • Seus campos precisam ser inicializados apenas com seus valores padrão. Esse valor seria zero para tipos numéricos, falso para tipos booleanos e nulo para tipos de referência.
    • Observe que no C # 6.0 as estruturas podem ter um construtor padrão que pode ser usado para inicializar os campos da estrutura para valores não padrão.
  • Você não precisa herdar de uma classe base (que não seja ValueType, da qual todas as estruturas herdam).
  • Você não precisa de comportamento polimórfico.
Yashwanth Chowdary Kata
fonte
5

Eu sempre usei um struct quando desejava agrupar alguns valores para retornar as coisas de uma chamada de método, mas não precisarei usá-lo para nada depois de ler esses valores. Apenas como uma maneira de manter as coisas limpas. Costumo ver as coisas em uma estrutura como "descartáveis" e as coisas em uma classe como mais úteis e "funcionais"

Ryan Skarin
fonte
4

Se uma entidade for imutável, a questão de usar uma estrutura ou uma classe geralmente será de desempenho, e não de semântica. Em um sistema de 32/64 bits, as referências de classe requerem armazenamento de 4/8 bytes, independentemente da quantidade de informações na classe; copiar uma referência de classe exigirá a cópia de 4/8 bytes. Por outro lado, todos os distintosA instância de classe terá 8/16 bytes de sobrecarga, além das informações que ela contém e do custo de memória das referências a ela. Suponha que se queira uma matriz de 500 entidades, cada uma contendo quatro números inteiros de 32 bits. Se a entidade for um tipo de estrutura, a matriz exigirá 8.000 bytes, independentemente de todas as 500 entidades serem todas idênticas, diferentes ou em algum lugar intermediário. Se a entidade for do tipo classe, a matriz de 500 referências ocupará 4.000 bytes. Se todas essas referências apontarem para objetos diferentes, os objetos exigiriam 24 bytes adicionais cada (12.000 bytes para todos os 500), um total de 16.000 bytes - o dobro do custo de armazenamento de um tipo de estrutura. Por outro lado, do código criou uma instância de objeto e copiou uma referência para todos os 500 slots da matriz, o custo total seria de 24 bytes para essa instância e 4, 000 para a matriz - um total de 4.024 bytes. Uma grande economia. Poucas situações funcionariam tão bem quanto a última, mas, em alguns casos, pode ser possível copiar algumas referências para slots de matriz suficientes para que esse compartilhamento valha a pena.

Se a entidade deve ser mutável, a questão de usar uma classe ou estrutura é, de certa forma, mais fácil. Suponha que "Thing" seja uma estrutura ou classe que possua um campo inteiro chamado xe faça o seguinte código:

  Coisa t1, t2;
  ...
  t2 = t1;
  t2.x = 5;

Alguém quer que a última declaração afete t1.x?

Se Thing for um tipo de classe, t1 e t2 serão equivalentes, ou seja, t1.x e t2.x também serão equivalentes. Portanto, a segunda instrução afetará t1.x. Se Thing for um tipo de estrutura, t1 e t2 serão instâncias diferentes, significando que t1.xe t2.x se referirão a números inteiros diferentes. Portanto, a segunda instrução não afetará t1.x.

Estruturas mutáveis ​​e classes mutáveis ​​têm comportamentos fundamentalmente diferentes, embora o .net tenha algumas peculiaridades no tratamento de mutações struct. Se alguém quiser um comportamento de tipo de valor (o que significa que "t2 = t1" copiará os dados de t1 para t2, deixando t1 e t2 como instâncias distintas) e se puder conviver com as peculiaridades no manuseio de tipos de valores pela .net, use uma estrutura. Se alguém quiser semântica de tipo de valor, mas as peculiaridades de .net causariam uma semântica de tipo de valor quebrada no aplicativo, use uma classe e murmure.

supercat
fonte
3

Além disso, as excelentes respostas acima:

Estruturas são tipos de valor.

Eles nunca podem ser definidos como Nothing .

Definir uma estrutura = Nothing, definirá todos os seus tipos de valores para seus valores padrão.

George Filippakos
fonte
2

quando você realmente não precisa de comportamento, mas precisa de mais estrutura do que uma simples matriz ou dicionário.

Acompanhamento É assim que penso nas estruturas em geral. Eu sei que eles podem ter métodos, mas eu gosto de manter essa distinção mental geral.

Jim Deville
fonte
Por que você diz isso? Estruturas podem ter métodos.
Esteban Araya
2

Como o @Simon disse, as estruturas fornecem semântica "do tipo valor"; portanto, se você precisar de um comportamento semelhante ao tipo de dados interno, use uma struct. Como as estruturas são passadas por cópia, você deseja garantir que elas sejam pequenas em tamanho, cerca de 16 bytes.

Scott Dorman
fonte
2

É um tópico antigo, mas queria fornecer um teste de benchmark simples.

Eu criei dois arquivos .cs:

public class TestClass
{
    public long ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

e

public struct TestStruct
{
    public long ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Executar referência:

  • Crie 1 TestClass
  • Crie 1 TestStruct
  • Criar 100 TestClass
  • Crie 100 TestStruct
  • Criar 10000 TestClass
  • Criar 10000 TestStruct

Resultados:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.101
[Host]     : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT  [AttachedDebugger]
DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT


|         Method |           Mean |         Error |        StdDev |     Ratio | RatioSD | Rank |    Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------- |---------------:|--------------:|--------------:|----------:|--------:|-----:|---------:|------:|------:|----------:|

|      UseStruct |      0.0000 ns |     0.0000 ns |     0.0000 ns |     0.000 |    0.00 |    1 |        - |     - |     - |         - |
|       UseClass |      8.1425 ns |     0.1873 ns |     0.1839 ns |     1.000 |    0.00 |    2 |   0.0127 |     - |     - |      40 B |
|   Use100Struct |     36.9359 ns |     0.4026 ns |     0.3569 ns |     4.548 |    0.12 |    3 |        - |     - |     - |         - |
|    Use100Class |    759.3495 ns |    14.8029 ns |    17.0471 ns |    93.144 |    3.24 |    4 |   1.2751 |     - |     - |    4000 B |
| Use10000Struct |  3,002.1976 ns |    25.4853 ns |    22.5920 ns |   369.664 |    8.91 |    5 |        - |     - |     - |         - |
|  Use10000Class | 76,529.2751 ns | 1,570.9425 ns | 2,667.5795 ns | 9,440.182 |  346.76 |    6 | 127.4414 |     - |     - |  400000 B |
Eduardas Šlutas
fonte
1

Hmm...

Eu não usaria a coleta de lixo como argumento a favor / contra o uso de estruturas versus classes. O heap gerenciado funciona como uma pilha - criar um objeto apenas o coloca no topo do heap, quase tão rápido quanto a alocação na pilha. Além disso, se um objeto tem vida curta e não sobrevive a um ciclo de GC, a desalocação é gratuita, pois o GC funciona apenas com memória ainda acessível. (Pesquise no MSDN, há uma série de artigos sobre gerenciamento de memória .NET. Estou com preguiça de procurar por eles).

Na maioria das vezes eu uso uma estrutura, acabo me chutando por fazer isso, porque depois descobri que ter semântica de referência tornaria as coisas um pouco mais simples.

De qualquer forma, esses quatro pontos no artigo do MSDN postados acima parecem uma boa diretriz.


fonte
1
Se, às vezes, você precisar de semântica de referência com uma estrutura, basta declarar class MutableHolder<T> { public T Value; MutableHolder(T value) {Value = value;} }e a MutableHolder<T>será um objeto com semântica de classe mutável (isso funciona da mesma forma se Tfor uma estrutura ou um tipo de classe imutável).
supercat
1

As estruturas estão na pilha e não no heap; portanto, são seguras para encadeamento e devem ser usadas ao implementar o padrão de objeto de transferência; você nunca deseja usar objetos no heap, eles são voláteis; nesse caso, você deseja usar a pilha de chamadas, este é um caso básico para usar uma estrutura. Estou surpreso com todas as respostas aqui,

Jack
fonte
-3

Eu acho que a melhor resposta é simplesmente usar struct quando o que você precisa é uma coleção de propriedades, classe quando é uma coleção de propriedades E comportamentos.

Lucian Gabriel Popescu
fonte
estruturas pode ter métodos muito
Nithin Chandran
é claro, mas se você precisar de métodos, as chances são de 99% e você está usando incorretamente struct em vez de classe. As únicas exceções que encontrei quando é ok para ter métodos em struct são chamadas de retorno
Lucian Gabriel Popescu