【问题标题】:C# how to filter a list by different properties at runtimeC#如何在运行时按不同属性过滤列表
【发布时间】:2016-08-12 04:45:52
【问题描述】:

我对 C#/Unity 还很陌生,请见谅。

我正在编写一个过滤系统,它将在运行时根据类的任何属性过滤列表。 我计划建立某种 whereclause 来过滤列表(我知道我可以访问服务器来获取我需要的列表,但目前只想过滤我已经拥有的数据)

假设我有一个包含 4 个属性的类“MyClass”列表:“param1”..“param4”

如果我想通过 param1 和 param2 正常过滤它,我可以这样做:

List<MyClass> myList = new List<MyClass>(existinglist);
myList = myList.Where(g => g.param1 == somevalue && g.param2 == someothervalue).ToList();

如何在运行时生成相同的 where 子句?

谢谢!

【问题讨论】:

  • 什么意思?那是一个运行时间。
  • 我的意思是不必像在我的示例中那样对我想要搜索的属性进行编码,我需要根据用户想要过滤的内容构建一个动态 whereclause
  • 我不明白让用户选择过滤器如何阻止您这样做?你的代码有什么问题?

标签: c# linq list collections


【解决方案1】:

您可以使用辅助方法,该方法根据传递的过滤器列表动态构建和编译 lambda。我使用KeyValuePair&lt;string, object&gt; 来表示过滤器信息(Key 用于属性名称,Value - 好吧,用于属性值),但当然您可以将其调整为其他数据结构(如自定义类等)。 )

public static class EnumerableExtensions
{
    public static IEnumerable<T> Where<T>(this IEnumerable<T> source, IEnumerable<KeyValuePair<string, object>> filters)
    {
        if (filters == null || !filters.Any()) return source;
        var parameter = Expression.Parameter(typeof(T), "x");
        var body = filters
            .Select(filter => Expression.Equal(
                Expression.PropertyOrField(parameter, filter.Key),
                Expression.Constant(filter.Value)))
            .Aggregate(Expression.AndAlso);
        var predicate = Expression.Lambda<Func<T, bool>>(body, parameter);
        return source.Where(predicate.Compile());
    }
}

示例用法:

var filters = new List<KeyValuePair<string, object>>
{
    new KeyValuePair<string, object>("param1", somevalue),
    new KeyValuePair<string, object>("param2", someothervalue),
};
var myList = existinglist.Where(filters).ToList();

【讨论】:

  • Expression.Equal 中的Expression.PropertyOrField 不应该是filter.Value 而不是密钥吗?您正在将常量与过滤器的值进行比较,还是我遗漏了什么?
  • @BercoviciAdrian string filter.Key 是所需的属性/字段名称,而object filter.Value 是值。我们正在构建表达式param.Name == value
  • 这将作为“喜欢”查询还是仅作为完全匹配来工作?
  • @Peck_conyon 您可以将Equal 视为采用2 个参数并返回布尔值的方法。因此它可以替换为任何其他 2 参数 bool 返回表达式。
【解决方案2】:

你可以这样写一个扩展方法:

public static IEnumerable<T> Where<T>(this IEnumerable<T> source, string propName, object value)
{
    var type = typeof(T);

    var propInfo = type.GetProperty(propName,BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

    var parameterExpr = Expression.Parameter( type, "x" ); //x
    var memberAccessExpr = Expression.MakeMemberAccess( parameterExpr, propInfo ); //x.Prop

    var lambda = Expression.Lambda( Expression.Equal(memberAccessExpr, Expression.Constant(value)), 
                                    parameterExpr );         //x=>x.Prop==value

    var mi = typeof(Enumerable)
                .GetMethods()
                .Where(m => m.Name == "Where")
                .First(m => m.GetParameters().Count() == 2)
                .MakeGenericMethod(type);

    return (IEnumerable<T>)mi.Invoke(null, new object[] { source, lambda.Compile() });
}

您现在可以将其用作

var test = new[] { new { a = 1 }, new { a = 2 } }.Where("a", 1).ToList();

【讨论】:

  • 感谢 Eser,这很有意义!如果我想一次性搜索多个属性,即 param1='1' 和 params2='2' 我可以在一个 where 子句中执行此操作吗?
  • @Neil 这意味着 2 wheres
  • 等等,这有什么比只使用 LINQ 更好的地方?你是否以失去类型安全为代价获得了什么? FWICT OP 正在处理具有一组已知属性的已知类?
  • @kai 见标题how to filter a list by different properties at **runtime**
  • @Eser 过滤总是在运行时完成,它是一个方法,它不能在编译时执行。
【解决方案3】:

lambda 表达式只是函数的简写。因此,您可以用任何接受 Myclass 并返回 bool 的函数替换该 lambda。然后在该方法中编写代码以动态评估您需要的内容 - 如有必要,可能使用反射。

myList = myList.Where(myFunction).ToList();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-02
    • 2021-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-03
    相关资源
    最近更新 更多