【问题标题】:Is there a way to search a List<T> using a partially populated object?有没有办法使用部分填充的对象搜索 List<T> ?
【发布时间】:2012-09-15 06:30:03
【问题描述】:

希望能够填充对象的任何属性并在集合中搜索与给定属性匹配的对象。

class Program
{
    static List<Marble> marbles = new List<Marble> { 
        new Marble {Color = "Red", Size = 3},
        new Marble {Color = "Green", Size = 4},
        new Marble {Color = "Black", Size = 6}
    };

    static void Main()
    {
        var search1 = new Marble { Color = "Green" };
        var search2 = new Marble { Size = 6 };
        var results = SearchMarbles(search1);
    }

    public static IEnumerable<Marble> SearchMarbles(Marble search)
    {
        var results = from marble in marbles
                      //where ???
                      //Search for marbles with whatever property matches the populated properties of the parameter
                      //In this example it would return just the 'Green' marble
                      select marble;
        return results;
    }

    public class Marble
    {
        public string Color { get; set; }
        public int Size { get; set; }
    }

}

【问题讨论】:

    标签: c# linq list


    【解决方案1】:

    假设如果属性具有默认值(即Color == nullSize == 0)则未填充:

    var results = from marble in marbles
                  where (marble.Color == search.Color || search.Color == null)
                     && (marble.Size == search.Size || search.Size == 0)
                  select marble;
    

    【讨论】:

    • OP 可以做 int 吗?尺寸。可以为 null 以显式。
    • 如果 Marble 只有两个属性 - ColorSize,这将起作用。
    • @Science_Fiction 假设 Size 是一些物理测量,0 作为非填充属性是有意义的。
    • @Krizz 这是 OP 给出的Marble 的定义。但是对于每个需要测试的附加属性,where 子句需要更加复杂。
    • 我的真实项目有更多的属性,希望这可以在没有明确使用属性的情况下完成。
    【解决方案2】:

    您可以在 Marbles 类中覆盖 equals

    public override bool Equals(object obj)
        {
            var other = obj as Marble;
    
            if (null == other) return false;
    
            return other.Color == this.color && other.size == this.size; // (etc for your other porperties
        }
    

    然后你可以搜索

    return marbles.Where(m => search == m);
    

    【讨论】:

    • 如果你想在字典中使用,不要忘记覆盖 GetHashCode。
    • 喜欢这种方法,它仍然需要使用属性,但它会像我想要的那样进行查询。
    • 您可以将其与 Desperter 的反射概念结合起来,这样您就可以通过反射所有属性来实现 Equals,并且您的查询仍然是简单的等式检查。
    【解决方案3】:

    您可以像这样使用单独的过滤器类:

    class Filter
    {
        public string PropertyName { get; set; }
        public object PropertyValue { get; set; }
    
        public bool Matches(Marble m)
        {
            var T = typeof(Marble);
            var prop = T.GetProperty(PropertyName);
            var value = prop.GetValue(m);
            return value.Equals(PropertyValue);
        }
    }
    

    您可以按如下方式使用此过滤器:

    var filters = new List<Filter>();
    filters.Add(new Filter() { PropertyName = "Color", PropertyValue = "Green" });
    
    //this is essentially the content of SearchMarbles()
    var result = marbles.Where(m => filters.All(f => f.Matches(m)));
    
    foreach (var r in result)
    {
        Console.WriteLine(r.Color + ", " + r.Size);
    }
    

    您可以使用 DependencyProperties 来避免键入属性名称。

    【讨论】:

      【解决方案4】:

      使用反射,此方法将适用于所有类型,无论它们包含多少或什么类型的属性。

      将跳过任何未填写的属性(引用类型为空,值类型为默认值)。如果它找到两个不匹配的已填写属性,则返回 false。如果所有填写的属性都相等,则返回 true。

      IsPartialMatch(object m1, object m2)
      {
          PropertyInfo[] properties = m1.GetType().GetProperties();
          foreach (PropertyInfo property in properties)
          {
              object v1 = property.GetValue(m1, null);
              object v2 = property.GetValue(m2, null);
              object defaultValue = GetDefault(property.PropertyType);
      
              if (v1.Equals(defaultValue) continue;
              if (v2.Equals(defaultVAlue) continue;
              if (!v1.Equals(v2)) return false;
          }
      
          return true;
      }
      

      将其应用于您的示例

      public static IEnumerable<Marble> SearchMarbles(Marble search)
      {
          return marbles.Where(m => IsPartialMatch(m, search))
      }
      

      GetDefault() 是这篇文章中的方法,Programmatic equivalent of default(Type)

      【讨论】:

        【解决方案5】:

        诚然,这很有趣,需要花点时间。首先,您需要获取search对象的所有属性值与默认值不同的值,这种方法是通用的,使用反射:

        var properties = typeof (Marble).GetProperties().Where(p =>
                        {
                            var pType = p.PropertyType;
                            var defaultValue = pType.IsValueType 
                                    ? Activator.CreateInstance(pType) : null;
        
                            var recentValue = p.GetValue(search);
        
                            return !recentValue.Equals(defaultValue);
                        });
        

        那么就可以使用LINQAll进行过滤了:

        var results = marbles.Where(m => 
                                 properties.All(p => 
                                 typeof (Marble).GetProperty(p.Name)
                                                .GetValue(m) == p.GetValue(search)));
        

        P.s:此代码已经过测试

        【讨论】:

          【解决方案6】:

          如果您想避免针对特定属性,可以使用反射。首先定义一个返回类型默认值的函数(请参阅here 以获得简单的解决方案,here 以获得更详细的解决方案)。

          然后,您可以在Marble 类上编写一个方法,该方法将Marble 的实例作为过滤器:

          public bool MatchesSearch(Marble search) {
              var t = typeof(Marble);
              return !(
                  from prp in t.GetProperties()
                  //get the value from the search instance
                  let searchValue = prp.GetValue(search, null)
                  //check if the search value differs from the default
                  where searchValue != GetDefaultValue(prp.PropertyType) &&
                        //and if it differs from the current instance
                        searchValue != prp.GetValue(this, null)
                  select prp
              ).Any();
          }
          

          然后,SearchMarbles 变为:

          public static IEnumerable<Marble> SearchMarbles(Marble search) {
              return
                  from marble in marbles
                  where marble.MatchesSearch(search)
                  select marble;
          }
          

          【讨论】:

          • 我相信 || 应该是 &amp;&amp; 因为您想检查是否有任何属性 searchValue 不是默认值 并且 不等于Marble 的财产价值。
          【解决方案7】:

          我将提出适用于任意数量的属性和任何对象的通用解决方案。它也可以在 Linq-To-Sql 上下文中使用 - 它可以很好地转换为 sql。

          首先,从定义函数开始,该函数将测试给定值是否被视为非集合,例如:

          static public bool IsDefault(object o)
          {
              return o == null || o.GetType().IsValueType && Activator.CreateInstance(o.GetType()).Equals(o);
          }
          

          然后,我们将有一个函数构造一个 Lambda 表达式,并针对 search 对象中所有设置属性的值进行测试:

          static public Expression<Func<T, bool>> GetComparison<T>(T search)
          {
              var param = Expression.Parameter(typeof(T), "t");
          
              var props = from p in typeof(T).GetProperties()
                          where p.CanRead && !IsDefault(p.GetValue(search, null))
                          select Expression.Equal(
                              Expression.Property(param, p.Name),
                              Expression.Constant(p.GetValue(search, null))
                          );
          
              var expr = props.Aggregate((a, b) => Expression.AndAlso(a, b));
              var lambda = Expression.Lambda<Func<T, bool>>(expr, param);         
              return lambda;
          } 
          

          我们可以在任何IQueryable上使用它:

          public static IEnumerable<Marble> SearchMarbles (Marble search)
          {
              var results = marbles.AsQueryable().Where(GetComparison(search));
              return results.AsEnumerable();
          }   
          

          【讨论】:

          • @fyrplace 很高兴听到这个消息。 :) 如果您也对我的答案投票,我将不胜感激。 ;)
          猜你喜欢
          • 1970-01-01
          • 2011-04-03
          • 1970-01-01
          • 2012-06-05
          • 1970-01-01
          • 1970-01-01
          • 2019-10-24
          • 1970-01-01
          • 2018-09-11
          相关资源
          最近更新 更多