Question

I have the following code for creating a dynamic method to call the Set method of a property in my VB.net app, using .NET 3.5 (can't switch to the Lambda expression style). Using an the example posted here, I added to the function because it did not work with Int64 properties, mainly that if it was called with a regular int, unbox operation would cause an Invalid Cast error. So I added the code to handle that, but now I have a new problem. Everything works fine when running under 64 bit, but as soon as I change to a 32 bit process, calling the delegate for an Int64 property results in an AccessViolationException, attempted to read or write protected memory. Other types like String, seem to work fine. See the Code below, What am I doing wrong?

Public Shared Sub SetFieldData(Instance As Object, PropInfo As PropertyInfo, value As Object)

        Dim Compiled As Action(Of Object, Object) = Nothing
        If Not _PropSetterCache.TryGetValue(PropInfo, Compiled) Then
            SyncLock _PropSetterCache
                If Not _PropSetterCache.TryGetValue(PropInfo, Compiled) Then

                    'http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/
                    Dim setMethod = PropInfo.GetSetMethod()

                    Dim arguments As Type() = New Type(1) {}
                    arguments(0) = GetType(Object)
                    arguments(1) = arguments(0)

                    Dim setter As New DynamicMethod([String].Concat("_Set", PropInfo.Name, "_"), Nothing, arguments, PropInfo.DeclaringType)
                    Dim generator As ILGenerator = setter.GetILGenerator()
                    generator.Emit(OpCodes.Ldarg_0)

                    generator.Emit(OpCodes.Castclass, PropInfo.DeclaringType)

                    If PropInfo.PropertyType.IsClass Then
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Castclass, PropInfo.PropertyType)
                    ElseIf PropInfo.PropertyType Is GetType(Boolean) Then
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, PropInfo.PropertyType)
                    ElseIf PropInfo.PropertyType.IsValueType Then
                        'my stuff, blog example doesn't cover the sent value being different type than the property



                        Dim LByte = generator.DefineLabel
                        Dim LInt = generator.DefineLabel
                        Dim LInt16 = generator.DefineLabel
                        Dim LInt32 = generator.DefineLabel
                        Dim LInt64 = generator.DefineLabel
                        Dim LSByte = generator.DefineLabel
                        Dim LUInt16 = generator.DefineLabel
                        Dim LUInt32 = generator.DefineLabel
                        Dim LUInt64 = generator.DefineLabel
                        Dim LDouble = generator.DefineLabel
                        Dim LSingle = generator.DefineLabel
                        Dim LElse = generator.DefineLabel
                        Dim LEnd = generator.DefineLabel


                        'byte
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Byte))
                        generator.Emit(OpCodes.Brtrue, LByte)
                        'int
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Integer))
                        generator.Emit(OpCodes.Brtrue, LInt)
                        'int16
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Int16))
                        generator.Emit(OpCodes.Brtrue, LInt16)
                        'int32
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Int32))
                        generator.Emit(OpCodes.Brtrue, LInt32)
                        'int64
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Int64))
                        generator.Emit(OpCodes.Brtrue, LInt64)
                        'double 
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Double))
                        generator.Emit(OpCodes.Brtrue, LDouble)
                        'short
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Single))
                        generator.Emit(OpCodes.Brtrue, LSingle)
                        'sbyte
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(SByte))
                        generator.Emit(OpCodes.Brtrue, LSByte)
                        'uint16
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(UInt16))
                        generator.Emit(OpCodes.Brtrue, LUInt16)
                        'uint32
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(UInt32))
                        generator.Emit(OpCodes.Brtrue, LUInt32)
                        'uint64
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(UInt64))
                        generator.Emit(OpCodes.Brtrue, LUInt64)
                        'else
                        generator.Emit(OpCodes.Br, LElse)


                        '
                        generator.MarkLabel(LByte)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Byte))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LInt)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Integer))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LInt16)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Int16))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LInt32)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Int32))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LInt64)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Int64))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LDouble)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Double))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LSingle)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Single))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LSByte)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(SByte))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LUInt16)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(UInt16))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LUInt32)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(UInt32))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LUInt64)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(UInt64))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LElse)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, PropInfo.PropertyType)
                        generator.Emit(OpCodes.Br, LEnd)

                        generator.MarkLabel(LEnd)


                    End If

                    generator.Emit(OpCodes.Callvirt, setMethod)
                    generator.Emit(OpCodes.Ret)

                    Compiled = setter.CreateDelegate(GetType(Action(Of Object, Object)))


                    _PropSetterCache.Add(PropInfo, Compiled)

                End If
            End SyncLock
        End If

        Compiled(Instance, value)

    End Sub
