8

I'm trying to pass a structure to a sub/void to fill it with data. In C# this works fine by doing

[TestFixture]
public class Boxing
{
    [Test]
    public void BoxingValue()
    {
       var res = (object)new Test();
       SomeVoid(res);         
        Assert.AreEqual(2, ((Test)res).Id);
    }

    public static void SomeVoid(object b)
    {
        var f = b.GetType().GetField("Id");
        f.SetValue(b, 2);
    }

    public struct Test
    {
        public int Id;
    }
}

This code passes the test in C# in vb thoug

<Test> Public Sub StructTest()
  Dim s As Object
    s = CObj(New Test)
    A(s)
    Assert.AreEqual(2, CType(s, Test).Id)
End Sub

Public Sub A(val As Object)
    Dim f = val.GetType().GetField("Id")
    f.SetValue(val, 2)
End Sub

Public Structure Test
    Public Id As Integer
End Structure

Does anyone have a explanation for this..

STRANGE?

PEtter
  • 806
  • 9
  • 22
  • 4
    I don't get it. Post the full code. What is the type of `res`? – Sriram Sakthivel Oct 28 '14 at 16:03
  • Yeah, without the declaration of `res` in C#, it's impossible to determine whether code was converted correctly. You need the two missing lines - `Dim s As Object` and `s = CObj(New Test)`. – Victor Zakharov Oct 28 '14 at 16:04
  • my bad.. should be there now – PEtter Oct 28 '14 at 16:05
  • 2
    Can you also include ` Public Sub StructTest()` in C# code and also make sure all class and variable names are consistent? `void a(b)` and `A(val)`, also `struct DtoStruct` vs `Structure Test`. – Victor Zakharov Oct 28 '14 at 16:06
  • 7
    This code burns my eyes. Mutable structs... converting C# to VB... using a side-effect of boxing... reflection... gah! – Adam Houldsworth Oct 28 '14 at 16:08
  • The code is just for demonstrating the effect of boxing/unboxing constructed for just that in mind. Not productioncode in any way – PEtter Oct 28 '14 at 16:20
  • 1
    @PEtter: even so, real-world code that actually is affected by the difference in behavior is still broken. As for the explanation, I think rather than posting here your best bet is to look at the IL (see ildasm.exe, or a tool like dotPeek or Reflector) and see what each compiler generated. I don't know off the top of my head why VB.NET would treat a boxed struct differently, but the answer is in the IL. – Peter Duniho Oct 28 '14 at 16:23
  • @PEtter Fair enough, but still. – Adam Houldsworth Oct 28 '14 at 16:26
  • tried byref but that didn't help. I will take a look at the IL tomorrow – PEtter Oct 28 '14 at 16:26
  • Do you get the same result if you use DirectCast rather than CType? – dodald Oct 28 '14 at 16:29
  • Yes. Seems the boxing 'disapears' when calling the sub. – PEtter Oct 28 '14 at 16:30
  • I will give this a more extensive research tomorrow, and post an update – PEtter Oct 28 '14 at 16:33
  • Actually I remember this, is the fact that the reflection call f.SetValue(val, 2) doesn't actually change val but a copy of val, I'll see if I can remember the solution! – tolanj Oct 28 '14 at 16:43
  • boxing should be the soloution for that :) – PEtter Oct 28 '14 at 16:44
  • btw I also tried to change the value directly via a direct cast. but didn't help either.. – PEtter Oct 28 '14 at 16:45
  • @AdamHouldsworth can you elaborate on the "side-effect of boxing"? – Millie Smith Oct 28 '14 at 17:28
  • @MillieSmith Yes. In this instance, boxing is creating an object wrapper around one copy of a struct implicitly. This is passed into the method and mutated. Were this struct passed into the method as a struct, the mutation would not have affected the caller's copy. The side-effect is the desired "reference" behaviour by wrapping the struct, which could have been done explicitly with a custom class, or with the `ref` keyword. Might not be a textbook example of side-effect, but it's smelly nonetheless. – Adam Houldsworth Oct 28 '14 at 21:25
  • That said, the question has matured a little into a more hypothetical question, where this code simply demonstrates a difference requiring explanation. – Adam Houldsworth Oct 28 '14 at 21:33

2 Answers2

7

I believe this is a known limitation with the use of SetValue in VB when passing structures (even if the variable itself is declared as an Object). If you look at the contents of val within A before and after the call to SetValue, you'll see that it doesn't change the value of the struct. The explanation I've seen is that VB internally boxes the underlying value again (via a call to GetObjectValue), creating a copy, and changes the copy's value.

One workaround I've seen is to cast the value to a ValueType and the call SetValue on it (you also need to change the parameter to be explicitly ByRef:

Public Sub A(ByRef val As Object)
    Dim f = val.GetType().GetField("Id")
    If val.GetType.IsValueType Then
        Dim vt As ValueType = val
        f.SetValue(vt, 2)
        val = vt
    Else
        f.SetValue(val, 2)
    End If    
End Sub

Of course, this complexity just reinforces the principle that mutable structs should be avoided at all costs.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • No problem. Giving you a +1. In any case, this makes me hate VB even more. You have to make a copy of the struct every time you do reflection on it. – Millie Smith Oct 28 '14 at 17:26
  • @MillieSmith: Just follow the guidelines - structures are supposed to be immutable. You never use SetValue on them in real life. If VB does not let you develop a spherical horse in vacuum, it's not a good reason to hate it. – Victor Zakharov Oct 28 '14 at 17:36
  • I belive you are on to someting here. I also made it work doing this modifications. But that was not the actual question :) I belive the code 'is the same' in c# an vb but i compile to difrent IL and that is what scares me. – PEtter Oct 28 '14 at 17:40
  • Your "question" was asking for an explanation - that is the explanation. VB internally boxes the value _again_ and works with a copy of it. – D Stanley Oct 28 '14 at 17:45
2

See Reflection on structure differs from class - but only in code for an explaination, but the summary is:

f.SetValue(val*, 2)

*{at this point val is being passed by value, ie it is a copy that is being updated}

as for a workaround...

'Struct workaround of course you only want this for structs!:
Public Sub A(ByRef val As Object)

    Dim x As ValueType
    x = CType(val, ValueType)
    Dim f = x.GetType().GetField("Id")
    f.SetValue(x, 2)
    val = x
End Sub

Obviously you'll need to protect yourself to only run this for structs...

Community
  • 1
  • 1
tolanj
  • 3,651
  • 16
  • 30