Quando devo usar uma estrutura em vez de uma classe em C #?


1391

Quando você deve usar struct e não classe em c #? Meu modelo conceitual é que as estruturas são usadas nos momentos em que o item é apenas uma coleção de tipos de valor . Uma maneira de uni-los logicamente em um todo coeso.

Encontrei estas regras aqui :

  • Uma estrutura deve representar um valor único.
  • Uma estrutura deve ter um espaço de memória menor que 16 bytes.
  • Uma estrutura não deve ser alterada após a criação.

Essas regras funcionam? O que significa uma estrutura semanticamente?


248
System.Drawing.Rectangleviola todas essas três regras.
22430 ChrisW

4
existem muito poucos jogos comerciais escritos em C #, o ponto é que eles são usados para o código otimizado
BlackTigerX

25
As estruturas fornecem melhor desempenho quando você tem pequenas coleções de tipos de valor que deseja agrupar. Isso acontece o tempo todo na programação de jogos, por exemplo, um vértice em um modelo 3D terá uma posição, coordenada de textura e um normal, e geralmente também será imutável. Um único modelo pode ter alguns mil vértices ou uma dúzia, mas as estruturas fornecem menos sobrecarga geral nesse cenário de uso. Eu verifiquei isso através do meu próprio design de motor.
21710 Chris D.


4
@ Chrishr eu vejo, mas esses valores não representam um retângulo, ou seja, um valor "único"? Como Vector3D ou Color, eles também possuem vários valores, mas acho que eles representam valores únicos?
Marson Mao

Respostas:


604

A fonte mencionada pelo OP tem alguma credibilidade ... mas e a Microsoft - qual é a posição sobre o uso da estrutura? Procurei um aprendizado extra da Microsoft e aqui está o que encontrei:

Considere definir uma estrutura em vez de uma classe se as instâncias do tipo forem pequenas e geralmente duram pouco ou geralmente são incorporadas a outros objetos.

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

  1. Representa logicamente um valor único, semelhante aos tipos primitivos (número inteiro, duplo e assim por diante).
  2. Possui um tamanho de instância menor que 16 bytes.
  3. É imutável.
  4. Não precisará ser embalado com freqüência.

A Microsoft viola consistentemente essas regras

Ok, # 2 e # 3 de qualquer maneira. Nosso amado dicionário possui 2 estruturas internas:

[StructLayout(LayoutKind.Sequential)]  // default for structs
private struct Entry  //<Tkey, TValue>
{
    //  View code at *Reference Source
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  View code at *Reference Source
}

* Fonte de referência

A fonte 'JonnyCantCode.com' obteve 3 em 4 - bastante perdoável, já que o nº 4 provavelmente não seria um problema. Se você estiver no boxe de uma estrutura, repensar sua arquitetura.

Vejamos por que a Microsoft usaria essas estruturas:

  1. Cada estrutura Entrye Enumeratorrepresenta valores únicos.
  2. Rapidez
  3. Entrynunca é passado como um parâmetro fora da classe Dictionary. Investigações adicionais mostram que, para satisfazer a implementação de IEnumerable, o Dictionary usa a Enumeratorestrutura que ele copia toda vez que um enumerador é solicitado ... faz sentido.
  4. Interno para a classe Dictionary. Enumeratoré público porque Dictionary é enumerável e deve ter acessibilidade igual à implementação da interface IEnumerator - por exemplo, getter IEnumerator.

Atualização - Além disso, saiba que quando uma estrutura implementa uma interface - como o Enumerator - e é convertida para esse tipo implementado, a estrutura se torna um tipo de referência e é movida para o heap. Interno à classe Dictionary, o Enumerator ainda é um tipo de valor. No entanto, assim que um método é chamado GetEnumerator(), um tipo de referência IEnumeratoré retornado.

O que não vemos aqui é qualquer tentativa ou prova de exigência de manter estruturas imutáveis ​​ou manter um tamanho de instância de apenas 16 bytes ou menos:

  1. Nada nas estruturas acima é declarado readonly- não é imutável
  2. O tamanho dessas estruturas pode ter mais de 16 bytes
  3. Entrytem um tempo de vida indeterminada (a partir de Add(), a Remove(), Clear()ou a recolha de lixo);

E ... 4. Ambas as estruturas armazenam TKey e TValue, que todos sabemos que são capazes de serem tipos de referência (informações adicionais sobre bônus)

Não obstante as chaves com hash, os dicionários são rápidos em parte porque instanciar uma estrutura é mais rápido que um tipo de referência. Aqui, eu tenho um Dictionary<int, int>que armazena 300.000 números aleatórios com chaves incrementadas sequencialmente.

Capacidade: 312874
MemSize: 2660827 bytes
Concluído Redimensionar: 5ms
Tempo total para preencher: 889ms

Capacidade : número de elementos disponíveis antes que a matriz interna precise ser redimensionada.

MemSize : determinado serializando o dicionário em um MemoryStream e obtendo um comprimento de bytes (preciso o suficiente para nossos propósitos).

Redimensionamento concluído : o tempo necessário para redimensionar a matriz interna de 150862 elementos para 312874 elementos. Quando você descobre que cada elemento é copiado sequencialmente Array.CopyTo(), isso não é muito ruim.

Tempo total para preencher : reconhecidamente distorcido devido ao log e a um OnResizeevento que adicionei à fonte; no entanto, ainda é impressionante preencher 300k números inteiros enquanto redimensiona 15 vezes durante a operação. Por curiosidade, qual seria o tempo total a preencher se eu já soubesse a capacidade? 13ms

Então, agora, e se Entryfosse uma aula? Esses tempos ou métricas realmente diferem tanto assim?

Capacidade: 312874
MemSize: 2660827 bytes
Concluído Redimensionar: 26ms
Tempo total para preencher: 964ms

Obviamente, a grande diferença está no redimensionamento. Alguma diferença se o Dictionary for inicializado com o Capacity? Não basta se preocupar com ... 12ms .

O que acontece é que, por Entryser uma estrutura, não requer inicialização como um tipo de referência. Essa é a beleza e a desgraça do tipo de valor. Para usar Entrycomo um tipo de referência, tive que inserir o seguinte código:

/*
 *  Added to satisfy initialization of entry elements --
 *  this is where the extra time is spent resizing the Entry array
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Entry( );
}
/*  *********************************************** */  

O motivo pelo qual eu tive que inicializar cada elemento da matriz Entrycomo um tipo de referência pode ser encontrado no MSDN: Design de Estrutura . Em resumo:

Não forneça um construtor padrão para uma estrutura.

Se uma estrutura define um construtor padrão, quando matrizes da estrutura são criadas, o common language runtime executa automaticamente o construtor padrão em cada elemento da matriz.

Alguns compiladores, como o compilador C #, não permitem que estruturas tenham construtores padrão.

Na verdade, é bastante simples e vamos emprestar as Três Leis da Robótica de Asimov :

  1. A estrutura deve ser segura para usar
  2. A estrutura deve executar sua função com eficiência, a menos que isso viole a regra 1
  3. A estrutura deve permanecer intacta durante seu uso, a menos que sua destruição seja necessária para satisfazer a regra nº 1

... o que tiramos disso : em resumo, seja responsável pelo uso de tipos de valor. Eles são rápidos e eficientes, mas têm a capacidade de causar muitos comportamentos inesperados, se não forem adequadamente mantidos (por exemplo, cópias não intencionais).


8
Quanto às regras da Microsoft, a regra sobre imutabilidade parece ter sido projetada para desencorajar o uso de tipos de valor de tal maneira que seu comportamento seja diferente do dos tipos de referência, apesar do fato de que a semântica de valores mutável por partes pode ser útil . Se ter um tipo mutável por partes facilitaria o trabalho, e se os locais de armazenamento do tipo deveriam ser logicamente separados uns dos outros, o tipo deve ser uma estrutura "mutável".
Supercat


2
O fato de muitos tipos da Microsoft violarem essas regras não representa um problema com esses tipos, mas indica que as regras não devem se aplicar a todos os tipos de estrutura. Se uma estrutura representa uma única entidade [como com Decimalou DateTime], se não cumprir as outras três regras, deve ser substituída por uma classe. Se uma estrutura possui uma coleção fixa de variáveis, cada uma das quais pode conter qualquer valor que seja válido para seu tipo [por exemplo Rectangle], deve obedecer a regras diferentes , algumas das quais são contrárias às estruturas de "valor único" .
Supercat 6/13

4
@IAbstract: Algumas pessoas justificariam o Dictionarytipo de entrada com base em que é apenas um tipo interno, o desempenho foi considerado mais importante que a semântica, ou alguma outra desculpa. Meu argumento é que um tipo como Rectangledeve ter seu conteúdo exposto como campos editáveis ​​individualmente, não "porque" os benefícios de desempenho superam as imperfeições semânticas resultantes, mas porque o tipo representa semanticamente um conjunto fixo de valores independentes e, portanto, a estrutura mutável é melhor desempenho e semanticamente superior .
Supercat

2
@ supercat: Eu concordo ... e o ponto principal da minha resposta foi que as 'diretrizes' são bastante fracas e as estruturas devem ser usadas com pleno conhecimento e compreensão dos comportamentos. Veja minha resposta na estrutura mutável aqui: stackoverflow.com/questions/8108920/…
IAbstract

155

Sempre que você:

  1. não precisa de polimorfismo,
  2. deseja semântica de valor e
  3. deseja evitar a alocação de heap e a sobrecarga de coleta de lixo associada.

A ressalva, no entanto, é que as estruturas (arbitrariamente grandes) são mais caras de se passar do que as referências de classe (geralmente uma palavra de máquina), portanto as classes podem acabar sendo mais rápidas na prática.


1
Essa é apenas uma "ressalva". Também deve considerar o "levantamento" de tipos de valor e casos como (Guid)null(não há problema em converter um nulo em um tipo de referência), entre outras coisas.

1
mais caro do que em C / C ++? em C ++ da forma recomendada é a passar objectos por valor
Ion Todirel

@IonTodirel Não foi por razões de segurança de memória, e não por desempenho? É sempre uma troca, mas passar 32 B pela pilha sempre (TM) será mais lento do que passar uma referência 4 B pelo registro. No entanto , observe também que o uso de "valor / referência" é um pouco diferente em C # e C ++ - quando você passa uma referência a um objeto, ainda passa por valor, mesmo que esteja passando uma referência (você ' passando o valor da referência, não uma referência à referência, basicamente). Não é semântica de valores , mas tecnicamente é "passagem por valor".
23415 Luaan 23/11

@Luaan Copiar é apenas um aspecto dos custos. A indireção extra devido ao ponteiro / referência também custa por acesso. Em alguns casos, a estrutura pode até ser movida e, portanto, nem precisa ser copiada.
Onur

@ Onur isso é interessante. Como você "se move" sem copiar? Eu pensei que a instrução asm "mov" na verdade não "se move". Ele copia.
Winger Sendon

148

Eu não concordo com as regras dadas no post original. Aqui estão as minhas regras:

1) Você usa estruturas de desempenho quando armazenadas em matrizes. (veja também Quando as estruturas são a resposta? )

2) Você precisa deles no código que passa dados estruturados de / para C / C ++

3) Não use estruturas a menos que precise delas:

  • Eles se comportam de maneira diferente de "objetos normais" ( tipos de referência ) sob atribuição e quando passam como argumentos, o que pode levar a um comportamento inesperado; isso é particularmente perigoso se a pessoa que olha o código não sabe que está lidando com uma estrutura.
  • Eles não podem ser herdados.
  • Passar estruturas como argumentos é mais caro que classes.

4
+1 Sim, eu concordo totalmente com o no. 1 (essa é uma grande vantagem ao lidar com coisas como imagens, etc.) e por apontar que eles são diferentes de "objetos normais" e que há maneira de saber isso, exceto pelo conhecimento existente ou examinando o próprio tipo. Além disso, você não pode converter um valor nulo em um tipo de estrutura :-) Este é realmente um caso em que quase desejo que haja algum 'húngaro' para tipos de valores não essenciais ou uma palavra-chave 'struct' obrigatória no site de declaração variável .

@ pst: É verdade que é preciso saber que algo é um structpara saber como se comportará, mas se algo é structcom campos expostos, é tudo o que se precisa saber. Se um objeto expõe uma propriedade de um tipo de estrutura de campo exposto e se o código lê essa estrutura em uma variável e se modifica, é possível prever com segurança que essa ação não afetará o objeto cuja propriedade foi lida, a menos ou até que a estrutura seja gravada de volta. Por outro lado, se a propriedade fosse um tipo mutável classe, lê-lo e modificá-lo pode atualizar o objeto subjacente como esperado, mas ...
supercat

... também pode acabar mudando nada, ou pode mudar ou corromper objetos que não se pretendem mudar. Ter código cuja semântica diga "altere essa variável a seu gosto; as alterações não farão nada até que você as armazene explicitamente em algum lugar" parece mais claro do que o código que diz "Você está obtendo uma referência a algum objeto, que pode ser compartilhado com qualquer número" de outras referências ou talvez não sejam compartilhadas; você terá que descobrir quem mais pode ter referências a esse objeto para saber o que acontecerá se você o alterar ".
Supercat

