1

As specified in the question, I am interested in using the Dynamic features of .net to cache an object's field getter/setter and calling it at runtime.

Using info from: Is there a way to create a delegate to get and set values for a FieldInfo?

I have put together a class and function that set up the functionality that I need:

Public Class c1
    Public someField As Integer 'we will Get the value of this dynamically
End Class

Public Function CreateGetter(Of S, T)(ByVal strFieldName As String) As Func(Of S, T)
    'creates a function to return the value
    Dim objFieldInfo As FieldInfo
    Dim strMethodName As String
    Dim objGetterMethod As DynamicMethod
    Dim objGen As ILGenerator
    objFieldInfo = GetType(S).GetField(strFieldName)
    strMethodName = Convert.ToString(objFieldInfo.ReflectedType.FullName) & ".get_" & Convert.ToString(objFieldInfo.Name)
    objGetterMethod = New DynamicMethod(strMethodName, GetType(T), New Type(0) {GetType(S)}, True)
    objGen = objGetterMethod.GetILGenerator()
    If objFieldInfo.IsStatic = False Then
        objGen.Emit(OpCodes.Ldarg_0)
        objGen.Emit(OpCodes.Ldfld, objFieldInfo)
    Else
        objGen.Emit(OpCodes.Ldsfld, objFieldInfo)
    End If
    objGen.Emit(OpCodes.Ret)
    Return DirectCast(objGetterMethod.CreateDelegate(GetType(Func(Of S, T))), Func(Of S, T))
End Function

I call this good code with:

Dim getValue = CreateGetter(Of c1, Integer)("someField")
Dim someValue As Integer
someValue = getValue(o1)

However, the part I am stumped at is how to modify function CreateGetter to be able to use it in a cached form like: (caching the instance object)

Dim getValue = CreateGetter(Of c1, Integer)(o1,"someField")
Dim someValue As Integer
someValue = getValue()

I realize this may require some modding of the IL code in CreateGetter but that is the tricky part that I am stuck at.

Community
  • 1
  • 1

1 Answers1

1

Actually, you can't do this using a single method. To hold the object permanently, you need a field in some object and then create a delegate that points to some method on that object.

You could do all that using Reflection.Emit, but that would be tedious. Instead, you can take advantage of the fact that the compiler can already do this to create closures: instead of returning the DynamicMethod delegate directly, you return a lambda that invokes that delegate:

Dim fieldAccessor = DirectCast(objGetterMethod.CreateDelegate(GetType(Func(Of S, T))), Func(Of S, T))

Return Function() fieldAccessor(obj)

Another option would be to use Expression Trees for all of this. This has the advantage that you don't have to deal with IL, which can be hard to get right. Something like:

Function CreateGetter(Of S, T)(obj as S, fieldName As String) As Func(Of T)
    Dim expr = Expression.Lambda(Of Func(Of T))(
        Expression.Field(Expression.Constant(obj), fieldName))
    Return expr.Compile()
End Function

This version works only for instance fields, I think that for static fields, an overload that doesn't take obj makes more sense.

svick
  • 236,525
  • 50
  • 385
  • 514
  • 1
    Hey @svick :) For instance fields, you can do this by simply using the overload of CreateDelegate takes a "target object". – Jb Evain Aug 08 '14 at 12:28
  • Pretty much like this: http://stackoverflow.com/questions/7935306/compiling-a-lambda-expression-results-in-delegate-with-closure-argument/7940617#7940617 – Jb Evain Aug 08 '14 at 12:34
  • Hi @svick, the fieldAccessor example doesn't seem to work, the CreateDelegate requires a second param. The second example seems to work, though. Since I'm using the Getter to access multiple fields from a record, and then traversing to the next record (i.e. 000s of records), I was wanting to achieve a performance gain by caching the object so that the Get calls to each field on each object were performance optimized and not needing the object ref passed per call. Ideally I would just set the obj ref once per record and theoretically this would offer better performance than first attempt above. – stackmike_2014 Aug 08 '14 at 14:15
  • @stackmike_2014 That `fieldAccessor` is directly copied from your code and it worked when I tried it. But I don't understand why do you expect performance improvements here, since each field would have a separate delegate. – svick Aug 08 '14 at 15:21
  • Yes, each field has a separate delegate. Maybe I should explain a little more: I create an array of these delegates once, with 1 for each field. When extracting the field values from 1000s of records just prior to a DB bulk insert/update, I was thinking it would be faster to cache the object per record, but now that I realize it I would have to do it per field of that record, so there wouldn't be a performance gain since I pass the obj ref in anyway per field get... Thanks for your comments svick, I think my current code is probably satisfactory... – stackmike_2014 Aug 08 '14 at 15:35