Cubicle.Jockey
Cubicle.Jockey

Reputation: 3328

LambdaExpression to Expression via Extensions Method

I looked at the other SO versions of this question but it seems the casting out of a method works for others. I am not sure what I am doing wrong here. I am new to the Expression Building part of Linq.

My extensions method is as follows:

void Main()
{
    var people = LoadData().AsQueryable();

    var expression = people.PropertySelector<Person>("LastName");
    expression.Should().BeOfType(typeof(Expression<Func<Person, object>>));

    var result = people.OrderBy(expression);
}

public static class Extensions
{
    public static Expression<Func<T, object>> PropertySelector<T>(this IEnumerable<T> collection, string propertyName)
    {
        if (string.IsNullOrWhiteSpace(propertyName))
        {
            throw new ArgumentException(nameof(propertyName));
        }

        var properties = typeof(T).GetProperties();
        if (!properties.Any(p => p.Name == propertyName))
        {
            throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
        }

        var propertyInfo = properties.Single(p => p.Name == propertyName);

        var alias = Expression.Parameter(typeof(T), "_");
        var property = Expression.Property(alias, propertyInfo);
        var funcType =  typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
        var lambda = Expression.Lambda(funcType, property, alias);

        return (Expression<Func<T, object>>)lambda;
    }
}


#region 

private Random rand = new Random();

// Define other methods and classes here
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

public IEnumerable<Person> LoadData()
{
    IList<Person> people = new List<Person>();
    for (var i = 0; i < 15; i++)
    {
        people.Add(new Person
        {
            FirstName = $"FirstName {i}",
            LastName = $"LastName {i}",
            Age = rand.Next(1, 100)
        });
    }
    return people;
}

#endregion

I get an exception on the return during the cast. At this point T is type Person and object is a string. My lambda.GetType() is reporting that it's of type Expression<Func<Person, string>> The exception is:

Unable to cast object of type 'System.Linq.Expressions.Expression`1[System.Func`2[UserQuery+Person,System.String]]' to type 'System.Linq.Expressions.Expression`1[System.Func`2[UserQuery+Person,System.Object]]'.

What about my cast is incorrect? Thanks.

EDIT: I updated with my full code that I am playing with in LinqPad. I am really just trying to figure out if there is an easy way to generate a lambda expression by passing in the property name. I was doing something like this before but I was just doing a switch on property name then using lambda syntax to create the OrderBy query dynamically.

This is strictly me trying to learn how Expression static methods can be used to achieve the same result as the example below. I am trying to mimic the below via extension method. But it doesn't have to be that way. It was just easiest to try while dinking around in LinqPad.

Expression<Func<Loan, object>> sortExpression;

    switch (propertyFilter)
    {
        case "Age":
            sortExpression = (l => l.Age);
            break;
        case "LastName":
            sortExpression = (l => l.LastName);
            break;
        default:
            sortExpression = (l => l.FirstName);
            break;
    }

    var sortedLoans = loans.AsQueryable().OrderBy(sortExpression);
    sortedLoans.Dump("Filtered Property Result");

Upvotes: 0

Views: 1331

Answers (3)

Cubicle.Jockey
Cubicle.Jockey

Reputation: 3328

I got the result I wanted. After comments from @Gusman, @IvanStoev and @PetSerAl I got it to function. I can move on with my exploring and learning again. Thank you very much. Final result was to template the Propertytype.

  public static Expression<Func<T, TPropertyType>> PropertySelector<T, TPropertyType>(this IEnumerable<T> collection, string propertyName)
    {
        if (string.IsNullOrWhiteSpace(propertyName))
        {
            throw new ArgumentException(nameof(propertyName));
        }

        var properties = typeof(T).GetProperties();
        if (!properties.Any(p => p.Name == propertyName))
        {
            throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
        }

        var propertyInfo = properties.Single(p => p.Name == propertyName);

        var alias = Expression.Parameter(typeof(T), "_");
        var property = Expression.Property(alias, propertyInfo);
        var funcType =  typeof(Func<,>).MakeGenericType(typeof(T), typeof(TPropertyType));
        var lambda = Expression.Lambda(funcType, property, alias);

        return (Expression<Func<T, TPropertyType>>)lambda;
    }

Upvotes: 0

pil0t
pil0t

Reputation: 2185

To keep current method signature you could do this:

        var funcType = typeof(Func<,>).MakeGenericType(typeof(T), typeof(object));
        var typeAs = Expression.TypeAs(property, typeof(object));
        var lambda = Expression.Lambda(funcType, typeAs, alias);

and better way is to change your method to

    public static Expression<Func<T, Tout>> PropertySelector<T, Tout>(this IEnumerable<T> collection, string propertyName)
    {
        if (string.IsNullOrWhiteSpace(propertyName))
        {
            throw new ArgumentException(nameof(propertyName));
        }

        var properties = typeof(T).GetProperties();
        if (!properties.Any(p => p.Name == propertyName))
        {
            throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
        }

        var propertyInfo = properties.Single(p => p.Name == propertyName);

        var alias = Expression.Parameter(typeof(T), "_");
        var property = Expression.Property(alias, propertyInfo);
        var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
        var lambda = Expression.Lambda(funcType, property, alias);

        return (Expression<Func<T, Tout>>)lambda;
    }

and call it with

        var expression = people.PropertySelector<Person, string>("LastName");

Upvotes: 1

Gusman
Gusman

Reputation: 15151

Your code is creating a Func<UserQuery, String> because you're geting it's intrinsic type with

var propertyInfo = properties.Single(p => p.Name == propertyName);
var funcType =  typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);

If you want to return a Func<T, object> then create a Func<T, object>, not a Func<T, (reflected property type)>, else the better solution is to use a Func<TOut, TIn> and create a totally generic function.

Upvotes: 1

Related Questions