Spot com # 1. Uma lista cheia de estruturas pode espremer dados muito mais relevantes nos caches L1 / L2 do que uma lista cheia de referências a objetos (para a estrutura de tamanho correta).
Matt Stephenson

2
A herança raramente é a ferramenta certa para o trabalho, e raciocinar muito sobre desempenho sem criação de perfil é uma má idéia. Em primeiro lugar, as estruturas podem ser passadas por referência. Em segundo lugar, passar por referência ou por valor raramente é um problema significativo de desempenho. Por fim, você não está contabilizando a alocação de heap e a coleta de lixo adicionais que precisam ocorrer para uma classe. Pessoalmente, prefiro pensar em estruturas como dados antigos simples e classes como coisas que fazem coisas (objetos), embora você também possa definir métodos em estruturas.
weberc2

88

Use uma estrutura quando desejar valor semântica, em vez de referenciar semântica.

Editar

Não sei por que as pessoas estão votando contra isso, mas este é um ponto válido, e foi feito antes da operação esclarecer sua pergunta, e é a razão básica mais fundamental para uma estrutura.

Se você precisar de semântica de referência, precisará de uma classe e não de uma estrutura.


13
Todo mundo sabe disso. Parece que ele está procurando mais do que uma resposta "struct é um tipo de valor".
6309 TheSmurf

21
É o caso mais básico e deve ser indicado para quem lê este post e não sabe disso.
21139 JoshBerke

3
Não que essa resposta não seja verdadeira; obviamente é. Esse não é realmente o ponto.
6309 TheSmurf

55
@ Josh: Para quem ainda não o conhece, basta dizer que é uma resposta insuficiente, pois é bem provável que eles também não saibam o que isso significa.
TheSmurf 06/02/09

1
Acabei de votar isso porque acho que uma das outras respostas deve estar no topo - qualquer resposta que diga "Para interoperabilidade com código não gerenciado, caso contrário, evite".
2111 Daniel Earwicker

59

Além da resposta "é um valor", um cenário específico para o uso de estruturas é quando você sabe que possui um conjunto de dados que está causando problemas de coleta de lixo e possui muitos objetos. Por exemplo, uma grande lista / matriz de instâncias de Pessoa. A metáfora natural aqui é uma classe, mas se você tiver um grande número de instâncias Person de vida longa, elas poderão acabar entupindo o GEN-2 e causando paradas do GC. Se o cenário o justificar, uma abordagem potencial aqui é usar uma matriz (não lista) de estruturas Person , ou seja Person[]. Agora, em vez de ter milhões de objetos no GEN-2, você tem um único pedaço no LOH (não estou assumindo seqüências de caracteres etc. - aqui, um valor puro sem referências). Isso tem muito pouco impacto no GC.

Trabalhar com esses dados é complicado, pois os dados provavelmente estão superdimensionados para uma estrutura e você não deseja copiar valores de gordura o tempo todo. No entanto, acessá-lo diretamente em uma matriz não copia a estrutura - ela está no local (contraste com um indexador de lista, que copia). Isso significa muito trabalho com índices:

int index = ...
int id = peopleArray[index].Id;

Observe que manter os próprios valores imutáveis ​​ajudará aqui. Para uma lógica mais complexa, use um método com um parâmetro by-ref:

void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);

Novamente, isso está no lugar - não copiamos o valor.

Em cenários muito específicos, essa tática pode ser muito bem-sucedida; no entanto, é um cenário bastante avançado que deve ser tentado apenas se você souber o que está fazendo e por quê. O padrão aqui seria uma classe.


+1 resposta interessante. Você gostaria de compartilhar histórias do mundo real sobre essa abordagem ser usada?
Jordão

@Jordao no celular, mas pesquise no google por: + gravell + "assalto por GC"
Marc Gravell

1
Muito obrigado. Encontrei aqui .
Jordão

2
@ MarcGravell Por que você mencionou: use uma matriz (não lista) ? ListEu acredito, usa um Arraybastidores. não ?
Royi Namir 11/02

4
@RoyiNamir Também fiquei curioso sobre isso, mas acredito que a resposta está no segundo parágrafo da resposta de Marc. "No entanto, acessá-lo diretamente em uma matriz não copia a estrutura - ela está no local (contraste com um indexador de lista, que copia)."
user1323245

40

Na especificação da linguagem C # :

1.7 Estruturas

Como as classes, as estruturas são estruturas de dados que podem conter membros de dados e membros da função, mas, diferentemente das classes, as estruturas são tipos de valor e não requerem alocação de heap. Uma variável de um tipo de estrutura armazena diretamente os dados da estrutura, enquanto uma variável de um tipo de classe armazena uma referência a um objeto alocado dinamicamente. Os tipos de estrutura não suportam a herança especificada pelo usuário e todos os tipos de estrutura herdam implicitamente do objeto de tipo.

Estruturas são particularmente úteis para pequenas estruturas de dados que possuem semântica de valor. Números complexos, pontos em um sistema de coordenadas ou pares de valores-chave em um dicionário são bons exemplos de estruturas. O uso de estruturas em vez de classes para pequenas estruturas de dados pode fazer uma grande diferença no número de alocações de memória que um aplicativo executa. Por exemplo, o programa a seguir cria e inicializa uma matriz de 100 pontos. Com o Point implementado como uma classe, 101 objetos separados são instanciados - um para a matriz e um para os 100 elementos.

class Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

Uma alternativa é fazer do Point uma estrutura.

struct Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

Agora, apenas um objeto é instanciado - o da matriz - e as instâncias Point são armazenadas em linha na matriz.

Os construtores Struct são chamados com o novo operador, mas isso não implica que a memória esteja sendo alocada. Em vez de alocar dinamicamente um objeto e retornar uma referência a ele, um construtor struct simplesmente retorna o próprio valor struct (normalmente em um local temporário na pilha), e esse valor é copiado conforme necessário.

Com as classes, é possível que duas variáveis ​​façam referência ao mesmo objeto e, portanto, que operações em uma variável afetem o objeto referenciado pela outra variável. Com estruturas, cada uma das variáveis ​​possui sua própria cópia dos dados, e não é possível que operações em uma afetem a outra. Por exemplo, a saída produzida pelo seguinte fragmento de código depende se Point é uma classe ou uma estrutura.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Se Point é uma classe, a saída é 20 porque aeb fazem referência ao mesmo objeto. Se Point for uma estrutura, a saída será 10 porque a atribuição de a a b cria uma cópia do valor, e essa cópia não é afetada pela atribuição subsequente a ax

O exemplo anterior destaca duas das limitações de estruturas. Primeiro, copiar uma estrutura inteira normalmente é menos eficiente do que copiar uma referência de objeto, portanto, a atribuição e a passagem de parâmetros de valor podem ser mais caras com estruturas do que com tipos de referência. Segundo, exceto pelos parâmetros ref e out, não é possível criar referências a estruturas, o que descarta seu uso em várias situações.


4
Embora o fato de que referências a estruturas não possam ser persistidas às vezes seja uma limitação, também é uma característica muito útil. Uma das principais fraquezas do .net é que não há uma maneira decente de passar para fora do código uma referência a um objeto mutável sem perder o controle para sempre desse objeto. Por outro lado, pode-se dar com segurança um método externo a refa uma estrutura mutável e saber que quaisquer mutações que o método externo executará nele serão feitas antes que ele retorne. É muito ruim .net não tem nenhum conceito de parâmetros efêmeros e valores de retorno de função, desde que ... #
31812

4
... isso permitiria que a semântica vantajosa das estruturas passadas reffosse alcançada com objetos de classe. Essencialmente, variáveis ​​locais, parâmetros e valores de retorno de função podem ser persistentes (padrão), retornáveis ​​ou efêmeros. O código seria proibido de copiar coisas efêmeras para qualquer coisa que sobreviveria ao presente escopo. Coisas retornáveis ​​seriam coisas efêmeras, exceto que elas poderiam ser retornadas de uma função. O valor de retorno de uma função seria limitado pelas restrições mais rígidas aplicáveis ​​a qualquer um dos seus parâmetros "retornáveis".
Supercat

34

As estruturas são boas para a representação atômica dos dados, onde os dados podem ser copiados várias vezes pelo código. A clonagem de um objeto geralmente é mais cara do que copiar uma estrutura, pois envolve alocar a memória, executar o construtor e desalocar / coletar lixo quando terminar com ele.


4
Sim, mas grandes estruturas podem ser mais caras que as referências de classe (ao passar para métodos).
1028 Alex

27

Aqui está uma regra básica.

  • Se todos os campos de membro forem tipos de valor, crie uma estrutura .

  • Se qualquer campo de um membro for um tipo de referência, crie uma classe . Isso ocorre porque o campo do tipo de referência precisará da alocação de heap de qualquer maneira.

Exmaples

public struct MyPoint 
{
    public int X; // Value Type
    public int Y; // Value Type
}

public class MyPointWithName 
{
    public int X; // Value Type
    public int Y; // Value Type
    public string Name; // Reference Type
}

3
Tipos de referência imutáveis ​​como stringsão semanticamente equivalentes a valores e armazenar uma referência a um objeto imutável em um campo não implica uma alocação de heap. A diferença entre uma estrutura com campos públicos expostos e um objeto de classe com campos públicos expostos é que, dada a sequência do código var q=p; p.X=4; q.X=5;, p.Xterá o valor 4 se afor um tipo de estrutura e 5 se for um tipo de classe. Se alguém deseja modificar convenientemente os membros do tipo, deve selecionar 'classe' ou 'estrutura' com base em se deseja que as alterações qafetem p.
Supercat

Sim, eu concordo que a variável de referência estará na pilha, mas o objeto a que se refere existirá na pilha. Embora estruturas e classes se comportem de maneira diferente quando atribuídas a uma variável diferente, mas eu não acho que isso seja um forte fator de decisão.
Usman Zafar

Estruturas mutáveis ​​e classes mutáveis ​​se comportam de maneira completamente diferente; se um estiver certo, o outro provavelmente estará errado. Não tenho certeza de como o comportamento não seria um fator decisivo na determinação de usar uma estrutura ou uma classe.
Supercat

Eu disse que não é um forte fator de decisão, porque muitas vezes quando você está criando uma classe ou estrutura não tem certeza de como será usado. Então você se concentra em como as coisas fazem mais sentido da perspectiva do design. Enfim, eu nunca vi em um único lugar na biblioteca. NET onde uma estrutura contém uma variável de referência.
Usman Zafar

1
O tipo de estrutura ArraySegment<T>encapsula a T[], que é sempre um tipo de classe. O tipo de estrutura KeyValuePair<TKey,TValue>é frequentemente usado com tipos de classe como parâmetros genéricos.
supercat

19

Primeiro: cenários de interoperabilidade ou quando você precisa especificar o layout da memória

Segundo: quando os dados tiverem quase o mesmo tamanho que um ponteiro de referência.


17

Você precisa usar uma "estrutura" nas situações em que deseja especificar explicitamente o layout da memória usando o StructLayoutAttribute - normalmente para PInvoke.

Edit: Comment indica que você pode usar classe ou estrutura com StructLayoutAttribute e isso certamente é verdade. Na prática, você normalmente usaria uma estrutura - ela é alocada na pilha versus a pilha, o que faz sentido se você está apenas passando um argumento para uma chamada de método não gerenciado.


5
O StructLayoutAttribute pode ser aplicado a estruturas ou classes, portanto, esse não é um motivo para usar estruturas.
6139 Stephen Martin

Por que faz sentido se você está apenas passando um argumento para uma chamada de método não gerenciado?
precisa saber é o seguinte

16

Uso estruturas para empacotar ou descompactar qualquer tipo de formato de comunicação binário. Isso inclui ler ou gravar em disco, listas de vértices do DirectX, protocolos de rede ou lidar com dados criptografados / compactados.

As três diretrizes que você listou não foram úteis para mim neste contexto. Quando eu precisar escrever quatrocentos bytes de material em uma Ordem Particular, vou definir uma estrutura de quatrocentos bytes e preenchê-la com quaisquer valores não relacionados que ela deva ter, e vou configurá-lo de qualquer maneira que faça mais sentido no momento. (Certo, quatrocentos bytes seriam bem estranhos - mas quando eu estava escrevendo arquivos do Excel para viver, estava lidando com estruturas de até quarenta bytes por toda parte, porque é o tamanho de alguns dos registros BIFF.)


Você não poderia usar tão facilmente um tipo de referência para isso?
precisa saber é o seguinte

15

Com exceção dos tipos de valor que são usados ​​diretamente pelo tempo de execução e vários outros para fins de PInvoke, você só deve usar tipos de valor em 2 cenários.

  1. Quando você precisar copiar semântica.
  2. Quando você precisa de inicialização automática, normalmente em matrizes desses tipos.

