我正在开发一个以通用方式过滤列表的项目。我在运行时检索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'
答案 0 :(得分:1)
使用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>
。
答案 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>
,这也可以解决您的问题。