【问题标题】:how to handle null valuetype in expression tree so it wont give nullreference?如何处理表达式树中的空值类型,使其不会给出空引用?
【发布时间】:2016-02-10 05:47:06
【问题描述】:

我知道如何fix a 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

【问题讨论】:

    标签: .net vb.net reflection .net-3.5 expression-trees


    【解决方案1】:

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

    • 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 是值类型,因此您会得到空引用异常。您希望将 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
    

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

    【讨论】:

    • 有没有办法改变表达式树来处理空值而不是修改调用它的代码?
    • 我将它移到 .net 3.5 的工作解决方案中,似乎可以工作
    • 我创建了一个代码审查问题,如果你有兴趣跳进去codereview.stackexchange.com/questions/110364/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-21
    • 1970-01-01
    • 1970-01-01
    • 2019-04-19
    • 2020-02-02
    • 2017-09-25
    • 1970-01-01
    相关资源
    最近更新 更多