Get value of nested property passed through a lambda expression

793 views Asked by At

I'm trying to build a string for a class that contains a list of all it's property names and their respective values for that instance.

Per the question Get Property Info from an object without giving the property name as string, I'm getting the property name by using an Expression of Type Func Of Type T, Object like this:

Private Function GetPropertyValuePair(Of T)(lambda As Expression(Of Func(Of T, Object))) As String
    Dim body As Expression = lambda

    'ensure we have the right type of thing
    If TypeOf body Is LambdaExpression Then body = DirectCast(body, LambdaExpression).Body
    If TypeOf body Is UnaryExpression Then body = DirectCast(body, UnaryExpression).Operand
    If body.NodeType <> ExpressionType.MemberAccess Then Throw New InvalidOperationException()

    'get property info
    Dim memberInfo = DirectCast(body, MemberExpression).Member
    Dim propInfo = DirectCast(memberInfo, PropertyInfo)

    Return propInfo.Name + " - " + propInfo.GetValue(Me, Nothing)
End Function

In this manner, I'm getting the current value by taking the PropertyInfo, and using GetValue which searches the current instance for that property.

Then I can call it like this:

GetPropertyValuePair(Of Foo)(Function(x) x.Bar)

Which will produce:

Bar - WhateverTheValueIsForBar

The problem is when I have is when the property is nested, GetValue will throw the exception:

Object does not match target type.

For example, in running the following expression:

GetPropertyValuePair(Of Foo)(Function(x) x.Bar.Car)

The property info will be of of type Car, which is not found directly on Foo.

Can I get the instance value from the original lambda expression without having to do the search with GetValue in the first place?

This is possible with the MVC @Html.EditorFor helper, so there must be some mechanism for delivering the value of the passed in function for something like this:

@Html.EditorFor(Function(model) model.Bar.Car)

Any Ideas?

1

There are 1 answers

0
KyleMit On BEST ANSWER

I pieced together a solution based on How to evaluate a lambda expression.

You can Compile the expression to produce the runnable function inside of it.

Dim func = lambda.Compile()
Dim output = func(Me)

Since I'm still working my way through lambdas, here's a little more by way of explanation:

Inside the Expression, we declared the function signature to look like this Func(Of T, Object)
When we called the method, we told the function evaluation to behave like this x => x.Bar.Car

Compiling will give us a new function, stored as a local variable, that will behave like this:

Function Anonymous(x as T) As Object
    Return x.Bar.Car
End

So we haven't actually passed in the instance at all. We've just passed in the definition for what should happen when we call this anonymous function and pass in something of type T.

So we still need to pull in Me. In this case, because this function happens to be sitting inside the same class, we cheated a little since the method already had access to the instance variables. If it was abstracted out somewhere else, the instance itself would have to be passed in as an additional parameter.