boxing and unboxing c# example
Boxeo y Unboxing en String.Format(…)… ¿se racionaliza lo siguiente? (6)
Estaba leyendo un poco acerca del boxeo / unboxing, y resulta que si haces un String.Format()
normal en el que tienes un tipo de valor en tu lista de argumentos de object[]
, causará una operación de boxeo. Por ejemplo, si está intentando imprimir el valor de un entero y hacer una string.Format("My value is {0}",myVal)
, pegará su myVal
int
en un cuadro y ejecutará la función ToString
en él. .
Navegando alrededor, encontré este artículo .
Parece que puede evitar la penalización de boxeo simplemente haciendo el .ToString
en el tipo de valor antes de transferirlo a la cadena. Función de string.Format("My value is {0}",myVal.ToString())
: string.Format("My value is {0}",myVal.ToString())
- ¿Es esto realmente cierto? Me inclino a creer la evidencia del autor.
- Si esto es cierto, ¿por qué el compilador simplemente no hace esto por ti? Tal vez ha cambiado desde 2006? ¿Alguien sabe? (No tengo el tiempo / experiencia para hacer todo el análisis de IL)
¿Por qué no probar cada enfoque cien millones de veces y ver cuánto tiempo lleva?
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
int myVal = 6;
sw.Start();
for (int i = 0; i < 100000000; i++)
{
string string1 = string.Format("My value is {0}", myVal);
}
sw.Stop();
Console.WriteLine("Original method - {0} milliseconds", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int i = 0; i < 100000000; i++)
{
string string2 = string.Format("My value is {0}", myVal.ToString());
}
sw.Stop();
Console.WriteLine("ToStringed method - {0} milliseconds", sw.ElapsedMilliseconds);
Console.ReadLine();
}
En mi máquina, estoy descubriendo que la versión .ToStringed se ejecuta en aproximadamente el 95% del tiempo que tarda la versión original, por lo que algunas pruebas empíricas de un ligero beneficio en el rendimiento.
1: sí, siempre que el tipo de valor ToString()
, como lo hacen todos los tipos incorporados.
2: porque tal comportamiento no está definido en la especificación, y el manejo correcto de un params object[]
(wrt-type-types) es: boxeo
string.Format es como cualquier otro método opaco; El hecho de que va a hacer eso es opaco para el compilador. También sería funcionalmente incorrecto si el patrón incluyera un formato como {0:n2}
(que requiere una transformación específica, no solo ToString()
). Tratar de comprender el patrón es indeseable y poco confiable ya que el patrón puede no ser conocido hasta el tiempo de ejecución.
El compilador no hace esto por usted porque string.Format
toma un params Object[]
. El boxeo ocurre debido a la conversión a Object
.
No creo que el compilador tenga métodos de casos especiales, por lo que no eliminará el boxeo en casos como este.
Sí, en muchos casos es cierto que el compilador no hará boxeo si llama a ToString()
primero. Si usa la implementación de Object
, creo que todavía tendría que encajonar.
En última instancia, la string.Format
análisis de formato de la cadena de formato en sí será mucho más lento que cualquier operación de boxeo, por lo que la sobrecarga es insignificante.
La fácil primero. La razón por la que el compilador no convierte string.Format("{0}", myVal)
en string.Format{"{0}", myVal.ToString())
, es que no hay ninguna razón por la que deba hacerlo. ¿Debería convertir BlahFooBlahBlah(myVal)
en BlahFooBlahBlah(myVal.ToString())
? Tal vez eso tenga el mismo efecto pero para un mejor rendimiento, pero es probable que presente un error. Mal compilador! No hay galletas!
A menos que se pueda razonar algo a partir de principios generales, el compilador debería dejarlo solo.
Ahora, para el bit interesante IMO: ¿Por qué el primero causa el boxeo y el segundo no?
Para el primero, ya que la única firma coincidente es string.Format(string, object)
el entero debe convertirse en un objeto (en caja) para pasar al método, que espera recibir una string y un objeto.
Sin embargo, la otra mitad de esto es ¿por qué myVal.ToString()
no myVal.ToString()
también?
Cuando el compilador llega a este bit de código, tiene el siguiente conocimiento:
- myVal es un int32.
- ToString () está definido por Int32
- Int32 es un tipo de valor y por lo tanto:
- myVal no puede ser una referencia nula * y:
- No es posible que haya una anulación más derivada: Int32.ToString () está efectivamente sellado.
Ahora, generalmente el compilador de C # usa callvirt
para todas las llamadas de método por dos razones. La primera es que a veces quieres que sea una llamada virtual después de todo. La segunda es que (más polémico) decidieron prohibir cualquier llamada de método en una referencia nula, y callvirt
tiene una prueba integrada para eso.
En este caso, sin embargo, ninguno de los que se aplican. No puede haber una clase más derivada que invalide Int32.ToString () y myVal no puede ser nulo. Por lo tanto, puede introducir una call
al método ToString()
que pasa el Int32
sin el boxeo.
Esta combinación (el valor no puede ser nulo, el método no se puede anular en otra parte) solo produce tipos de referencia con mucha menos frecuencia, por lo que el compilador no puede aprovechar tanto de ella (tampoco costaría tanto) , ya que no tendrían que ser encajonados).
Este no es el caso si Int32
hereda un método de implementación. Por ejemplo, myVal.GetType()
encajonaría myVal
ya que no hay una anulación Int32
(no puede haber, no es virtual), por lo que solo se puede acceder a ella tratando a myVal
como un objeto, myVal
.
El hecho de que esto signifique que el compilador de C # usará callvirt
para métodos no virtuales y que a veces call
métodos virtuales, no está exento de ironía.
* Tenga en cuenta que incluso un entero nulo establecido en nulo no es lo mismo que una referencia nula a este respecto.
Sería mejor evitar el boxeo construyendo la cadena con StringBuilder o StringWriter y utilizando las sobrecargas escritas.
La mayoría de las veces, el boxeo debe ser de poca preocupación y no vale la pena ni siquiera ser consciente de ello.
string.Format("My value is {0}", myVal)<br>
myVal is an object<br><br>
string.Format("My value is {0}",myVal.ToString())<br>
myVal.ToString() is a string<br><br>
ToString está sobrecargado y, por lo tanto, el compilador no puede decidir por ti.