简体   繁体   中英

Using Reflection on Type T to create Expression<Func<T, TValue>> for Properties that have attributes

I'm currently exploring the viability to encapsulate some logic that would typically be iterative. Using a 3rd party library it has a generic Html Helper method that allows you to map properties from a Generic T class to a table component. Typically you'd have to write something to the effect of:

HtmlHelper.GenerateTable<ClassName>
.configure(tableDefinition =>{
  // Adding each property you want to render here
  tableDefinition.add(myClassRef => myClassRef.propertyA);
  tableDefinition.add(myClassRef => myClassRef.propertyB);
})

I'm exploring the idea of adding attributes to properties such as a standard Display attribute and then using reflection, adding that property to the container. The add method only accepts an argument of type Expression<Func<T, TValue>> . With my current understanding of reflection I know I can property identify properties that would be applicable by looping through PropertyInfo and checking for the attribute I want using GetCustomAttribute .

What I'm stumped on is if it's possible using reflection to supply the argument type that the add method expects?

I'll throw the helper method logic I've started using so far. My assumption is that this is leading me towards Expression and Lambda classes, but I haven't been able to flesh out anything that works since I don't technically have a TValue .


var t = typeof(T);
List<PropertyInfo> properties = t.GetProperties().ToList();
foreach(var prop in properties)
{
    var attr = (DisplayAttribute[])prop.GetCustomAttributes(typeof(DisplayAttribute), false);
    if (attr.Length > 0)
    {
        // TODO: Call add with expression?
    }
}

You can achieve this fairly easily with the methods on the Expression class . To start with, it's easiest to write expressions using lambdas (eg Expression<Func<A, B>> expr = x => x.PropertyA ), then inspect it in a debugger to see what the compiler constructs.

In your case, something like this should work:

// The parameter passed into the expression (myClassRef) in your code
var parameter = Expression.Parameter(typeof(T), "myClassRef");

// Access the property described by the PropertyInfo 'prop' on the
// myClassRef parameter
var propertyAccess = Expression.Property(parameter, prop);
// Since we're returning an 'object', we'll need to make sure we box value types.
var box = Expression.Convert(propertyAccess, typeof(object));

// Construct the whole lambda
var lambda = Expression.Lambda<Func<T, object>>(box, parameter);
tableDefinition.add(lambda);

Note that I'm passing an Expression<Func<T, object>> into add , rather than an Expression<Func<T, TValue>> . I'm guessing that this doesn't matter, and it avoids having to call add using reflection. When I've written methods similar to add in the past, I don't care about TValue at all: I just inspect the Expression and fetch the PropertyInfo from the property access.

If you do need to pass an Expression<Func<T, TValue>> you'll have to do something like:

var delegateType = typeof(Func<,>).MakeGenericType(typeof(T), prop.PropertyType);
var lambda = Expression.Lambda(delegateType, propertyAccess, parameter);

// I'm assuming that your `add` method looks like:
// void add<T, TValue>(Expression<Func<T, TValue>> expr)
// I'm also assuming there's only one method called 'add' -- be smarter in that
// 'GetMethod' call if not.
var addMethod = typeof(TableDefinition)
    .GetMethod("add")
    .MakeGenericMethod(typeof(T), prop.PropertyType);

addMethod.Invoke(tableDefinition, new object[] { lambda });

Note that you don't need the Expression.Convert(..., typeof(object)) in this case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM