I have some part of my code that uses reflection and there is some slowdown issue that I have to fix.
After some research I found that expression tree seem to be a good candidate to fix it also it MUST work under .NET 3.5.
I have less than a week of experience with expression tree and the code below was found on some blogs/articles/questions/answers and I modified it a little.
This is the code that I want to be reviewed:
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 = If((Not propertyInfo.PropertyType.IsValueType), Expression.TypeAs(getterCall, GetType(Object)), Expression.Convert(getterCall, GetType(Object))) 'Expression.Convert(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 IsNull As BinaryExpression = Expression.Equal(value, Expression.Constant(Nothing, GetType(Object)))
Dim nullValue As UnaryExpression = Expression.Convert(propertyInfo.PropertyType.GetDefaultExpression, GetType(Object))
Dim nullCheckedValue = Expression.Condition(IsNull, nullValue, 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(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
<Extension>
Public 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
End Module
This is the code that I'm using to test it:
Option Strict On
Option Explicit On
Imports System.ComponentModel
Imports System.Linq.Expressions
Imports System.Reflection
Imports System.Runtime.CompilerServices
Module Module1
Const loopRun As Integer = 10000
Const benchRun As Integer = 5
Private myObj As New Obj With {.Prop1 = "hello", .Prop2 = "world", .Prop3 = "!",
.Prop4 = 123, .Prop5 = 456, .Prop6 = 42,
.Prop7 = Now.AddDays(-1), .Prop8 = Now, .Prop9 = Now.AddDays(1),
.Prop10 = Obj.test.value1, .Prop11 = Obj.test.value2, .Prop12 = Obj.test.value3,
.Prop13 = New ObjTest With {.Abc = "inst1", .Cde = 1},
.Prop14 = New ObjTest With {.Abc = "inst2", .Cde = 2},
.Prop15 = New ObjTest With {.Abc = "inst3", .Cde = 3}}
Private myProp() As String = New String() {"Prop1", "Prop2", "Prop3", "Prop4", "Prop5", "Prop6", "Prop7", "Prop8", "Prop9", "Prop10", "Prop11", "Prop12", "Prop13", "Prop14", "Prop15"}
Sub Main()
DisplayValue()
WarmUp()
Benchmark()
Console.Read()
End Sub
Private Sub DisplayValue()
Dim value As Object
For Each i In Cache.reflectionDict
value = i.Value.GetValue(myObj, Nothing)
Console.WriteLine("Original reflectionDict.{0}={1}", i.Key, i.Value.GetValue(myObj, Nothing))
i.Value.SetValue(myObj, Nothing, Nothing)
Console.WriteLine("Cleared reflectionDict.{0}={1}", i.Key, i.Value.GetValue(myObj, Nothing))
i.Value.SetValue(myObj, value, Nothing)
Console.WriteLine("Old reflectionDict.{0}={1}", i.Key, i.Value.GetValue(myObj, Nothing))
Console.WriteLine()
Next
For Each i In Cache.descriptorDict
value = i.Value.GetValue(myObj)
Console.WriteLine("Original reflectionDict.{0}={1}", i.Key, i.Value.GetValue(myObj))
i.Value.SetValue(myObj, Nothing)
Console.WriteLine("Cleared descriptorDict.{0}={1}", i.Key, i.Value.GetValue(myObj))
i.Value.SetValue(myObj, value)
Console.WriteLine("Old descriptorDict.{0}={1}", i.Key, i.Value.GetValue(myObj))
Console.WriteLine()
Next
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)
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
Private Sub WarmUp()
For j = 0 To 100
For Each i In Cache.reflectionDict
i.Value.SetValue(myObj, i.Value.GetValue(myObj, Nothing), Nothing)
Next
For Each i In Cache.descriptorDict
i.Value.SetValue(myObj, i.Value.GetValue(myObj))
Next
For Each i In Cache.expressionSetDict
i.Value.Invoke(myObj, Cache.expressionGetDict(i.Key)(myObj))
Next
Next
End Sub
Private Sub Benchmark()
Dim sw As Stopwatch
For z = 1 To benchRun
sw = Stopwatch.StartNew
For i = 0 To loopRun
For Each s In myProp
Cache.descriptorDict(s).SetValue(myObj, Cache.descriptorDict(s).GetValue(myObj))
Next
Next
sw.Stop()
Console.WriteLine("{0} : {1}ms", "descriptorDict", sw.Elapsed.TotalMilliseconds)
Next
For z = 1 To benchRun
sw = Stopwatch.StartNew
For i = 0 To loopRun
For Each s In myProp
Cache.reflectionDict(s).SetValue(myObj, Cache.reflectionDict(s).GetValue(myObj, Nothing), Nothing)
Next
Next
sw.Stop()
Console.WriteLine("{0} : {1}ms", "reflectionDict", sw.Elapsed.TotalMilliseconds)
Next
For z = 1 To benchRun
sw = Stopwatch.StartNew
For i = 0 To loopRun
For Each s In myProp
Cache.expressionSetDict(s)(myObj, Cache.expressionGetDict(s)(myObj))
Next
Next
sw.Stop()
Console.WriteLine("{0} {1} : {2}ms", "expressionSetDict", "expressionGetDict", sw.Elapsed.TotalMilliseconds)
Next
End Sub
End Module
Public Class ObjTest
Public Property Abc As String
Public Property Cde As Integer
Public Overrides Function ToString() As String
Return String.Format("ObjTest {0} : {1}", Abc, Cde)
End Function
End Class
Public Class Obj
Public Enum test As Byte
value1 = 10
value2 = 50
value3 = 250
End Enum
Public Property Prop1 As String
Public Property Prop2 As String
Public Property Prop3 As String
Public Property Prop4 As Integer
Public Property Prop5 As Integer
Public Property Prop6 As Integer
Public Property Prop7 As DateTime
Public Property Prop8 As DateTime
Public Property Prop9 As DateTime
Public Property Prop10 As test
Public Property Prop11 As test
Public Property Prop12 As test
Public Property Prop13 As ObjTest
Public Property Prop14 As ObjTest
Public Property Prop15 As ObjTest
End Class
Public Module Cache
Public ReadOnly reflectionDict As New Dictionary(Of String, PropertyInfo)
Public ReadOnly descriptorDict As New Dictionary(Of String, PropertyDescriptor)
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])
reflectionDict.Add(p.Name, p)
Next
For Each p As PropertyDescriptor In TypeDescriptor.GetProperties(GetType(Obj))
descriptorDict.Add(p.Name, p)
Next
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 = If((Not propertyInfo.PropertyType.IsValueType), Expression.TypeAs(getterCall, GetType(Object)), Expression.Convert(getterCall, GetType(Object))) 'Expression.Convert(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 IsNull As BinaryExpression = Expression.Equal(value, Expression.Constant(Nothing, GetType(Object)))
Dim nullValue As UnaryExpression = Expression.Convert(propertyInfo.PropertyType.GetDefaultExpression, GetType(Object))
Dim nullCheckedValue = Expression.Condition(IsNull, nullValue, 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(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
<Extension>
Public 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
End Module