# 2 parece ser parte da razão para a prevalência struct em classes de coleção Net ..
iAbstract

Se a primeira coisa que você faria ao criar um local de armazenamento de um tipo de classe fosse criar uma nova instância desse tipo, armazene uma referência a ele nesse local e nunca copie a referência em outro lugar nem a substitua, em seguida, uma struct e a classe se comportaria de forma idêntica. As estruturas têm uma maneira padrão conveniente de copiar todos os campos de uma instância para outra e geralmente oferecem melhor desempenho nos casos em que nunca se duplicaria uma referência a uma classe (exceto o thisparâmetro efêmero usado para invocar seus métodos); classes permitem duplicar referências.
Supercat

13

O .NET suporta value typese reference types(em Java, você pode definir apenas tipos de referência). Instâncias de reference typesserem alocadas no heap gerenciado e são coletadas como lixo quando não há referências pendentes para elas. Instâncias de value types, por outro lado, são alocadas na stackmemória e, portanto, alocada são recuperadas assim que seu escopo termina. E, é claro, value typespasse pelo valor e reference typespela referência. Todos os tipos de dados primitivos em C #, exceto System.String, são tipos de valor.

Quando usar struct sobre a classe,

Em C #, structsare value types, classes are reference types. Você pode criar tipos de valor, em C #, usando a enumpalavra - chave e a structpalavra - chave. Usar um em value typevez de a reference typeresultará em menos objetos no heap gerenciado, o que resulta em menor carga no coletor de lixo (GC), ciclos de GC menos frequentes e, consequentemente, melhor desempenho. No entanto, também value typestêm suas desvantagens. Passar por um grande structé definitivamente mais caro do que passar uma referência, esse é um problema óbvio. O outro problema é a sobrecarga associada boxing/unboxing. Caso esteja se perguntando o que boxing/unboxingsignifica, siga estes links para obter uma boa explicação boxingeunboxing. Além do desempenho, há momentos em que você simplesmente precisa que os tipos possuam semântica de valores, o que seria muito difícil (ou feio) de implementar, se houver reference typestudo o que você tem. Você deve usar value typesapenas Quando você precisar copiar semântica ou precisar de inicialização automática, normalmente arraysnesses tipos.


Copiar pequenas estruturas ou passar por valor é tão barato quanto copiar ou passar uma referência de classe ou passar as estruturas por ref. Passar qualquer estrutura de tamanho por refcustos é o mesmo que passar uma referência de classe por valor. Copiar qualquer estrutura de tamanho ou passar por valor é mais barato do que executar uma cópia defensiva de um objeto de classe e armazenar ou passar uma referência a isso. As grandes épocas das classes são melhores do que as estruturas para armazenar valores são (1) quando as classes são imutáveis ​​(para evitar cópias defensivas), e cada instância criada é transmitida muito, ou ...
supercat

... (2) quando, por várias razões, uma estrutura simplesmente não seria utilizável [por exemplo, porque é necessário usar referências aninhadas para algo como uma árvore ou porque é necessário polimorfismo]. Observe que, ao usar tipos de valor, geralmente deve-se expor os campos diretamente ausentes por uma razão específica a não ser (enquanto que na maioria dos tipos de classes os campos devem ser agrupados nas propriedades). Muitos dos chamados "males" dos tipos de valores mutáveis ​​decorrem do agrupamento desnecessário de campos nas propriedades (por exemplo, enquanto alguns compiladores permitiriam chamar um configurador de propriedades em uma estrutura somente leitura, porque às vezes ...
supercat

... faça a coisa certa, todos os compiladores rejeitariam corretamente as tentativas de definir campos diretamente nessas estruturas; a melhor maneira de garantir que os compiladores rejeitem readOnlyStruct.someMember = 5;não é criar someMemberuma propriedade somente leitura, mas transformá-la em um campo.
Supercat

12

Uma estrutura é um tipo de valor. Se você atribuir uma estrutura a uma nova variável, a nova variável conterá uma cópia do original.

public struct IntStruct {
    public int Value {get; set;}
}

A execução dos seguintes resultados em 5 instâncias da estrutura armazenada na memória:

var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1;  // A copy is made
var struct3 = struct2;  // A copy is made
var struct4 = struct3;  // A copy is made
var struct5 = struct4;  // A copy is made

// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.

// Although structs are designed to use less system resources
// than classes.  If used incorrectly, they could use significantly more.

Uma classe é um tipo de referência. Quando você atribui uma classe a uma nova variável, a variável contém uma referência ao objeto de classe original.

public class IntClass {
    public int Value {get; set;}
}

A execução dos seguintes resultados em apenas uma instância do objeto de classe na memória.

var class1 = new IntClass() { Value = 0 };
var class2 = class1;  // A reference is made to class1
var class3 = class2;  // A reference is made to class1
var class4 = class3;  // A reference is made to class1
var class5 = class4;  // A reference is made to class1  

Struct s pode aumentar a probabilidade de um erro de código. Se um objeto de valor for tratado como um objeto de referência mutável, um desenvolvedor poderá se surpreender quando as alterações feitas forem inesperadamente perdidas.

var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when 
// struct1.Value is 0 and not 1

12

Fiz uma pequena referência com o BenchmarkDotNet para entender melhor o benefício "struct" em números. Estou testando loop através de matriz (ou lista) de estruturas (ou classes). Criar essas matrizes ou listas está fora do escopo do benchmark - é claro que "classe" é mais pesada utilizará mais memória e envolverá o GC.

Portanto, a conclusão é: tenha cuidado com o LINQ e as estruturas ocultas boxe / unboxing e o uso de estruturas para micro-otimizações permanecem estritamente com matrizes.

PS: Outra referência sobre a passagem de struct / class pela pilha de chamadas existe https://stackoverflow.com/a/47864451/506147

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

Código:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List<C1> testListClass = new List<C1>();
        List<S1> testListStruct = new List<S1>();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }

Você já descobriu por que as estruturas são muito mais lentas quando usadas em listas? É por causa do boxe e unboxing oculto que você mencionou? Se sim, por que isso acontece?
Marko Grdinic

O acesso à estrutura na matriz deve ser mais rápido, devido a nenhuma referência adicional necessária. Boxe / Unboxing é o caso de linq.
Roman Pokrovskij

10

