2015-11-09 1 views
0

Я знаю как fix a nullrefence, но в этом случае он находит/фиксирует его в дереве выражений.Как обрабатывать null valuetype в дереве выражений, чтобы он не дал nullreference?

Я не достаточно знаком с деревом выражений, чтобы сделать это сам, чтобы кто-то мог обучить меня этому?

Этот код будет работать с первым свойством (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 

ответ

1

Насколько я могу судить, у вас есть пара вещей, странных здесь.

  • Эта линия в GetValueGetter Dim convert As UnaryExpression = Expression.TypeAs(getterCall, GetType(Object)) Должно быть Expression.Convert, не TypeAs. TypeAs работает только с ссылочными типами, а три из четырех свойств - это типы значений. Однако это не является вашей текущей ошибкой.

  • Аналогичным образом, VB.NET's Nothing отключает вас. VB.NET компилирует Nothing во время компиляции. Поскольку ваши динамически сгенерированные функции имеют тип Object, поэтому назначение Nothing пытается присвоить Object «Ничто» (которое является ссылкой null) на Prop4. Так как Prop4 является типом значения, вы получаете исключение нулевой ссылки. Вы хотите, чтобы Integer ничего не назначалось 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 

Редактировать:

Проблема также разрешима в выражении-build. Вместо того, чтобы делать выше, вы можете изменить 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 

Это второе решение встраивает нуль-проверку в функции выражения сгенерированных и заменяет нулевое значение с default(T), как C-Sharpists бы сказать. В VB-языке я думаю, вы сказали бы, что вы заменяете неправильный Nothing с правом Nothing.

+0

Есть ли способ изменить дерево выражений для обработки нулевого значения вместо изменения кода, который его вызывает? – Fredou

+0

Обновлен ответ. – Shlomo

+0

Я переместил его в рабочее решение для .net 3.5, похоже, работает – Fredou

Смежные вопросы