【问题标题】:Generic method to filter a list object过滤列表对象的通用方法
【发布时间】:2013-12-21 14:30:41
【问题描述】:

我正在尝试创建一个采用三个参数的通用方法。 1) 列表集合 2) 字符串属性名 3) 字符串过滤字符串

思路是我们传递一个Objects的集合,对象的属性名 和过滤条件,它返回属性包含的对象列表 过滤器字符串。

此外,PropertyName 是可选的,因此如果未提供,我想返回 在任何属性中包含 FilterString 的所有对象。

对此的任何指示都会非常有帮助。

我正在尝试使用这样的方法签名: public static List FilterList(List collection, String FilterString, String Property = "")

这样我可以从任何地方调用这个方法并将任何列表传递给它,它会返回一个过滤列表。

【问题讨论】:

  • 你需要反射和/或表达式树。
  • 你必须使用一些反射。尝试并在遇到具体问题时回来!
  • 为什么不直接使用 Linq?
  • 忘记基于字符串的东西,使用 LINQ。

标签: c# generics filter


【解决方案1】:

你可以使用 LINQ 做你想做的事,

var collection = ...
var filteredCollection = 
    collection.Where(item => item.Property == "something").ToList();

否则,您可以尝试反射,

public List<T> Filter<T>(
    List<T> collection, 
    string property, 
    string filterValue)
{
    var filteredCollection = new List<T>();
    foreach (var item in collection)
    {
         // To check multiple properties use,
         // item.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)

         var propertyInfo = 
             item.GetType()
                 .GetProperty(property, BindingFlags.Public | BindingFlags.Instance);
         if (propertyInfo == null)
             throw new NotSupportedException("property given does not exists");             

         var propertyValue = propertyInfo.GetValue(item, null);
         if (propertyValue == filterValue)
             filteredCollection.Add(item);       
    }

    return filteredCollection;
}

此解决方案的问题在于,更改属性名称或拼写错误会导致运行时错误,而不是使用硬类型名称的实际属性表达式时的编译错误。

另外,请注意,根据绑定标志,这仅适用于publicnon-static 属性。您可以通过传递不同的flags 来修改此类行为。

【讨论】:

  • 感谢您的回答,但我怎样才能使它成为一个可以接受任何 List 的方法,而不是 List 该方法看起来像这样; public static List FilterList(List collection, String FilterString, String Property = "")
  • 您可以直接将SomeObject 更改为T,并使该方法具有通用性。我有机会给你一个线索。
  • @user3067743 可以借助 Dynamic LINQ,我也提供了答案。
  • 这是只允许 string 属性的正确方法吗? if (propertyInfo == null || propertyInfo.PropertyType != typeof(string)) throw new NotSupportedException("Unsupported property.");
【解决方案2】:

您可以为此使用反射和动态表达式的组合。我整理了一个样本,乍一看可能看起来有点长。但是,它符合您的要求并通过

解决这些问题
  • 使用反射查找字符串类型的属性并匹配属性名称 - 如果提供的话。
  • 创建一个调用字符串的表达式。包含已识别的所有属性。如果已识别多个属性,则对 string.Contains 的调用由 Or 表达式组合。这个过滤器表达式被编译并作为参数传递给 Where 扩展方法。使用表达式过滤提供的列表。

按照此link 运行示例。

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;

public class Test
{
    public static IEnumerable<T> SelectItems<T>(IEnumerable<T> items, string propName, string value)
    {
        IEnumerable<PropertyInfo> props;
        if (!string.IsNullOrEmpty(propName))
            props = new PropertyInfo[] { typeof(T).GetProperty(propName) };
        else
            props = typeof(T).GetProperties();
        props = props.Where(x => x != null && x.PropertyType == typeof(string));
        Expression lastExpr = null;
        ParameterExpression paramExpr = Expression.Parameter(typeof(T), "x");
        ConstantExpression valueExpr = Expression.Constant(value);
        foreach(var prop in props)
        {
            var propExpr = GetPropertyExpression(prop, paramExpr, valueExpr);
            if (lastExpr == null)
                lastExpr = propExpr;
            else
                lastExpr = Expression.MakeBinary(ExpressionType.Or, lastExpr, propExpr);
        }
        if (lastExpr == null)
            return new T[] {};
        var filterExpr = Expression.Lambda(lastExpr, paramExpr);
        return items.Where<T>((Func<T, bool>) filterExpr.Compile());
    }