Tipos de estrutura em C # ou outras linguagens .net geralmente são usados ​​para armazenar coisas que devem se comportar como grupos de valores de tamanho fixo. Um aspecto útil dos tipos de estrutura é que os campos de uma instância do tipo de estrutura podem ser modificados modificando o local de armazenamento em que ela é mantida e de nenhuma outra maneira. É possível codificar uma estrutura de tal maneira que a única maneira de alterar qualquer campo é construir uma instância nova e usar uma atribuição de estrutura para alterar todos os campos do destino, substituindo-os por valores da nova instância, mas a menos que uma estrutura não forneça meios de criar uma instância em que seus campos tenham valores não padrão, todos os seus campos serão mutáveis ​​se e se a própria estrutura estiver armazenada em um local mutável.

Observe que é possível projetar um tipo de estrutura para que ele se comporte essencialmente como um tipo de classe, se a estrutura contiver um campo particular do tipo de classe e redirecione seus próprios membros para o objeto de classe agrupado. Por exemplo, a PersonCollectionpode oferecer propriedades SortedByNamee SortedById, ambas com uma referência "imutável" a PersonCollection(definida no construtor) e implementada GetEnumeratorchamando creator.GetNameSortedEnumeratorou creator.GetIdSortedEnumerator. Tais estruturas se comportariam como uma referência a PersonCollection, exceto que seus GetEnumeratormétodos seriam vinculados a métodos diferentes no PersonCollection. Também é possível que uma estrutura envolva uma parte de uma matriz (por exemplo, pode-se definir uma ArrayRange<T>estrutura que conteria um T[]chamado Arr, um int Offsete um intLength, com uma propriedade indexada que, para um índice idxno intervalo de 0 a Length-1, acessaria Arr[idx+Offset]). Infelizmente, se foofor uma instância somente leitura dessa estrutura, as versões atuais do compilador não permitirão operações como foo[3]+=4;porque elas não têm como determinar se essas operações tentariam gravar nos campos de foo.

