简体   繁体   中英

How to cast an `IEnumerable<Unknown T>` to `IEnumerable<Whatever>`

I am working on a project to filter a list in a generic way. I am retrieving an IEnumerable<T> at runtime but I don't know what is T . I need to cast the list I am retrieving to IEnumerable<T> and not IEnumerable , because I need extension methods like ToList and Where . Here is my code.

private IList<object> UpdateList(KeyValuePair<string, string>[] conditions)
{
    // Here I want to get the list property to update
    // The List is of List<Model1>, but of course I don't know that at runtime
    // casting it to IEnumerable<object> would give Invalid Cast Exception
    var listToModify = (IEnumerable<object>)propertyListInfoToUpdate.GetValue(model);

    foreach (var condition in conditions)
    {
        // Filter is an extension method defined below
        listToModify = listToModify.Filter(condition .Key, condition .Value);
    }

    // ToList can only be invoked on IEnumerable<T>
    return listToModify.ToList();
}



public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, string propertyName, object value)
{
    var parameter = Expression.Parameter(typeof(T), "x");
    var property = Expression.Property(parameter, propertyName);
    var propertyType = ((PropertyInfo)property.Member).PropertyType;
    Expression constant = Expression.Constant(value);

    if (((ConstantExpression)constant).Type != propertyType)
    { constant = Expression.Convert(constant, propertyType); }

    var equality = Expression.Equal(property, constant);
    var predicate = Expression.Lambda<Func<T, bool>>(equality, parameter);

    var compiled = predicate.Compile();

    // Where can only be invoked on IEnumerable<T>
    return source.Where(compiled);
}

Also please note that I cannot retrieve the List like this

((IEnumerable)propertyListInfoToUpdate.GetValue(model)).Cast<object>()

since it will generate the below exception in the Filter extention

ParameterExpression of type 'Model1' cannot be used for delegate parameter of type 'System.Object'

Use GetGenericArguments and MakeGenericMethod to interface generic signatures.

private IList<object> UpdateList(KeyValuePair<string, string> conditions)
{
    var rawList = (IEnumerable)propertyListInfoToUpdate.GetValue(model);

    var listItemType = propertyListInfoToUpdate.PropertyType.GetGenericArguments().First();
    var filterMethod = this.GetType().GetMethod("Filter").MakeGenericMethod(genericType);

    object listToModify = rawList;
    foreach (var condition in conditions)
    {
        listToModify = filterMethod.Invoke(null, new object[] { listToModify, condition.Key, condition.Value })
    }

    return ((IEnumerable)listToModify).Cast<object>().ToList();
}

Assuming your propertyListInfoToUpdate is a PropertyInfo and the property type is List<T> .

Why are you using Expression at all? It's hard to understand your question without a good Minimal, Complete, and Verifiable code example . Even if we could solve the casting question, you're still returning IList<object> . It's not like the consumer of the code will benefit from the casting.

And it's not really possible to solve the casting problem, at least not in the way you seem to want. A different approach would be to call the Filter() dynamically. In the olden days, we'd have to do this by using reflection, but the dynamic type gives us runtime support. You could get it to work something like this:

    private IList<object> UpdateList(KeyValuePair<string, string>[] conditions)
    {
        dynamic listToModify = propertyListInfoToUpdate.GetValue(model);

        foreach (var condition in conditions)
        {
            // Filter is an extension method defined below
            listToModify = Filter(listToModify, condition.Key, condition.Value);
        }

        // ToList can only be invoked on IEnumerable<T>
        return ((IEnumerable<object>)Enumerable.Cast<object>(listToModify)).ToList();
    }

NOTE: your original code isn't valid; I made the assumption that conditions is supposed to be an array, but of course if you change it to anything that has a GetEnumerator() method, that would be fine.

All that said, it seems to me that given the lack of a compile-time type parameter, it would be more direct to just change your Filter() method so that it's not generic, and so that you use object.Equals() to compare the property value to the condition value. You seem to be jumping through a lot of hoops to use generics, without gaining any of the compile-time benefit of generics.

Note that if all this was about was executing LINQ query methods, that could be addressed easily simply by using Enumerable.Cast<object>() and using object.Equals() directly. It's the fact that you want to use expressions to access the property value (a reasonable goal) that is complicating the issue. But even there, you can stick with IEnumerable<object> and just build the object.Equals() into your expression.

Creating an expression and compiling it every time is very expensive. You should either use Reflection directly or a library like FastMember (or cache the expressions). In addition, your code uses Expression.Equal which translates into the equality operator ( == ), which is not a good way of comparing objects. You should be using Object.Equals .

Here is the code using FastMember:

private IList<object> UpdateList(KeyValuePair<string, string>[] conditions)
{
    var listToModify = ((IEnumerable)propertyListInfoToUpdate.GetValue(model)).Cast<object>();

    foreach (var condition in conditions)
    {
        listToModify = listToModify.Where(o => Equals(ObjectAccessor.Create(o)[condition.Key], condition.Value));
    }

    return listToModify.ToList();
}

Side note - your Filter method doesn't really need generics. It can be changed to accept and return an IEnumerable<object> by tweaking the expression, which also would've solved your problem.

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