    private static Expression GetPropertyExpression(PropertyInfo prop, ParameterExpression paramExpr, ConstantExpression valueExpr)
    {
        var memberAcc = Expression.MakeMemberAccess(paramExpr, prop);
        var containsMember = typeof(string).GetMethod("Contains");
        return Expression.Call(memberAcc, containsMember, valueExpr);
    }

    class TestClass
    {
        public string SomeProp { get; set; }
        public string SomeOtherProp { get; set; }
    }

    public static void Main()
    {
        var data = new TestClass[] {
            new TestClass() { SomeProp = "AAA", SomeOtherProp = "BBB" }, 
            new TestClass() { SomeProp = "BBB", SomeOtherProp = "CCC" }, 
            new TestClass() { SomeProp = "CCC", SomeOtherProp = "AAA" }, 
        };
        var result = SelectItems(data, "", "A");
        foreach(var item in result)
            Console.WriteLine(item.SomeProp);
    }
}

与完全基于反射的方法相比,这种方法只组装一次过滤器表达式并对其进行编译,因此我期望性能有(小的)改进。

【讨论】:

  • 我必须说,这非常简洁明了。谢谢你。由于我的菜鸟等级,我不能将其标记为有帮助,但我发现它很有用,而且我相信将来也会有很多其他的东西。谢谢大佬...
  • 哇,这太棒了,我不知道 c# 真的可以做到这一点。很棒的编码谢谢!
  • @harris 不客气。很高兴它有帮助:-)
【解决方案3】:

你应该使用Dynamic LINQ,例如,给定SomeClass

public class SomeClass 
{
    public int SomeField { get; set; }
}
List<SomeClass> list = new List<SomeClass>() { new SomeClass() { SomeField = 2 } };

然后:

var temp = list.AsQueryable().Where("SomeField == 1").Select("it");
var result= temp .Cast<SomeClass>().ToList();

所以你的函数会更简单,将属性名称和过滤器合并到一个参数中:

public List<T> Filter<T>(List<T> list, string filter)
{
    var temp = list.AsQueryable().Where(filter).Select("it");
    return temp.Cast<T>().ToList();
}

您可以提供不同的过滤器,例如"SomeField &gt; 4 &amp;&amp; SomeField &lt; 10" 等。

【讨论】:

  • 我也尝试了您的解决方案,它的工作原理与宣传的一样。看不出性能上有什么不同。谢谢康拉德。
【解决方案4】:

使用 Markus 解决方案时,它仅在所有 String 属性不为空时才有效。 为确保您可以这样做:

class TestClass
{
    private string _someProp { get; set; }
    public string SomeProp { 
        get
        {
            if(string.IsNullOrEmpty(_someProp)
            {
                _someProp = "";
            }
            return _someProp;
        }
        set
        {
            _someProp = value;
        }
    }
    private string _someOtherProp { get; set; }
    public string SomeOtherProp { 
        get
        {
            if(string.IsNullOrEmpty(_someOtherProp)
            {
                _someOtherProp = "";
            }
            return _someOtherProp;
        }
        set
        {
            _someOtherProp = value;
        }
    }
}

由于我的代表低于 50,我无法发表评论;)

【讨论】:

    猜你喜欢
    • 2017-06-14
    • 2015-03-24
    • 1970-01-01
    • 2021-09-26
    • 1970-01-01
    • 2016-03-02
    • 1970-01-01
    • 2018-01-17
    • 2022-01-16
    相关资源
    最近更新 更多