PEtter
PEtter

Reputation: 836

Boxing behaves differently in C# and VB

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?

Upvotes: 8

Views: 254

Answers (2)

tolanj
tolanj

Reputation: 3724

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

Upvotes: 2

D Stanley
D Stanley

Reputation: 152566

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.

Upvotes: 7

Related Questions