如何将`IEnumerable <unknown t =“”>`转换为`IEnumerable <whatever>`

时间:2016-07-09 21:44:40

标签: c# linq

我正在开发一个以通用方式过滤列表的项目。我在运行时检索IEnumerable<T>,但我不知道T是什么。我需要将我检索的列表转换为IEnumerable<T>而不是IEnumerable,因为我需要ToListWhere等扩展方法。这是我的代码。

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

另请注意,我无法像这样检索列表

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

因为它会在Filter扩展

中生成以下异常
ParameterExpression of type 'Model1' cannot be used for delegate parameter of type 'System.Object'

3 个答案:

答案 0 :(得分:1)

使用GetGenericArgumentsMakeGenericMethod来连接通用签名。

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

假设您的propertyListInfoToUpdatePropertyInfo且属性类型为List<T>

答案 1 :(得分:1)

你为什么要使用Expression?如果没有好的Minimal, Complete, and Verifiable code example,很难理解你的问题。即使我们可以解决投射问题,您仍然会返回IList<object>。这并不是代码的消费者会从演员中受益。

并不是真的有可能解决铸造问题,至少不是你想要的方式。另一种方法是动态调用Filter()。在过去,我们必须通过使用反射来完成此操作,但dynamic类型为我们提供了运行时支持。你可以让它像这样工作:

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

注意:您的原始代码无效;我假设conditions应该是一个数组,但当然如果你把它改成任何有GetEnumerator()方法的东西,那就没关系了。

所有这一切,在我看来,由于缺少编译时类型参数,更改你的Filter()方法更加直接,因此它不是通用的,所以你使用{ {1}}将属性值与条件值进行比较。你似乎在使用泛型中跳过很多箍,而没有获得泛型的任何编译时益。

请注意,如果所有这些都是执行LINQ查询方法,那么只需使用object.Equals()并直接使用Enumerable.Cast<object>()即可轻松解决这个问题。事实上,您希望使用表达式来访问使问题复杂化的属性值(合理的目标)。但即使在那里,你仍然可以坚持使用object.Equals()并将IEnumerable<object>构建到你的表达中。

答案 2 :(得分:1)

创建表达式并每次编译都非常昂贵。您应该直接使用Reflection或像FastMember这样的库(或缓存表达式)。此外,您的代码使用Expression.Equal转换为相等运算符(==),这不是比较对象的好方法。您应该使用Object.Equals

以下是使用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();
}

旁注 - 您的Filter方法并不真正需要泛型。可以通过调整表达式将其更改为接受并返回IEnumerable<object>,这也可以解决您的问题。