Pregunta

La forma .NET 1.0 de crear una colección de números enteros (por ejemplo) era:

ArrayList list = new ArrayList();
list.Add(i);          /* boxing   */
int j = (int)list[0]; /* unboxing */

La desventaja de usar esto es la falta de seguridad y rendimiento del tipo debido al boxeo y unboxing.

La forma .NET 2.0 es utilizar genéricos:

List<int> list = new List<int>();
list.Add(i);
int j = list[0];

El precio del boxeo (según tengo entendido) es la necesidad de crear un objeto en el montón, copiar el entero asignado de la pila al nuevo objeto y viceversa para desempacar.

¿Cómo supera esto el uso de genéricos?¿El entero asignado a la pila permanece en la pila y es apuntado desde el montón (supongo que este no es el caso debido a lo que sucederá cuando salga del alcance)?Parece que todavía es necesario copiarlo en otro lugar de la pila.

¿Qué pasa en realidad?

¿Fue útil?

Solución

Cuando se trata de colecciones, los genéricos hacen posible evitar el boxeo / unboxing mediante la utilización de matrices reales T[] internamente. List<T> por ejemplo utiliza una matriz T[] para almacenar su contenido.

La array , por supuesto, es un tipo de referencia y por lo tanto es (en la versión actual de la CLR, yada yada) almacenados en el montón. Pero ya que es un T[] y no un object[], los elementos de la matriz se pueden almacenar "directamente": es decir, que todavía están en el montón, pero están en el montón en la matriz en lugar de siendo en caja y que tiene la matriz contiene referencias a las cajas.

Así que para una List<int>, por ejemplo, lo que tendría en la matriz haría "mirada" de esta manera:

[ 1 2 3 ]

Compare esto con un ArrayList, que utiliza un object[] y por lo tanto "look" algo como esto:

[ *a *b *c ]

... donde *a, etc., son referencias a objetos (números enteros en caja):

*a -> 1
*b -> 2
*c -> 3

Perdone esas ilustraciones de crudo; es de esperar que sabes lo que quiero decir.

Otros consejos

Su confusión es el resultado de la incomprensión cuál es la relación entre la pila, la pila, y las variables. Esta es la forma correcta de pensar en ello.

  • Una variable es una ubicación de almacenamiento que tiene un tipo.
  • El tiempo de vida de una variable o bien puede ser corto o largo. Por "corto" queremos decir "hasta que la corriente devuelve la función o tiros" y "largo" queremos decir "posiblemente más que eso".
  • Si el tipo de una variable es un tipo de referencia a continuación, el contenido de la variable es una referencia a una ubicación de almacenamiento de larga duración. Si el tipo de una variable es un tipo de valor entonces el contenido de la variable es un valor.

Como un detalle de implementación, un lugar de almacenamiento que está garantizado para ser de corta duración, puede ser asignado en la pila. Un lugar de almacenamiento que puede ser vivido largo se asigna en el montón. Tenga en cuenta que esto no dice nada acerca de "los tipos de valor siempre se asignan en la pila." Los tipos de valor son no siempre asignado en la pila:

int[] x = new int[10];
x[1] = 123;

x[1] es un lugar de almacenamiento. Es vivió largo; podría vivir más tiempo que este método. Por lo tanto, debe estar en el montón. El hecho de que contiene un int es irrelevante.

correctamente decir por qué una caja int es caro:

  

El precio de boxeo es la necesidad de crear un objeto en el montón, copia la pila asignado número entero al nuevo objeto y viceversa para unboxing.

Donde ir mal es decir "la pila asignado entero". No importa donde se le asignó el número entero. Lo que importa es que su almacenamiento contiene el entero , en lugar de contener una referencia a una ubicación montón . El precio es la necesidad de crear el objeto y hacer la copia; ese es el único costo que es relevante.

¿Por qué no es costosa una variable genérica? Si usted tiene una variable de tipo T, y T está construido para ser int, entonces usted tiene una variable de tipo int, y punto. Una variable de tipo int es una ubicación de almacenamiento, y contiene un int. Ya sea que la ubicación de almacenamiento está en la pila o el montón es completamente irrelevante . Lo que es relevante es que la ubicación de almacenamiento contiene un int , en lugar de contener una referencia a algo en el montón . Dado que la ubicación de almacenamiento contiene un entero, que no tiene que asumir los costes de boxeo y unboxing:. Asignar un nuevo almacenamiento en el montón y la copia de la int para el nuevo almacenamiento

Es que claro ahora?

Los genéricos permite matriz interna de la lista para ser escrito int[] lugar de manera efectiva object[], lo que requeriría el boxeo.

Esto es lo que sucede sin genéricos:

  1. Usted llama Add(1).
  2. El 1 número entero está encajonado en un objeto, lo que requiere un nuevo objeto a ser construido en el montón.
  3. Este objeto se pasa a ArrayList.Add().
  4. El objeto en caja se embute en un object[].

Hay tres niveles de indirección aquí:. ArrayList -> object[] -> object -> int

Con los genéricos:

  1. Usted llama Add(1).
  2. El int 1 se pasa a List<int>.Add().
  3. El int se embute en un int[].

Así que sólo hay dos niveles de indirección:. List<int> -> int[] -> int

Algunas otras diferencias:

  • El método no genérico requerirá una suma de 8 o 12 bytes (un puntero, una int) para almacenar el valor, 4/8 en una asignación y 4 en el otro. Y esto probablemente será más debido a la alineación y el relleno. El método genérico requerirá sólo 4 bytes de espacio en la matriz.
  • El método no genérico requiere la asignación de un int en caja; el método genérico no lo hace. Esto es más rápido y reduce GC pérdida de clientes.
  • El método no genérico requiere moldes a valores de extracto. Esto no es typesafe y es un poco más lento.

Un ArrayList sólo maneja el tipo object modo de utilizar esta clase requiere la fundición hasta y desde object. En el caso de los tipos de valor, este casting implica el boxeo y unboxing.

Cuando se utiliza una lista genérica de las salidas del compilador de código especializados para ese tipo de valor para que los valores reales se almacenan en la lista en lugar de una referencia a los objetos que contienen los valores. Por lo tanto no se requiere el boxeo.

  

El precio de boxeo (a mi entender) es la necesidad de crear un objeto en el montón, copie la pila asignado número entero al nuevo objeto y viceversa para el unboxing.

creo que usted está asumiendo que los tipos de valor siempre se crean instancias en la pila. Este no es el caso - que pueden ser creadas en el montón, en la pila o en los registros. Para obtener más información sobre esto, por favor véase el artículo de Eric Lippert: La verdad sobre el Valor Tipos .

En .NET 1, cuando el Add método se llama:

  1. Se asigna espacio en el montón;se hace una nueva referencia
  2. El contenido del i la variable se copia en la referencia
  3. Una copia de la referencia se coloca al final de la lista.

En .NET 2:

  1. Una copia de la variable. i se pasa a la Add método
  2. Una copia de esa copia se coloca al final de la lista.

Sí el i La variable todavía se copia (después de todo, es un tipo de valor y los tipos de valor siempre se copian, incluso si son solo parámetros de método).Pero no se realiza ninguna copia redundante en el montón.

¿Por qué estás pensando en términos de valores \ WHERE los objetos se almacenan? En C # tipos de valores pueden ser almacenados en la pila, así como montón dependiendo de lo que los elige CLR.

Cuando los genéricos hacen la diferencia se WHAT se almacena en la colección. En caso de ArrayList la colección contiene referencias a objetos encajonados en tanto que la List<int> contiene mismos valores int.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top