【问题标题】:filter method with enums in c#在 C# 中使用枚举的过滤方法
【发布时间】:2013-12-26 11:26:46
【问题描述】:

我有一个人的集合,每个人都有一个名字、年龄和国家。名称是字符串,年龄是整数,国家是枚举。我想在这个集合上做一个过滤方法。它的编写方式应该让我以后可以轻松地向过滤器添加新属性,例如电子邮件地址。

GetAllPersonsFilteredBy(string name, int age, Country country)

这种方法的问题是我希望也能够仅按国家/地区过滤而忽略姓名和年龄。

我可以添加以下方法:

GetAllPersonsFilteredByCountry(Country country)

GetAllPersonsFilteredByCountryAndName(Country country, String name)

GetAllPersonsFilteredByCountryAndAge(Country country, int Age)

GetAllPersonsFilteredByName(String Name)

and so on...

但是我会有重复的代码和一堆过滤方法。 另一种选择是检查空值。如果为名称提供 null 或空字符串,则不要过滤名称。问题是您不能将 null 作为枚举的参数!所以我总是必须给出一个国家或国际,即使我只想过滤姓名和年龄而忽略国家。

第三种选择是瀑布式,我首先根据姓名过滤,然后根据年龄过滤,然后根据布尔值过滤国家/地区:

GetAllPersonsFilteredBy(string name, bool filterName, int age, bool filterAge, Country country, bool filterCountry)
{
    List<Person> allPersons = getAllPersons();

    if (filterName)
        //filter all persons out the list based on the name parameter
          ...
    if (filterAge)
        //filter all persons out the list based on the age parameter
          ...
    if( filterCountry)
        //filter all persons out of the list based on the country parameter
          ...

    return allPersons;
}

这看起来是最好的解决方案,但我不确定。也许我可以在 c# 中使用可选参数和命名参数做一些事情来制作一个健壮但灵活的过滤方法?请考虑枚举不能为空的面孔!

【问题讨论】:

  • 1.) 我没有看到枚举 2.) 尝试语言集成查询语法?您可以搜索的 LinQ 和 Lambda 表达式
  • 我在第一行说 Country 是一个枚举

标签: c# enums


【解决方案1】:

您当前的解决方案需要在每次更改人员结构时更改签名。它不是很灵活并且有很多参数,这使得它难以阅读。

尝试传递一个谓词,该谓词将包含过滤信息。通过这种方式,您可以在调用代码中定义自己的过滤器,而GetAllPersonsFilteredBy如何 过滤一无所知。

IEnumerable<Person> GetAllPersonsFilteredBy(Func<Person, bool> filter)
{
    List<Person> allPersons = getAllPersons();

    return allPersons.Where(filter);
}

现在可以

var filteredPersons = GetAllPersonsFilteredBy(p => p.Name == "John" && p.Age < 60);

甚至

var filteredPersons = GetAllPersonsFilteredBy(p => p.Name == "John" || p.Age < 60);

注意,从方法调用中我什至无法猜测参数之间的关系是&amp;&amp; 还是||。此外,您在指定 p.Age &lt; 60p.Age == 60 或任何其他谓词方面有更多选择

【讨论】:

  • 我最终使用了这个简单的查询语法,它运行顺利,谢谢
【解决方案2】:

你可以不做吗:

GetAllPersonsFilteredBy(Country? country, string name = string.Empty, int age - 1)

有一个类似于 LINQ 的语句

// select statement
where ((country != null) ? MyCountry == country) &&
     ((name != string.Empty) ? MyName == name : true) &&
     ((age != -1) ? MyAge == age : true)

在此示例中,假设 MyName 和 MyAge 是值,无论是来自数据库还是要检查的任何地方

