如何将"IEnumerable<Unknown T>"转换为"IEnumerable<Whatever>"



我正在做一个以通用方式过滤列表的项目。我正在运行时检索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'

使用 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>

你为什么要用Expression?如果没有一个好的最小、完整和可验证的代码示例,就很难理解您的问题。即使我们能解决选角问题,你还是会回来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()方法以使其不是泛型方法,并使用object.Equals()将属性值与条件值进行比较会更直接。您似乎跳过了很多箍来使用泛型,而没有获得泛型的任何编译时优势。

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

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

以下是使用 FastTMember 的代码:

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>,这也将解决您的问题。

相关内容

  • 没有找到相关文章

最新更新