Por lo general, tratar una estructura S como una interfaz activará el I de la estructura, lo que puede tener un impacto en el rendimiento si se hace con frecuencia. Sin embargo, si escribo un método genérico tomando un parámetro de tipo T : I y lo llamo con una S , ¿el compilador omitirá el boxeo, ya que conoce el tipo S y no tiene que usar la interfaz?
Este código muestra mi punto:
interface I{ void foo(); } struct S : I { public void foo() { /* do something */ } } class Y { void doFoo(I i){ i.foo(); } void doFooGeneric<T>(T t) where T : I { t.foo(); // <--- Will an S be boxed here?? } public static void Main(string[] args){ S x; doFoo(x); // x is boxed doFooGeneric(x); // x is not boxed, at least not here, right? } } El método doFoo llama a foo() en un objeto de tipo I , por lo que una vez que lo llamemos con una S , esa S quedará encuadrada. El método doFooGeneric hace lo mismo. Sin embargo, una vez que lo llamamos con una S , es posible que no se requiera autoboxing, ya que el tiempo de ejecución sabe cómo llamar a foo() en una S ¿Pero se hará esto? ¿O el tiempo de ejecución encuadrará ciegamente S a I para llamar al método de interfaz?
void doFooGeneric<T>(T t) where T : I { t.foo(); // <--- Will an S be boxed here?? }¡Se evitará el boxeo allí!
La estructura tipo S está sellada. Para las versiones de tipo de valor del parámetro de tipo T para su método doFooGeneric anterior, el compilador de C# brinda un código que llama al miembro de estructura relevante directamente, sin recuadro.
que es genial
Consulte la respuesta de Sameer para obtener algunos detalles técnicos.
Bien, se me ocurrió un ejemplo de esto. Estaré interesado en mejores ejemplos si alguien tiene algunos:
using System; using System.Collections.Generic; namespace AvoidBoxing { static class Program { static void Main() { var myStruct = new List<int> { 10, 20, 30, }.GetEnumerator(); myStruct.MoveNext(); // moves to '10' in list // // UNCOMMENT ONLY *ONE* OF THESE CALLS: // //UseMyStruct(ref myStruct); //UseMyStructAndBox(ref myStruct); Console.WriteLine("After call, current is now: " + myStruct.Current); // 10 or 20? } static void UseMyStruct<T>(ref T myStruct) where T : IEnumerator<int> { myStruct.MoveNext(); } static void UseMyStructAndBox<T>(ref T myStruct) { ((IEnumerator<int>)myStruct).MoveNext(); } } } Aquí, el tipo de myStruct es un tipo de valor mutable que contiene una referencia a List<> , y también contiene un "contador" que recuerda qué índice en List<> hemos alcanzado hasta ahora.
¡Tuve que usar ref , de lo contrario, el tipo de valor se copiaría por valor cuando se pasara a cualquiera de los métodos!
Cuando elimino el comentario de la llamada a UseMyStruct (solo), este método mueve el "contador" dentro de nuestro tipo de valor una posición por delante. Si hiciera eso en una copia en caja del tipo de valor, no lo veríamos en la instancia original de la estructura.
Para ver cuál es la diferencia con el boxeo, intente llamar a UseMyStructAndBox en su lugar (comente UseMyStruct nuevamente). Crea un cuadro en el molde y MoveNext ocurre en una copia. ¡Así que la salida es diferente!
Para aquellos que no están contentos con (o confundidos por) el ref , simplemente escriba Current desde dentro del método en su lugar. Entonces podemos deshacernos de ref . Ejemplo:
static void F<T>(T t) where T : IEnumerator<int> { t.MoveNext(); // OK, not boxed Console.WriteLine(t.Current); } static void G<T>(T t) where T : IEnumerator<int> { ((IEnumerator<int>)t).MoveNext(); // We said "Box!", it will box; 'Move' happens to a copy Console.WriteLine(t.Current); }Se evitará el boxeo ya que los códigos de operación restringidos entran en juego en el segundo caso.