Was it helpful?

Solution

If I compile your code for .Net 4.5, I get InvalidProgramException, no matter whether it's compiled as 32 or 64 bit. That's because you can't simply assign a value of one type to a field of a completely different type. You would need to include proper casts, which would make your code even more complicated. I think the fact that your code actually works in some cases is a bug in .Net (one that was fixed in newer versions).

But you can use expressions to do this, even in .Net 3.5. Instead of using Assign, you can use Call on the set method. You can't call that method as a method in VB.NET (or C#), but you can do that in an expression.

But for this to work correctly, you need to cast the value twice: first to unbox it, and then to cast it to the desired type. But because of that, the generated code depends on the type of the value. This means you need to change the key of your cache to something like Tuple(Of PropertyInfo, Type).

The code could look like this:

Private Shared ReadOnly _PropSetterCache As Dictionary(Of Tuple(Of PropertyInfo, Type), Action(Of Object, Object)) _
    = New Dictionary(Of Tuple(Of PropertyInfo, Type), Action(Of Object, Object))

Public Shared Sub SetFieldData(instance As Object, propInfo As PropertyInfo, value As Object)

    Dim compiled As Action(Of Object, Object) = Nothing
    Dim key = New Tuple(Of PropertyInfo, Type)(propInfo, If(value IsNot Nothing, value.GetType(), GetType(Object)))
    If Not _PropSetterCache.TryGetValue(key, compiled) Then
        SyncLock _PropSetterCache
            If Not _PropSetterCache.TryGetValue(key, compiled) Then
                Dim setMethod = propInfo.GetSetMethod()

                Dim instanceParameter = Expression.Parameter(GetType(Object), "instance")
                Dim castedInstance = Expression.Convert(instanceParameter, propInfo.DeclaringType)

                Dim valueParameter = Expression.Parameter(GetType(Object), "value")
                Dim valueCastedOnce = Expression.Convert(valueParameter, If(value IsNot Nothing, value.GetType(), GetType(Object)))
                Dim valueCastedTwice = Expression.Convert(valueCastedOnce, propInfo.PropertyType)

                Dim callExpression = Expression.Call(castedInstance, setMethod, valueCastedTwice)

                Dim lambda = Expression.Lambda(Of Action(Of Object, Object))(callExpression, instanceParameter, valueParameter)

                compiled = lambda.Compile()

                _PropSetterCache.Add(key, compiled)

            End If
        End SyncLock
    End If

    compiled(instance, value)

End Sub

.Net 3.5 doesn't have Tuple, but you can create one yourself:

NotInheritable Class Tuple(Of T1, T2)
    Implements IEquatable(Of Tuple(Of T1, T2))
    Private ReadOnly _value1 As T1

    Public ReadOnly Property Value1 As T1
        Get
            Return _value1
        End Get
    End Property

    Private ReadOnly _value2 As T2

    Public ReadOnly Property Value2 As T2
        Get
            Return _value2
        End Get
    End Property

    Public Sub New(ByVal value1 As T1, ByVal value2 As T2)
        _value1 = value1
        _value2 = value2
    End Sub

    '' following code generated by R#
    Public Overloads Function Equals(ByVal other As Tuple(Of T1, T2)) As Boolean Implements IEquatable(Of Tuple(Of T1, T2)).Equals
        If ReferenceEquals(Nothing, other) Then Return False
        If ReferenceEquals(Me, other) Then Return True
        Return EqualityComparer(Of T1).[Default].Equals(_value1, other._value1) AndAlso EqualityComparer(Of T2).[Default].Equals(_value2, other._value2)
    End Function

    Public Overloads Overrides Function Equals(ByVal obj As Object) As Boolean
        If ReferenceEquals(Nothing, obj) Then Return False
        If ReferenceEquals(Me, obj) Then Return True
        Return TypeOf obj Is Tuple(Of T1, T2) AndAlso Equals(DirectCast(obj, Tuple(Of T1, T2)))
    End Function

    Public Overrides Function GetHashCode() As Integer
        Dim hashCode = EqualityComparer(Of T1).[Default].GetHashCode(_value1)
        hashCode = CInt((hashCode * 397L) Mod Integer.MaxValue) Xor EqualityComparer(Of T2).[Default].GetHashCode(_value2)
        Return hashCode
    End Function
End Class
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top