2016-07-09 31 views
0

我正在处理项目以通用方式筛选列表。我在运行时检索到IEnumerable<T>,但我不知道T是什么。我需要将我检索的列表转换为IEnumerable<T>而不是IEnumerable,因为我需要扩展方法,如ToListWhere。这是我的代码。如何将`IEnumerable <Unknown T>`转换为`IEnumerable <Whatever>`

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 extention

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

这对我来说看起来像一个非常合理的问题,而不是那种肯定应该被低估而没有任何评论的那种。 –

+0

为什么不给'UpdateList'一个泛型类型参数? 'UpdateList '。并输入'propertyListInfoToUpdate.GetValue(model)'作为参数(无论如何,这是更好的方法,因为现在你的方法依赖于外部状态)。 –

+0

@GertArnold同样的问题仍然存在,因为外部调用者仍然不知道什么是“T”。我所知道的只是我需要检索的财产,没有更多。 – Ayman

回答

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

为什么你根本用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()方法以使其不是通用的,因此您可以使用object.Equals()来比较属性值为条件值。您似乎正在跳过大量的使用泛型的箍,而没有获得泛型的编译时好处。

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

+0

感谢您的回复彼得。 1.抛开问题的写法,我返回'IList ',因为在我的逻辑中,我返回的内容并不重要,甚至可能只返回'IList'或'IEnumerable',但这不是重点。 2.感谢“动态”提示。 3.我的数组输入错误。 4.我需要'Filter'扩展名的泛型能够构造所需的表达式并且无需反射就可以调用'Where'扩展方法。另外我已经提到''Cast '抛出运行时异常。 – Ayman

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

相关问题