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:
- Representa logicamente um valor único, semelhante aos tipos primitivos (número inteiro, duplo e assim por diante).
- Possui um tamanho de instância menor que 16 bytes.
- É imutável.
- 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:
- Cada estrutura
Entry
e Enumerator
representa valores únicos.
- Rapidez
Entry
nunca é 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 Enumerator
estrutura que ele copia toda vez que um enumerador é solicitado ... faz sentido.
- 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:
- Nada nas estruturas acima é declarado
readonly
- não é imutável
- O tamanho dessas estruturas pode ter mais de 16 bytes
Entry
tem 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 OnResize
evento 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 Entry
fosse 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 Entry
ser 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 Entry
como 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 Entry
como 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 :
- A estrutura deve ser segura para usar
- A estrutura deve executar sua função com eficiência, a menos que isso viole a regra 1
- 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).
System.Drawing.Rectangle
viola todas essas três regras.