【问题标题】:Sort a List<T> using query expressions使用查询表达式对 List<T> 进行排序
【发布时间】:2010-10-16 07:18:48
【问题描述】:

我在使用 Linq 订购这样的结构时遇到问题:

public class Person
{
    public int ID { get; set; }
    public List<PersonAttribute> Attributes { get; set; }
}

public class PersonAttribute
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Value { get; set; }
}

一个人可能会这样:

PersonAttribute Age = new PersonAttribute { ID = 8, Name = "Age", Value = "32" };
PersonAttribute FirstName = new PersonAttribute { ID = 9, Name = "FirstName", Value = "Rebecca" };
PersonAttribute LastName = new PersonAttribute { ID = 10, Name = "LastName", Value = "Johnson" };
PersonAttribute Gender = new PersonAttribute { ID = 11, Name = "Gender", Value = "Female" };

我想使用 LINQ 投影按我选择的人员属性升序对人员列表进行排序,例如,按年龄排序,或按名字排序。

我正在尝试类似的东西

string mySortAttribute = "Age"
PersonList.OrderBy(p => p.PersonAttribute.Find(s => s.Name == mySortAttribute).Value);

但是语法让我失望了。有什么线索吗?

【问题讨论】:

    标签: c# linq sorting expression


    【解决方案1】:

    OrderBy 是一个生成新序列的 LINQ 扩展。要订购现有序列,您需要添加一两个扩展方法...然后您可以使用:

    PersonList.Sort(p => p.Attributes.Find(
      s => s.Name == mySortAttribute).Value);
    
    public static class ListExtensions {
      public static void Sort<TSource, TValue>(
        this List<TSource> source,
        Func<TSource, TValue> selector)
      {
        var comparer = Comparer<TValue>.Default;
        source.Sort((x, y) => comparer.Compare(selector(x), selector(y)));
      }
      public  static void SortDescending<TSource, TValue>(
        this List<TSource> source,
        Func<TSource, TValue> selector)
      {
        var comparer = Comparer<TValue>.Default;
        source.Sort((x, y) => comparer.Compare(selector(y), selector(x)));
      }
    }
    

    【讨论】:

      【解决方案2】:

      我知道这是一篇旧帖子,但我想我会发布一个我不久前找到的比较器,以防其他人需要它。

      public class GenericComparer<T> : IComparer<T>
       {
           public string SortExpression { get; set; }
           public int SortDirection { get; set; } // 0:Ascending, 1:Descending
      
           public GenericComparer(string sortExpression, int sortDirection)
           {
               this.SortExpression = sortExpression;
               this.SortDirection = sortDirection;
           }
           public GenericComparer() { }
      
           #region IComparer<T> Members
           public int Compare(T x, T y)
           {
               PropertyInfo propertyInfo = typeof(T).GetProperty(SortExpression);
               IComparable obj1 = (IComparable)propertyInfo.GetValue(x, null);
               IComparable obj2 = (IComparable)propertyInfo.GetValue(y, null);
      
               if (SortDirection == 0)
               {
                   return obj1.CompareTo(obj2);
               }
               else return obj2.CompareTo(obj1);
           }
           #endregion
       }
      

      用法

      List<MyObject> objectList = GetObjects(); /* from your repository or whatever */
      objectList.Sort(new GenericComparer<MyObject>("ObjectPropertyName", (int)SortDirection.Descending));
      dropdown.DataSource = objectList;
      dropdown.DataBind();
      

      您可以重载构造函数以接受 SortDirection 枚举。我没有这样做,因为该类在库中,没有对 System.Web 的引用。

      【讨论】:

        【解决方案3】:

        为什么不使用键值字典而不是 List ?我认为它会更适合,并使其他一切变得更容易。

        更新 - 像这样:

        public class Person
        {
          public Dictionary<string, string> Attributes = new Dictionary<string,string>();
        }
        
        List<Person> people = new List<Person>();
        
        Person rebecca = new Person();
        rebecca.Attributes["Age"] = "32";
        rebecca.Attributes["FirstName"] = "Rebecca";
        rebecca.Attributes["LastName"] = "Johnson";
        rebecca.Attributes["Gender"] = "Female";
        people.Add(rebecca);
        
        var PeopleInAgeOrder = people.OrderBy(p => p.Attributes["Age"]);
        

        【讨论】:

        • 这没有回答他的问题。他没有问如何重组他的数据,他问的是如何对(可能是现有的,并且可能无法更改的遗留)数据结构进行排序。
        【解决方案4】:

        这是假设 Attribute 类实现了 IComparable 或者有一个很好的 ToString 函数(我希望)。

        var list = personList.OrderBy(p => p.Attributes.FirstOrDefault(a => a.Name == "Age"))
        

        否则语法会变得更加复杂:

        var list = personList
                    .OrderBy(p => 
                             p.Attributes.FirstOrDefault(a => a.Name == "Age") == null ?
                             "" : p.Attributes.First(a => a.Name == "Age").Value
                    );
        

        我还假设每个键都有一个值 - 否则你需要更智能的代码... ;-)

        【讨论】:

          【解决方案5】:

          会不会是你的语法错误?您的属性称为属性,但您在代码中使用称为 ObjectSettings 的东西?或者这是一个错字。

          如果是,那么您的代码看起来很好,除非并非所有 Person 实例都具有您尝试排序的属性,在这种情况下您会遇到异常。

          编辑: 另外,不要使用 Find,而是尝试使用 First。

          PersonList.OrderBy(p => p.Attributes.First(a => a.Name == "Age").Value)
          

          【讨论】:

          • 我已经修正了错字,发现我的问题是 IQueryable 和 List 需要演员表。感谢您的帮助。
          【解决方案6】:

          我想你会遇到一个异常,其中一个项目没有年龄属性。 我尝试了下面的代码,它运行良好 - 正如其他海报所指出的那样,我猜你的数据有点不对劲。无论如何,下面的工作正常......

              List<Person> personList = new List<Person>();
              Random rand = new Random();
          
              //generate 50 random persons
              for (int i = 0; i < 50; i++)
              {
                  Person p = new Person();
                  p.Attributes = new List<PersonAttribute>();
                  p.Attributes.Add(new PersonAttribute() { ID = 8, Name = "Age", Value = rand.Next(0, 100).ToString() });
                  p.Attributes.Add(new PersonAttribute() { ID = 10, Name = "Name", Value = rand.Next(0, 100).ToString() });
                  personList.Add(p);
              }
          
              var finalList = personList.OrderBy(c => c.Attributes.Find(a => a.Name == "Age").Value).ToList();
          

          【讨论】:

            【解决方案7】:

            您需要考虑的一些情况:

            • 由于您的属性是字符串,“30”的年龄和“3”将在“4”之前排序
            • 该属性可能不存在

            如果你创建了这个扩展方法类:

            public static class ListExtenstions
            {
                public static List<Person> OrderList(this List<Person> list, string attributeName, PersonAttribute defaultAttribute)
                {
                    return OrderList(list, attributeName, defaultAttribute, x => x);
                }
            
                public static List<Person> OrderList<T>(this List<Person> list, string attributeName, PersonAttribute defaultAttribute, Func<string, T> convertion)
                {
                    return list.OrderBy(x => convertion((x.Attributes.FirstOrDefault(y => y.Name == attributeName) ?? defaultAttribute).Value)).ToList();
            
                    // Query Syntax
                    //return
                    //    (from p in list
                    //     let attribute = p.Attributes.FirstOrDefault(a => a.Name == attributeName) ?? defaultAttribute
                    //     orderby attribute.Value
                    //     select p).ToList();
                }
            }
            

            然后您可以通过这种方式正确地对列表进行排序:

            List<Person> persons = ...
            ...
            PersonAttribute defaultAttribute = new PersonAttribute() { Value = "0" };
            var ordered = persons.OrderList("Age", defaultAttribute, x => Convert.ToInt32(x));
            

            这将给出正确的排序顺序。 如果该属性始终存在,您可以删除 defaultAttribute

            要按“名称”排序,只需使用:

            List<Person> persons = ...
            ...
            PersonAttribute defaultAttribute = new PersonAttribute() { Value = String.Empty };
            var ordered persons.OrderList("Name", defaultAttribute);
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-07-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多