使用查询表达式对List<T>进行排序

13

我在使用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);

但是语法让我失败了。有什么提示吗?

7个回答

9

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)));
  }
}

8

我知道这是一篇旧文章,但我想分享一下我之前发现的一个比较工具,以便其他人需要时可以使用。

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的引用的库中。

5
为什么不使用键值对字典代替List<PersonAttribute>?我认为这样更适合,并且可以使其他事情更容易。
更新-像这样:
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"]);

10
这并没有回答他的问题。他没有问如何重构数据,他问的是如何对一个(可能已经存在且可能无法更改的)数据结构进行排序。 - Mark T

1
这里假设 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
            );

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


0
一些需要考虑的情况:
  • 由于您的属性是字符串,因此"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);

0

可能是你的语法有误吗?你的属性被称为Attributes,但在代码中使用了ObjectSettings?还是打错字了。

如果是这样,那么你的代码看起来很好,除非并非所有Person实例都具有你尝试按其排序的Attribute,否则你将会得到一个异常。

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

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

我已经修正了打字错误,发现我的问题与IQueryable和List有关,需要进行类型转换。感谢你的帮助。 - user84426

0

我猜你遇到了一个异常,其中一个项目没有年龄属性。我尝试了下面的代码,它可以正常工作 - 我猜你的数据有点问题,正如其他帖子所指出的那样。无论如何,以下代码可以正常工作...

    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();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接