0

我有以下代码用于创建动态方法来调用我的 VB.net 应用程序中的属性的 Set 方法,使用 .NET 3.5(无法切换到 Lambda 表达式样式)。使用此处发布的示例,我添加了该函数,因为它不适用于Int64属性,主要是如果使用常规调用它int,取消装箱操作会导致 Invalid Cast 错误。所以我添加了代码来处理这个问题,但现在我遇到了一个新问题。在 64 位下运行时一切正常,但是一旦我更改为 32 位进程,调用委托的Int64属性会导致AccessViolationException, 尝试读取或写入受保护的内存。其他类型,如字符串,似乎工作正常。请参阅下面的代码,我做错了什么?

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
4

1 回答 1

2

如果我为 .Net 4.5 编译您的代码,InvalidProgramException无论它是编译为 32 位还是 64 位,我都会得到。这是因为您不能简单地将一种类型的值分配给完全不同类型的字段。您需要包含正确的强制转换,这会使您的代码更加复杂。我认为您的代码在某些情况下确实有效的事实是 .Net 中的一个错误(在较新版本中已修复)。

但是您可以使用表达式来执行此操作,即使在 .Net 3.5 中也是如此。Assign您可以使用Callset 方法而不是 using 。您不能将该方法作为 VB.NET(或 C#)中的方法调用,但您可以在表达式中执行此操作。

但要使其正常工作,您需要将值转换两次:首先将其拆箱,然后将其转换为所需的类型。但正因为如此,生成的代码取决于值的类型。这意味着您需要将缓存的键更改为类似Tuple(Of PropertyInfo, Type).

代码可能如下所示:

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 没有Tuple,但您可以自己创建一个:

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
于 2013-01-15T16:18:14.473 回答