La fuente a la que hace referencia el OP tiene cierta credibilidad ... pero ¿qué pasa con Microsoft? ¿Cuál es la postura sobre el uso de la estructura? Busqué algo de aprendizaje adicional de Microsoft , y esto es lo que encontré:
Considere definir una estructura en lugar de una clase si las instancias del tipo son pequeñas y comúnmente de corta duración o están incrustadas comúnmente en otros objetos.
No defina una estructura a menos que el tipo tenga todas las características siguientes:
- Lógicamente representa un valor único, similar a los tipos primitivos (entero, doble, etc.).
- Tiene un tamaño de instancia menor de 16 bytes.
- Es inmutable.
- No tendrá que ser encuadrado con frecuencia.
Microsoft viola constantemente esas reglas
Bien, # 2 y # 3 de todos modos. Nuestro querido diccionario tiene 2 estructuras 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
}
* Fuente de referencia
La fuente 'JonnyCantCode.com' obtuvo 3 de 4, bastante perdonable ya que el # 4 probablemente no sería un problema. Si se encuentra boxeando una estructura, reconsidere su arquitectura.
Veamos por qué Microsoft usaría estas estructuras:
- Cada estructura
Entry
y Enumerator
representa valores únicos.
- Velocidad
Entry
nunca se pasa como parámetro fuera de la clase Diccionario. La investigación adicional muestra que para satisfacer la implementación de IEnumerable, Dictionary utiliza la Enumerator
estructura que copia cada vez que se solicita un enumerador ... tiene sentido.
- Interno a la clase Diccionario.
Enumerator
es público porque Dictionary es enumerable y debe tener igual accesibilidad a la implementación de la interfaz IEnumerator, por ejemplo, IEnumerator getter.
Actualización : además, tenga en cuenta que cuando una estructura implementa una interfaz, como lo hace Enumerator, y se convierte a ese tipo implementado, la estructura se convierte en un tipo de referencia y se mueve al montón. Interna a la clase Diccionario, Enumerator sigue siendo un tipo de valor. Sin embargo, tan pronto como un método llama GetEnumerator()
, IEnumerator
se devuelve un tipo de referencia .
Lo que no vemos aquí es ningún intento o prueba de requisito para mantener las estructuras inmutables o mantener un tamaño de instancia de solo 16 bytes o menos:
- No se declara nada en las estructuras anteriores
readonly
, no es inmutable
- El tamaño de estas estructuras podría superar los 16 bytes.
Entry
tiene una vida indeterminado (de Add()
, a Remove()
, Clear()
o la recolección de basura);
Y ... 4. Ambas estructuras almacenan TKey y TValue, que todos sabemos que son bastante capaces de ser tipos de referencia (información adicional)
A pesar de las claves hash, los diccionarios son rápidos en parte porque instaurar una estructura es más rápido que un tipo de referencia. Aquí, tengo un Dictionary<int, int>
que almacena 300,000 enteros aleatorios con claves incrementadas secuencialmente.
Capacidad: 312874 Tamaño de
memoria: 2660827 bytes
Tamaño completado : 5 ms Tiempo
total de llenado: 889 ms
Capacidad : número de elementos disponibles antes de que se deba cambiar el tamaño de la matriz interna.
MemSize : determinado serializando el diccionario en un MemoryStream y obteniendo una longitud de byte (lo suficientemente precisa para nuestros propósitos).
Redimensionado completado : el tiempo que lleva cambiar el tamaño de la matriz interna de 150862 elementos a 312874 elementos. Cuando te das cuenta de que cada elemento se copia secuencialmente Array.CopyTo()
, eso no es nada malo.
Tiempo total para llenar : admitidamente sesgado debido al registro y un OnResize
evento que agregué a la fuente; Sin embargo, sigue siendo impresionante llenar 300k enteros mientras se redimensiona 15 veces durante la operación. Solo por curiosidad, ¿cuál sería el tiempo total para llenar si ya conociera la capacidad? 13ms
Entonces, ¿y si Entry
fuera una clase? ¿Estos tiempos o métricas realmente diferirían tanto?
Capacidad: 312874 Tamaño de
memoria: 2660827 bytes
Tamaño completado: 26ms
Tiempo total para llenar: 964ms
Obviamente, la gran diferencia está en cambiar el tamaño. ¿Hay alguna diferencia si el Diccionario se inicializa con la Capacidad? No es suficiente para preocuparse por ... 12ms .
Lo que sucede es que, como Entry
es una estructura, no requiere inicialización como un tipo de referencia. Esto es tanto la belleza como la ruina del tipo de valor. Para usar Entry
como tipo de referencia, tuve que insertar el siguiente 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( );
}
/* *********************************************** */
La razón por la que tuve que inicializar cada elemento de matriz Entry
como tipo de referencia se puede encontrar en MSDN: Diseño de estructura . En breve:
No proporcione un constructor predeterminado para una estructura.
Si una estructura define un constructor predeterminado, cuando se crean matrices de la estructura, Common Language Runtime ejecuta automáticamente el constructor predeterminado en cada elemento de la matriz.
Algunos compiladores, como el compilador de C #, no permiten que las estructuras tengan constructores predeterminados.
En realidad es bastante simple y tomaremos prestado de las Tres leyes de la robótica de Asimov :
- La estructura debe ser segura de usar
- La estructura debe realizar su función de manera eficiente, a menos que esto viole la regla # 1
- La estructura debe permanecer intacta durante su uso a menos que se requiera su destrucción para cumplir con la regla # 1
... qué nos quitamos de esto : en resumen, ser responsables con el uso de los tipos de valor. Son rápidos y eficientes, pero tienen la capacidad de causar muchos comportamientos inesperados si no se mantienen adecuadamente (es decir, copias no intencionales).
System.Drawing.Rectangle
viola las tres reglas.