如何处理表达式树中的null值类型,使其不会给出null引用



我知道如何修复nullrefence,但在这种情况下,它是在表达式树中查找/修复它。

我对表达树不够熟悉,不能自己做,所以有人能用这个来教育我吗?

此代码将适用于第一个属性(Prop1),但不适用于第二个(Prop4

Option Strict On
Option Explicit On
Imports System.Linq.Expressions
Imports System.Reflection
Imports System.Runtime.CompilerServices
Module Module1
    Const loopRun As Integer = 25000
    Const benchRun As Integer = 5
    Private myObj As New Obj With {.Prop1 = "hello",
                                   .Prop4 = 123,
                                   .Prop7 = Now,
                                   .Prop10 = Obj.test.value2}
    Sub Main()
        DisplayValue()
        Console.Read()
    End Sub
    Private Sub DisplayValue()
        Dim value As Object
        For Each i In Cache.expressionGetDict
            value = i.Value(myObj)
            Console.WriteLine("Original expressionGetDict.{0}={1}", i.Key, i.Value(myObj))
            Cache.expressionSetDict(i.Key)(myObj, Nothing) ''on Prop4, null reference
            Console.WriteLine("Cleared expressionGetDict.{0}={1}", i.Key, i.Value(myObj))
            Cache.expressionSetDict(i.Key)(myObj, value)
            Console.WriteLine("Old expressionGetDict.{0}={1}", i.Key, i.Value(myObj))
            Console.WriteLine()
        Next
    End Sub
End Module
Public Class Obj
    Public Enum test As Byte
        value1 = 10
        value2 = 50
        value3 = 250
    End Enum
    Public Property Prop1 As String
    Public Property Prop4 As Integer
    Public Property Prop7 As DateTime
    Public Property Prop10 As test
End Class

Public Module Cache
    Public ReadOnly expressionGetDict As New Dictionary(Of String, Func(Of Object, Object))
    Public ReadOnly expressionSetDict As New Dictionary(Of String, Action(Of Object, Object))
    Sub New()
        For Each p In GetType(Obj).GetProperties(BindingFlags.Instance Or BindingFlags.[Public])
            expressionGetDict.Add(p.Name, p.GetValueGetter)
            expressionSetDict.Add(p.Name, p.GetValueSetter)
        Next
    End Sub
End Module
Public Module PropertyInfoExtensions
    <Extension> _
    Public Function GetValueGetter(propertyInfo As PropertyInfo) As Func(Of Object, Object)
        Dim instance As ParameterExpression = Expression.Parameter(GetType(Object), "instance")
        Dim instanceCast As UnaryExpression = If(Not propertyInfo.DeclaringType.IsValueType, Expression.TypeAs(instance, propertyInfo.DeclaringType), Expression.Convert(instance, propertyInfo.DeclaringType))
        Dim getterCall As MethodCallExpression = Expression.[Call](instanceCast, propertyInfo.GetGetMethod())
        Dim convert As UnaryExpression = Expression.TypeAs(getterCall, GetType(Object))
        Dim lambda As Expression(Of Func(Of Object, Object)) = Expression.Lambda(Of Func(Of Object, Object))(convert, instance)
        Return lambda.Compile
    End Function
    <Extension> _
    Public Function GetValueSetter(propertyInfo As PropertyInfo) As Action(Of Object, Object)
        Dim instance As ParameterExpression = Expression.Parameter(GetType(Object), "instance")
        Dim value As ParameterExpression = Expression.Parameter(GetType(Object), "value")
        Dim instanceCast As UnaryExpression = If((Not propertyInfo.DeclaringType.IsValueType), Expression.TypeAs(instance, propertyInfo.DeclaringType), Expression.Convert(instance, propertyInfo.DeclaringType))
        Dim valueCast As UnaryExpression = If((Not propertyInfo.PropertyType.IsValueType), Expression.TypeAs(value, propertyInfo.PropertyType), Expression.Convert(value, propertyInfo.PropertyType))
        Dim setterCall As MethodCallExpression = Expression.[Call](instanceCast, propertyInfo.GetSetMethod(), valueCast)
        Dim lambda As Expression(Of Action(Of Object, Object)) = Expression.Lambda(Of Action(Of Object, Object))(setterCall, instance, value)
        Return lambda.Compile()
    End Function
End Module

使用Shlomo的答案,我为.net 3.5 创建了一个有效的解决方案

Public Function GetValueSetter(propertyInfo As PropertyInfo) As Action(Of Object, Object)
    Dim instance As ParameterExpression = Expression.Parameter(GetType(Object), "instance")
    Dim value As ParameterExpression = Expression.Parameter(GetType(Object), "value")
    Dim nullCheckedValue = Expression.Condition(
            Expression.Equal(value, Expression.Constant(Nothing, GetType(Object))),
            Expression.Convert(GetDefaultExpression(propertyInfo.PropertyType), GetType(Object)),
            value
        )
    Dim instanceCast As UnaryExpression = Expression.Convert(instance, propertyInfo.DeclaringType)
    Dim valueCast As UnaryExpression = Expression.Convert(nullCheckedValue, propertyInfo.PropertyType)
    Dim setterCall As MethodCallExpression = Expression.[Call](instanceCast, propertyInfo.GetSetMethod(), valueCast)
    Dim lambda As Expression(Of Action(Of Object, Object)) = Expression.Lambda(Of Action(Of Object, Object))(setterCall, instance, value)
    Return lambda.Compile
End Function
Private Function GetDefaultExpression(type As Type) As Expression
    If type.IsValueType Then
        Return Expression.Constant(Activator.CreateInstance(type), GetType(Object))
    End If
    Return Expression.Constant(Nothing, GetType(Object))
End Function

据我所知,这里有几件事很奇怪。

  • GetValueGetter Dim convert As UnaryExpression = Expression.TypeAs(getterCall, GetType(Object))中的此行应该是Expression.Convert,而不是TypeAsTypeAs仅适用于引用类型,并且您的四个属性中有三个是值类型。但是,这似乎不是您当前的错误。

  • 类似地,VB.NET的Nothing也会让你绊倒。VB.NET在编译时编译Nothing。由于动态生成的函数属于Object类型,因此Nothing赋值尝试将Object的Nothing(即null引用)赋值给Prop4。由于Prop4是一个值类型,因此会得到null引用异常。您想要Integer的Nothing分配给Prop4

经过以下修改,我得到了可以工作的代码:

设置模块缓存如下:

Public Module Cache
    Public ReadOnly expressionGetDict As New Dictionary(Of String, Func(Of Object, Object))
    Public ReadOnly expressionSetDict As New Dictionary(Of String, Action(Of Object, Object))
    Public ReadOnly propertyTypeDict As New Dictionary(Of String, Type)
    Sub New()
        For Each p In GetType(Obj).GetProperties(BindingFlags.Instance Or BindingFlags.[Public])
            expressionGetDict.Add(p.Name, p.GetValueGetter.Compile())
            expressionSetDict.Add(p.Name, p.GetValueSetter.Compile())
            propertyTypeDict(p.Name) = p.PropertyType
        Next
    End Sub
End Module

取代了Cache.expressionSetDict(i.Key)(myObj, Nothing)(在DisplayValue中为:

Dim propertyType = Cache.propertyTypeDict(i.Key)
Dim typedNothing = CTypeDynamic(Nothing, propertyType)
Cache.expressionSetDict(i.Key)(myObj, typedNothing) 'on Prop4,  no longer a null reference exception

编辑:

这个问题在表达式构建中也是可以解决的。您可以相应地修改GetValueSetter,而不是执行上述操作:

Public Function GetValueSetter(propertyInfo As PropertyInfo) As Expression(Of Action(Of Object, Object))
    Dim instance As ParameterExpression = Expression.Parameter(GetType(Object), "instance")
    Dim value As ParameterExpression = Expression.Parameter(GetType(Object), "value")
    Dim nullCheckedValue = Expression.Condition(
            Expression.ReferenceEqual(value, Expression.Default(GetType(Object))),
            Expression.Convert(Expression.Constant(CTypeDynamic(Nothing, propertyInfo.PropertyType)), GetType(Object)),
            value
        )
    Dim instanceCast As UnaryExpression = Expression.Convert(instance, propertyInfo.DeclaringType)
    Dim valueCast As UnaryExpression = Expression.Convert(nullCheckedValue, propertyInfo.PropertyType)
    Dim setterCall As MethodCallExpression = Expression.[Call](instanceCast, propertyInfo.GetSetMethod(), valueCast)
    Dim lambda As Expression(Of Action(Of Object, Object)) = Expression.Lambda(Of Action(Of Object, Object))(setterCall, instance, value)
    Return lambda
End Function

第二个解决方案在表达式生成的函数中嵌入了一个null检查,并用default(T)替换null值,正如C-Sharpists所说。用VB的说法,我想你会说你用正确的Nothing替换了错误的Nothing

相关内容

  • 没有找到相关文章

最新更新