我正在做一个以通用方式过滤列表的项目。我正在运行时检索IEnumerable<T>
,但我不知道T
是什么。我需要将我正在检索的列表投射到 IEnumerable<T>
而不是IEnumerable
,因为我需要像 ToList
和 Where
这样的扩展方法。这是我的代码。
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'
使用 GetGenericArguments
和 MakeGenericMethod
来连接通用签名。
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();
}
假设您的propertyListInfoToUpdate
是PropertyInfo
并且属性类型为 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>
,这也将解决您的问题。