Entonces eso será un problema. Su método pasa un int
en caja , luego lo desempaqueta y agrega 3 al age2
local , lo que provoca otra operación de boxeo y luego tira el valor. De hecho, estás asignando age2
a dos objetos diferentes en el montón, no apuntan al mismo objeto. Sin modificar la firma del método, esto no será posible.
Si observa el IL generado para AddThree
, verás esto claramente:
AddThree:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: unbox.any System.Int32 // unbox age2
IL_0007: ldc.i4.3 // load 3
IL_0008: add // add the two together
IL_0009: box System.Int32 // box the result
IL_000E: starg.s 00
IL_0010: ret
Desempaqueta el valor, agrega 3 y luego vuelve a colocar el valor en la caja, pero nunca lo devuelve.
Para visualizar más este caso, intente devolver el valor recién encuadrado del método (solo por el bien de la prueba) y use object.ReferenceEquals
para comparar ambos:
static void Main(string[] args)
{
int age = 3;
object myBox = age;
var otherBox = AddThree(myBox);
Console.WriteLine(object.ReferenceEquals(otherBox, myBox)); // False
}
private static object AddThree(object age2)
{
age2 = (int)age2 + 3;
return age2;
}
Referencias en recuadro destinadas a ser inmutables. Por ejemplo, esto no compilará:
((Point)p).X += 3; // CS0445: Cannot modify the result of an unboxing conversion.
Como dijeron los demás, esta línea provoca un par de operaciones de encajonado y desencajonado, que terminan en una nueva referencia:
age2 = (int)age2 + 3;
Entonces, aunque un int encuadrado es en realidad una referencia, la línea anterior también modifica la referencia del objeto, por lo que la persona que llama seguirá viendo el mismo contenido a menos que el objeto en sí se pase por referencia.
Sin embargo, hay algunas formas de desreferenciar y cambiar un valor encuadrado sin cambiar la referencia (sin embargo, no se recomienda ninguna).
La forma más sencilla es a través de la reflexión. Esto parece un poco tonto porque el Int32.m_value
El campo es el valor int en sí mismo, pero esto le permite acceder al int directamente.
private static void AddThree(object age2)
{
FieldInfo intValue = typeof(int).GetTypeInfo().GetDeclaredField("m_value");
intValue.SetValue(age2, (int)age2 + 3);
}
Solución 2:
Este es un truco mucho más grande e implica el uso del TypedReference
principalmente indocumentado y el __makeref()
operador pero más o menos esto es lo que sucede en segundo plano en la primera solución:
private static unsafe void AddThree(object age2)
{
// pinning is required to prevent GC reallocating the object during the pointer operations
var objectPinned = GCHandle.Alloc(age2, GCHandleType.Pinned);
try
{
// The __makeref() operator returns a TypedReference.
// It is basically a pair of pointers for the reference value and type.
TypedReference objRef = __makeref(age2);
// Dereference it to access the boxed value like this: objRef.Value->object->boxed content
// For more details see the memory layout of objects: https://blogs.msdn.microsoft.com/seteplia/2017/05/26/managed-object-internals-part-1-layout/
int* rawContent = (int*)*(IntPtr*)*(IntPtr*)&objRef;
// rawContent now points to the type handle (just another pointer to the method table).
// The actual instance fields start after these 4 or 8 bytes depending on the pointer size:
int* boxedInt = rawContent + (IntPtr.Size == 4 ? 1 : 2);
*boxedInt += 3;
}
finally
{
objectPinned.Free();
}
}