Build expressions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Employee | |
{ | |
public IList<Order> Orders { get; set; } | |
} | |
class Order | |
{ | |
public int OrderID { get; set; } | |
public IList<Customer> Customers { get; set; } | |
} | |
class Customer | |
{ | |
public int CustomerID { get; set; } | |
} | |
private static Expression BuildAccessors(Expression parent, string[] properties, int index) | |
{ | |
if (index < properties.Length) | |
{ | |
var member = properties[index]; | |
// If it's IEnumerable like Orders is, then we need to do something more complicated | |
if (typeof(IEnumerable).IsAssignableFrom(parent.Type) && parent.Type != typeof(string)) | |
{ | |
var enumerableType = parent.Type.GetGenericArguments().SingleOrDefault(); // input eg: Employee.Orders (type IList<Order>), output: type Order | |
var param = Expression.Parameter(enumerableType, "x"); // declare parameter for the lambda expression of Orders.Select(x => x.OrderID) | |
var lambdaBody = BuildAccessors(param, properties, index); // Recurse to build the inside of the lambda, so x => x.OrderID. | |
var funcType = typeof(Func<,>).MakeGenericType(enumerableType, lambdaBody.Type); // Lambda is of type Func<Order, int> in the case of x => x.OrderID | |
var lambda = Expression.Lambda(funcType, lambdaBody, param); | |
// This part is messy, I want to find the method Enumerable.Select<Order, int>(..) but I don't think there's a more succint way. Might be wrong. | |
var selectMethod = (from m in typeof(Enumerable).GetMethods() | |
where m.Name == "Select" | |
&& m.IsGenericMethod | |
let parameters = m.GetParameters() | |
where parameters.Length == 2 | |
&& parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>) | |
select m).Single().MakeGenericMethod(enumerableType, lambdaBody.Type); | |
// Do Orders.Select(x => x.OrderID) | |
var invokeSelect = Expression.Call(null, selectMethod, parent, lambda); | |
return invokeSelect; | |
} | |
else | |
{ | |
// Simply access a property like OrderID | |
var newParent = Expression.PropertyOrField(parent, member); | |
// Recurse | |
return BuildAccessors(newParent, properties, ++index); | |
} | |
} | |
else | |
{ | |
// Return the final expression once we're done recursing. | |
return parent; | |
} | |
} | |
static void Main(string[] args) | |
{ | |
// Slightly more complex example to show it should be able to handle this scenario too | |
var str = "Employee.Orders.Customers.CustomerID"; | |
var split = str.Split('.'); | |
// Create the root of the expression, namely accessing an employee variable. Could be a Expression.Parameter too. | |
var baseExpr = Expression.Variable(typeof(Employee), split[0]); | |
// Start at index 1, we've already processed index 0 (the root) | |
var result = BuildAccessors(baseExpr, split, 1); | |
// Create the resulting lambda | |
var lambda = Expression.Lambda<Func<Employee, IEnumerable<IEnumerable<int>>>>(result, baseExpr); | |
// Compile to verify that it's valid | |
var del = lambda.Compile(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment