Tipo de valor de boxeo para enviarlo a un método y obtener el resultado.

Tipo de valor de boxeo para enviarlo a un método y obtener el resultado.

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).

Solución 1:

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();
    }
}