0

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");
8
  • 1
    Classes are invariant. So, you can not cast Expression<TFunc1> to Expression<TFunc2> even if cast from TFunc1 to TFunc2 is allowed. Commented May 3, 2016 at 19:19
  • What are you trying to accomplish with this method? I am trying to understand why you would need the Func<T, object>. Are you trying to filter based on a specific property? Commented May 3, 2016 at 19:20
  • You can cast to object via Expression.Convert(property, typeof (object)). Commented May 3, 2016 at 19:22
  • Do you really need the output to be Expression<Func<T, object>>? or Expression<Func<T, TProperty>>? Commented May 3, 2016 at 19:23
  • Joining the other comments. Also the extension method is weird - it's "extending" the IEnumerable<T> instance (the collection argument)but does not use it. You'd better show a sample intended usage. Commented May 3, 2016 at 19:54

3 Answers 3

1

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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");

Comments

0

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;
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.