【讨论】:

    【解决方案3】:

    您的最后一个解决方案最接近我最常见的解决方案,只需将其包装在一个类中即可:

    class PersonFilter
    {
        public string NameFilter { get; set; }
    
        public int? AgeFilter { get; set; }
    
        //...
    }
    

    等等

    然后你可以用类似这样的方式使用它:

    List<Person> GetAllPersonsFilteredBy(PersonFilter filter)
    {
        IEnumerable<Person> result = getAllPersons();
    
        if (filter.NameFilter != null)
            result = result.Where(p => p.Name == filter.NameFilter);
    
        if (filter.Age.HasValue)
            result = result.Where(p => p.Age == filter.Age.Value);
    
        return result.ToList();
    }
    

    进一步的发展当然是摆脱这群ifs。许多可能的解决方案之一是使用以下实用方法:

    public static Expression<Func<Person, bool>> BuildFilter(IFilter filter)
    {
        var param = Expression.Parameter(typeof(Person), "p");
    
        List<Expression> conditions = new List<Expression>();
        foreach (var p in filter.GetType().GetProperties())
        {
            if (p.GetValue(filter) != null)
            {
                Attribute filterAttribute
                    = p.GetCustomAttributes(
                        typeof(FilterElementAttribute),
                        false).SingleOrDefault() as Attribute;
    
                if (filterAttribute == null)
                {
                    continue; // throw internal error
                }
    
                var expressionType = ((FilterElementAttribute)filterAttribute)
                    .ExpressionType;
                conditions.Add(
                    Expression.MakeBinary(expressionType,
                                          Expression.Property(param, p.Name),
                                          Expression.Constant(p.GetValue(filter))));
            }
        }
    
        return Expression.Lambda<Func<Person, bool>>(
            conditions.Aggregate((e1, e2) => Expression.And(e1, e2)),
            param);
    }
    

    过滤器的字段按以下方式装饰:

    public class FilterElementAttribute : Attribute
    {
        public ExpressionType ExpressionType { get; private set; }
    
        public FilterElementAttribute(ExpressionType expressionType)
        {
            ExpressionType = expressionType;
        }
    }
    
    public class PersonFilter : IFilter
    {
        [FilterElement(ExpressionType.Equal)]
        public string Name { get; set; }
    
        [FilterElement(ExpressionType.GreaterThanOrEqual)]
        public int? Age { get; set; }
    
        //...
    }
    

    BuildFilter 方法当然可以使用泛型轻松扩展以处理任何实体/过滤器类。

    使用示例:

    public static void Main()
    {
        Person[] people = new[] 
            { 
                new Person() { Name = "p1", Age = 17 },
                new Person() { Name = "p2", Age = 18 } 
            };
    
        var onlyOver18 = BuildFilter(new PersonFilter() { Name = null, Age = 18 });
        Console.WriteLine(onlyOver18.ToString());
    
        foreach (var p in people.Where(onlyOver18.Compile()))
        {
            Console.WriteLine(p.Name);
        }
    
        Console.ReadKey();
    }
    

    【讨论】:

    • 你将如何摆脱 if 的?我很欣赏您的建议,因为它易于理解并且接近我的思维方式,但是当我查看其他答案时,我认为它们提供了更大的灵活性。
    • @user1884155 我添加了一种可能的解决方案。
    【解决方案4】:

    您可以使用可为空的枚举,这样您就可以指定为空。我假设Country 是枚举:

    GetAllPersonsFilteredBy(string name, int age, Country? country)
    {
         // ...
    
         if (Country.HasValue) {
             // filter here
         }
    
         // ...
    }
    

    age 也是如此,你可以使用可为空的 int 并制作方法签名:

    GetAllPersonsFilteredBy(string name, int? age, Country? country)
    

    【讨论】:

      【解决方案5】:

      对于一个简单的解决方案,我可以建议使用可为空的类型。 您可以将任何枚举或值类型(例如 int)转换为也可以为 null 的东西。 可空类型用 ? 定义到底。例如:

      int? myNullable = null; // this is completely legal.
      if(!myNullable.HasValue)
          muNullable = 3; // also completely legal.
      
      // int realValue = myNullable; // this will throw a compilation error.
      int realValue = myNullable.Value; // this is the right way to use the value
      

      还有这个可爱的语法糖:

      int realValue = myNullable ?? 5; // if myNullable is null then 5, otherwise the value of myNullable
      

      在您的代码中:

      GetAllPersonsFilteredBy(string name, bool filterName, int age, bool filterAge, Country? country, bool filterCountry)
      {
          List<Person> allPersons = getAllPersons();
      
          if (filterName != null)
              //filter all persons out the list based on the name parameter
                ...
          if (filterAge != null)
              //filter all persons out the list based on the age parameter
                ...
          if(filterCountry.HasValue)
          {
              Country countryValue = filterCountry.Value;
              //filter all persons out of the list based on the country parameter
               ...
          }
      
      return allPersons;
      

      }

      这个简单的解决方案会起作用,但我建议使用以下几条过滤器组合进行更复杂的设计:

      interface IFilter
      {
          bool TestFilter(Person person);
      }
      
      class CompositeFilter : IFilter
      {
          List<IFilter> _filters = new List<IFilter>();
      
          void AddFilter(IFilter filter)
          {
             _filters.Add(filter);
          }
      
          bool TestFilter(Person person)
          {
              foreach(IFilter filter in _filters)
              {
                  if(!filter.TestFilter(person))
                  {
                      return false;
                  }
              }
      
              return true;
          }
      }
      
      class CountryFilter : IFilter
      {
          Cuntry Country { get; set; }
      
          bool TestFilter(Person person)
          {
              return person.Country = this.Country;
          }
      }
      

      然后你这样使用它:

      CompositeFilter filter = new CompositeFilter();
      CountryFilter countryFilter = new Countryfilter();
      countryFilter.Country = Country.Jamaica;
      
      filter.AddFilter(countryFilter);
      
      if(filter.TestFilter(person))
      {
       // pass
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-29
        相关资源
        最近更新 更多