Também é possível projetar uma estrutura para se comportar como um tipo de valor que contém uma coleção de tamanho variável (que parecerá ser copiada sempre que a estrutura for), mas a única maneira de fazer esse trabalho é garantir que nenhum objeto para o qual struct mantém uma referência que será exposta a qualquer coisa que possa sofrer uma mutação. Por exemplo, pode-se ter uma estrutura do tipo matriz que contém uma matriz privada e cujo método "put" indexado cria uma nova matriz cujo conteúdo é semelhante ao original, exceto por um elemento alterado. Infelizmente, pode ser um pouco difícil fazer com que essas estruturas funcionem com eficiência. Embora haja momentos em que a semântica da estrutura pode ser conveniente (por exemplo, ser capaz de passar uma coleção do tipo matriz para uma rotina, com o chamador e o destinatário sabendo que o código externo não modifica a coleção,


10

Nah - eu não concordo totalmente com as regras. São boas diretrizes a serem consideradas com desempenho e padronização, mas não à luz das possibilidades.

Como você pode ver nas respostas, existem várias maneiras criativas de usá-las. Portanto, essas diretrizes precisam ser apenas isso, sempre por uma questão de desempenho e eficiência.

Nesse caso, eu uso classes para representar objetos do mundo real em sua forma maior, uso estruturas para representar objetos menores que têm usos mais exatos. Do jeito que você disse, "um todo mais coeso". A palavra-chave é coesa. As classes serão mais elementos orientados a objetos, enquanto estruturas podem ter algumas dessas características, embora em menor escala. IMO.

Eu os uso muito nas tags Treeview e Listview, onde atributos estáticos comuns podem ser acessados ​​muito rapidamente. Eu sempre lutei para obter essa informação de outra maneira. Por exemplo, em meus aplicativos de banco de dados, eu uso um Treeview em que tenho Tabelas, SPs, Funções ou quaisquer outros objetos. Eu crio e preencho minha estrutura, coloco na tag, retiro, obtenho os dados da seleção e assim por diante. Eu não faria isso com uma aula!

Eu tento mantê-los pequenos, usá-los em situações de instância única e impedir que eles mudem. É prudente estar ciente da memória, alocação e desempenho. E o teste é tão necessário.


As estruturas podem ser sensatamente usadas para representar objetos imutáveis ​​leves, ou podem ser sensatamente usadas para representar conjuntos fixos de variáveis ​​relacionadas, mas independentes (por exemplo, as coordenadas de um ponto). Os conselhos nessa página são bons para estruturas projetadas para servir ao objetivo anterior, mas estão erradas para estruturas projetadas para servir ao objetivo final. Meu pensamento atual é que estruturas que possuem campos particulares geralmente devem atender à descrição indicada, mas muitas estruturas devem expor todo o seu estado através de campos públicos.
22813 supercat

Se a especificação para um tipo de "ponto 3d" indicar que todo o seu estado é exposto por meio de membros legíveis x, ye z, e é possível criar uma instância com qualquer combinação de doublevalores para essas coordenadas, essa especificação o obrigaria a comportar-se semanticamente de forma idêntica a uma estrutura de campo exposto, exceto por alguns detalhes de comportamento multithread (a classe imutável seria melhor em alguns casos, enquanto a estrutura de campo exposto seria melhor em outras; uma estrutura chamada "imutável" seria pior em todos os casos).
Supercat

8

Minha regra é

1, sempre use classe;

2, se houver algum problema de desempenho, tento alterar alguma classe para estruturar, dependendo das regras mencionadas pela @IAbstract e, em seguida, faço um teste para verificar se essas alterações podem melhorar o desempenho.


Um caso de uso substancial que a Microsoft ignora é quando se deseja que uma variável do tipo Fooencapsule uma coleção fixa de valores independentes (por exemplo, coordenadas de um ponto) que, às vezes, você deseja passar como grupo e às vezes deseja mudar de forma independente. Eu não encontrei nenhum padrão para usar classes que combine ambos os propósitos tão bem quanto uma estrutura simples de campo exposto (que, sendo uma coleção fixa de variáveis ​​independentes, se encaixa perfeitamente).
Supercat

1
@ supercat: Eu acho que não é totalmente justo culpar a Microsoft por isso. O problema real aqui é que o C # como uma linguagem orientada a objetos simplesmente não se concentra em tipos de registro simples que apenas expõem dados sem muito comportamento. C # não é uma linguagem de múltiplos paradigmas na mesma extensão que, por exemplo, C ++. Dito isto, também acredito que poucas pessoas programam POO puro, então talvez o C # seja uma linguagem idealista demais. (Eu, pelo menos recentemente, também comecei a expor public readonlycampos nos meus tipos, porque a criação de propriedades somente leitura é simplesmente muito trabalhosa para praticamente nenhum benefício.)
stakx - não está mais contribuindo com

1
@takx: Não há necessidade de se "concentrar" nesses tipos; reconhecê-los pelo que são são suficientes. A maior fraqueza do C # em relação às estruturas também é seu maior problema em muitas outras áreas: a linguagem fornece instalações inadequadas para indicar quando determinadas transformações são ou não apropriadas, e a falta de tais instalações leva a decisões infelizes de design. Por exemplo, 99% das "estruturas mutáveis ​​são más" decorre da transformação do compilador MyListOfPoint[3].Offset(2,3);em var temp=MyListOfPoint[3]; temp.Offset(2,3);uma transformação que é falsa quando aplicada ...
supercat

... para o Offsetmétodo. A maneira correta de impedir esse código falso não deve tornar as estruturas desnecessariamente imutáveis, mas permitir que métodos como Offsetsejam marcados com um atributo que proíba a transformação mencionada acima. Conversões numéricas implícitas também poderiam ter sido muito melhores se pudessem ser etiquetadas de forma a serem aplicáveis ​​apenas nos casos em que sua chamada seria óbvia. Se existem sobrecargas para foo(float,float)e foo(double,double), gostaria de postular que a tentativa de usar um floate doublemuitas vezes não se deve aplicar uma conversão implícita, mas deve antes ser um erro.
Supercat 31/08

Uma atribuição direta de um doublevalor a float, ou transmiti-lo a um método que pode levar a um floatargumento, mas não o doublefaria, quase sempre faria o que o programador pretendia. Por outro lado, atribuir floatexpressão a doublesem uma conversão de texto explícita geralmente é um erro. O único momento em que permitir a double->floatconversão implícita causaria problemas seria quando uma sobrecarga abaixo do ideal seria selecionada. Eu diria que o caminho certo para evitar isso não deveria ser proibir implícito double-> float, mas marcar sobrecargas com atributos para impedir a conversão.
supercat 31/08

8

Uma classe é um tipo de referência. Quando um objeto da classe é criado, a variável à qual o objeto está atribuído mantém apenas uma referência a essa memória. Quando a referência do objeto é atribuída a uma nova variável, a nova variável se refere ao objeto original. As alterações feitas através de uma variável são refletidas na outra variável porque ambas se referem aos mesmos dados. Uma estrutura é um tipo de valor. Quando uma estrutura é criada, a variável à qual a estrutura é atribuída mantém os dados reais da estrutura. Quando a estrutura é atribuída a uma nova variável, ela é copiada. A nova variável e a variável original contêm, portanto, duas cópias separadas dos mesmos dados. As alterações feitas em uma cópia não afetam a outra cópia. Em geral, as classes são usadas para modelar comportamentos mais complexos ou dados que devem ser modificados após a criação de um objeto de classe.

Classes e estruturas (Guia de Programação em C #)


As estruturas também são muito boas nos casos em que é necessário fixar algumas variáveis ​​relacionadas, mas independentes, junto com fita adesiva (por exemplo, as coordenadas de um ponto). As diretrizes do MSDN são razoáveis ​​se alguém estiver tentando produzir estruturas que se comportam como objetos, mas são muito menos apropriadas ao projetar agregados; alguns deles estão quase precisamente errados nesta última situação. Por exemplo, quanto maior o grau de independência das variáveis ​​encapsuladas por um tipo, maior a vantagem de usar uma estrutura de campo exposto em vez de uma classe imutável.
Supercat

6

MITO 1: ESTRUTURAS SÃO CLASSES LEVE

Este mito vem em uma variedade de formas. Algumas pessoas acreditam que os tipos de valor não podem ou não devem ter métodos ou outro comportamento significativo - eles devem ser usados ​​como tipos simples de transferência de dados, com apenas campos públicos ou propriedades simples. O tipo DateTime é um bom contra-exemplo: faz sentido que seja um tipo de valor, em termos de ser uma unidade fundamental como um número ou um caractere, e também faz sentido poder executar cálculos com base em seu valor. Observando as coisas de outra direção, os tipos de transferência de dados geralmente devem ser de referência - a decisão deve se basear no valor desejado ou na semântica do tipo de referência, não na simplicidade do tipo. Outras pessoas acreditam que os tipos de valor são "mais leves" do que os tipos de referência em termos de desempenho. A verdade é que, em alguns casos, os tipos de valor são mais eficientes - eles não exigem coleta de lixo, a menos que estejam em caixas, não possuem a sobrecarga de identificação de tipo e não exigem desreferenciamento, por exemplo. Mas, de outras formas, os tipos de referência são mais eficientes - passagem de parâmetros, atribuição de valores a variáveis, retorno de valores e operações semelhantes exigem que apenas 4 ou 8 bytes sejam novamente copiados (dependendo de você estar executando o CLR de 32 ou 64 bits ) em vez de copiar todos os dados. Imagine se ArrayList fosse de alguma forma um tipo de valor "puro" e passar uma expressão ArrayList para um método envolvido na cópia de todos os seus dados! Em quase todos os casos, o desempenho não é realmente determinado por esse tipo de decisão. Os gargalos quase nunca estão onde você pensa que estarão; antes de tomar uma decisão de design com base no desempenho, você deve medir as diferentes opções. Vale a pena notar que a combinação das duas crenças também não funciona. Não importa quantos métodos um tipo possui (seja uma classe ou uma estrutura) - a memória usada por instância não é afetada. (Existe um custo em termos de memória ocupada pelo próprio código, mas é incorrida uma vez e não para cada instância.)

MITO 2: TIPOS DE REFERÊNCIA VIVEM NO PÃO; TIPOS DE VALOR AO VIVO NA PILHA

Este geralmente é causado pela preguiça por parte da pessoa que o repete. A primeira parte está correta - uma instância de um tipo de referência é sempre criada no heap. É a segunda parte que causa problemas. Como já observei, o valor de uma variável vive onde quer que seja declarado; portanto, se você tiver uma classe com uma variável de instância do tipo int, o valor dessa variável para qualquer objeto sempre estará sempre onde estão os demais dados do objeto. na pilha. Somente variáveis ​​locais (variáveis ​​declaradas nos métodos) e parâmetros do método vivem na pilha. No C # 2 e versões posteriores, mesmo algumas variáveis ​​locais não ficam realmente na pilha, como você verá quando analisamos métodos anônimos no capítulo 5. ESTES CONCEITOS SÃO RELEVANTES AGORA? É discutível que, se você estiver escrevendo um código gerenciado, deixe o tempo de execução se preocupar com a melhor maneira de usar a memória. De fato, a especificação da linguagem não garante o que mora onde; um tempo de execução futuro poderá criar alguns objetos na pilha, se souber que pode se safar, ou o compilador C # poderá gerar código que dificilmente usa a pilha. O próximo mito é geralmente apenas uma questão terminológica.

MITO 3: OS OBJETOS SÃO PASSADOS POR REFERÊNCIA EM C # POR PADRÃO

Este é provavelmente o mito mais amplamente propagado. Mais uma vez, as pessoas que fazem essa afirmação com frequência (embora nem sempre) sabem como o C # realmente se comporta, mas não sabem o que "passar por referência" realmente significa. Infelizmente, isso é confuso para as pessoas que sabem o que isso significa. A definição formal de passagem por referência é relativamente complicada, envolvendo valores de l e terminologia semelhante da ciência da computação, mas o importante é que, se você passar uma variável por referência, o método que você está chamando pode alterar o valor da variável do chamador alterando seu valor de parâmetro. Agora, lembre-se de que o valor de uma variável do tipo de referência é a referência, não o próprio objeto. Você pode alterar o conteúdo do objeto ao qual um parâmetro se refere sem que o próprio parâmetro seja passado por referência. Por exemplo,

void AppendHello(StringBuilder builder)
{
    builder.Append("hello");
}

Quando esse método é chamado, o valor do parâmetro (uma referência a um StringBuilder) é passado por valor. Se você alterasse o valor da variável do construtor dentro do método - por exemplo, com a instrução construtor = null; - essa alteração não seria vista pelo chamador, ao contrário do mito. É interessante notar que não apenas o bit "por referência" do mito é impreciso, mas também o bit "objetos são passados". Os próprios objetos nunca são passados, nem por referência nem por valor. Quando um tipo de referência está envolvido, a variável é passada por referência ou o valor do argumento (a referência) é passado por valor. Além de qualquer outra coisa, isso responde à pergunta do que acontece quando nulo é usado como argumento por valor - se objetos estivessem sendo distribuídos, isso causaria problemas, pois não haveria um objeto para passar! Em vez de, a referência nula é passada por valor da mesma maneira que qualquer outra referência seria. Se essa rápida explicação o deixou perplexo, convém examinar meu artigo "Parâmetro passando em C #" (http://mng.bz/otVt ), que entra em muito mais detalhes. Esses mitos não são os únicos por aí. O boxe e o unboxing são uma boa parte de mal-entendidos, que tentarei esclarecer a seguir.

Referência: C # in Depth 3rd Edition por Jon Skeet


1
Muito bom, supondo que você esteja correto. Também é muito bom adicionar uma referência.
NoChance 17/01

5

Eu acho que uma boa primeira aproximação é "nunca".

Eu acho que uma boa segunda aproximação é "nunca".

Se você está desesperado por perf, considere-os, mas sempre meça.


24
Eu discordaria dessa resposta. As estruturas têm um uso legítimo em muitos cenários. Aqui está um exemplo - agrupar dados cruza processos de maneira atômica.
Franci Penov

25
Você deve editar sua postagem e elaborar seus pontos - você deu sua opinião, mas deve justificar o motivo pelo qual você expressa essa opinião.
214 Erik Forbes

4
Eu acho que eles precisam de um equivalente ao cartão Totin 'Chip ( en.wikipedia.org/wiki/Totin%27_Chip ) para usar estruturas. A sério.
Greg

4
Como uma pessoa de 87,5K publica uma resposta como essa? Ele fez isso quando era criança?
Rohit Vipin Mathews

3
@Rohit - foi há seis anos; os padrões do site eram muito diferentes na época. ainda é uma resposta ruim, você está certo.
Andrew Arnold

5

Eu estava lidando com o Pipe nomeado do Windows Communication Foundation [WCF] e notei que faz sentido usar Structs para garantir que a troca de dados seja do tipo valor em vez do tipo de referência .


1
Esta é a melhor pista de todas, IMHO.
317 Ivan Ivan

4

A estrutura C # é uma alternativa leve para uma classe. Pode fazer quase o mesmo que uma classe, mas é menos "caro" usar uma estrutura do que uma classe. A razão para isso é um pouco técnica, mas, para resumir, novas instâncias de uma classe são colocadas no heap, onde estruturas recém-instanciadas são colocadas na pilha. Além disso, você não está lidando com referências a estruturas, como em classes, mas está trabalhando diretamente com a instância struct. Isso também significa que quando você passa uma estrutura para uma função, é por valor, e não como referência. Há mais sobre isso no capítulo sobre parâmetros de função.

Portanto, você deve usar estruturas quando desejar representar estruturas de dados mais simples e, principalmente, se souber que irá instanciar muitas delas. Existem muitos exemplos na estrutura .NET, onde a Microsoft usou estruturas em vez de classes, por exemplo, a estrutura Point, Rectangle e Color.



3

Os tipos de estrutura ou valor podem ser usados ​​nos seguintes cenários -

  1. Se você deseja impedir que o objeto seja coletado por coleta de lixo.
  2. Se for um tipo simples e nenhuma função membro modificar seus campos de instância
  3. Se não houver necessidade de derivar de outros tipos ou de derivar para outros tipos.

Você pode saber mais sobre os tipos e valores aqui neste link


3

Resumidamente, use struct se:

1- as propriedades / campos do seu objeto não precisam ser alterados. Quero dizer, você só quer dar a eles um valor inicial e depois lê-los.

2 - as propriedades e os campos do seu objeto são do tipo valor e não são tão grandes.

Nesse caso, você pode aproveitar as estruturas para obter um melhor desempenho e alocação de memória otimizada, pois elas usam apenas pilhas, em vez de pilhas e pilhas (nas classes)


2

Eu raramente uso uma estrutura para as coisas. Mas sou só eu. Depende se eu preciso que o objeto seja anulável ou não.

Conforme declarado em outras respostas, eu uso classes para objetos do mundo real. Eu também tenho a mentalidade de estruturas são usadas para armazenar pequenas quantidades de dados.


-11

Estruturas são, em muitos aspectos, como classes / objetos. A estrutura pode conter funções, membros e pode ser herdada. Mas as estruturas são em C # usadas apenas para retenção de dados . As estruturas precisam de menos RAM do que as classes e são mais fáceis para o coletor de lixo coletar . Mas quando você usa funções em sua estrutura, o compilador realmente leva essa estrutura de maneira muito semelhante à classe / objeto; portanto, se você deseja algo com funções, use classe / objeto .


2
Estruturas não pode ser herdada, ver msdn.microsoft.com/en-us/library/0taef578.aspx
HimBromBeere
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.