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