Type of bonus value to send it to the method and get the result

I am wondering how C # behaves when passing values ​​/ reference types to a method. I would like to pass the type of the nested value to the "AddThree" method. The idea is to get the result of operations performed in "AddThree" in the caller (Main) function.

static void Main(string[] args) { int age = 3; object myBox = age; AddThree(myBox); // here myBox = 3 but I was expecting to be = 6 } private static void AddThree(object age2) { age2 = (int)age2 + 3; } 

I tried this with pure reference types like string and get the same result. If I β€œwrap” my int inside the class, and I use the β€œwrap” class in this example, it works as I expect, i.e. I get myBox = 6. If I modify the signature of the AddThree method to pass the ref parameter, it also returns 6. But, I don’t want to change the signature or create a wrapper class, I just want to specify a value.

+5
source share
3 answers

I don’t want to change the signature or create a wrapper class, I just want to put the value.

Then it will be a problem. Your method skips the int box, then unpacks it and adds 3 to local age2 , which causes another boxing operation, and then discards the value. De facto you evaluate age2 two different objects on the heap, they do not point to the same object. Without changing the method signature, this will not be possible.

If you look at the generated IL for AddThree , you will see this clearly:

 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 

Cancel the value, add 3, and then enter the value again, but you will never return it.

To visualize this case, try returning a recently entered value from a method (test only) and use object.ReferenceEquals to compare both of them:

 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; } 
+2
source

Assigning a new value to a method parameter that has not passed through ref will not change the original link.

In your case, age2 (method parameter) is a copy of myBox . They both refer to the same object (in the age box), but the purpose of age2 does not change myBox . It just makes the age2 link a reference to another object.

Actually, this has nothing to do with boxing. It's just how parameters are passed to methods.

+1
source

Boxed links should be unchanged. For example, this will not compile:

 ((Point)p).X += 3; // CS0445: Cannot modify the result of an unboxing conversion. 

As others have said, this line causes a couple of packing and unpacking operations, which ends with a new link:

 age2 = (int)age2 + 3; 

Thus, even though the packed int is actually a link, the line above also changes the link to the object, so the caller will still see the same content if the object itself is not passed by reference.

However, there are several ways to dereference and change boxed values ​​without changing the link (however, none of them are recommended).

Solution 1:

The easiest way is through reflection. This seems a little silly because the Int32.m_value field is the int value itself, but it allows you to access the int directly.

 private static void AddThree(object age2) { FieldInfo intValue = typeof(int).GetTypeInfo().GetDeclaredField("m_value"); intValue.SetValue(age2, (int)age2 + 3); } 

Solution 2:

This is a much more TypedReference hack and involves using the mostly undocumented TypedReference and the __makeref() operator, but more or less this is what happens in the background in the first solution:

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

Source: https://habr.com/ru/post/1236499/


All Articles