如何通过对象中的属性对List<T>进行排序

1621
我有一个名为Order的类,它具有诸如OrderIdOrderDateQuantityTotal等属性。我有一个这个Order类的列表:
List<Order> objListOrder = new List<Order>();
GetOrderList(objListOrder); // fill list of orders

我想根据Order对象的一个属性对列表进行排序;例如,根据OrderDateOrderID进行排序。
在C#中,我该如何实现这个功能?
24个回答

2269

我能想到的最简单的方法是使用Linq:

List<Order> SortedList = objListOrder.OrderBy(o=>o.OrderDate).ToList();

37
如何按降序排序这个? - Cute Bear
278
@BonusKun List<Order> SortedList = objListOrder. OrderByDescending (o=>o.OrderDate).ToList(); (注:此为计算机程序语言,无法进行翻译。) - javajavajavajavajava
97
注意,这会创建一个全新的列表,其中包含所有项目,这可能会导致性能方面的问题。 - staafl
20
对于这样的努力,@staafl将listWithObjects = listWithObjects.OrderByDescending(o => o.Status).ToList();是否足够? - Andrew Grinder
36
据我所知,我们正在对对象引用列表进行排序,而不是复制对象本身。虽然这会使引用列表使用的内存翻倍,但并不像实际复制所有对象本身那样糟糕,因此在大多数情况下,除非处理的数据集非常巨大并且已经存在内存中的问题,否则应该足够满足需求。 - Lazarus
显示剩余4条评论

1138

如果您需要在原地对列表进行排序,则可以使用Sort方法,并传递一个Comparison<T>委托:

objListOrder.Sort((x, y) => x.OrderDate.CompareTo(y.OrderDate));

如果你更喜欢创建一个新的、排序后的序列,而不是在原地进行排序,那么你可以使用 LINQ 的 OrderBy 方法,如其他答案中所提到的。


53
是的,这个答案是“正确”的,并且应该比创建一个新的IEnumerable然后将其转换为一个新的列表更加高效。 - Jonathan Wood
83
当然,如果你需要降序排列,请将箭头 => 右侧的 xy 交换位置。 - Jeppe Stig Nielsen
23
真正的答案是能够就地排序列表的方法。 - Mitchell Currie
4
这是更好的选择,无论您使用的内存量是否成问题,这个解决方案都避免了不必要的内存分配,而这是非常昂贵的。从编码角度来看,这个选项同样简单,并且快了一个数量级。 - Cdaragorn
16
针对 Nullable<>(以 DateTime? 为例),可以使用 .Sort((x, y) => Nullable.Compare(x.OrderDate, y.OrderDate)) 进行排序,这个方法将把 null 视为在所有非空值之前。与之等价的是 .Sort((x, y) => Comparer<DateTime?>.Default.Compare(x.OrderDate, y.OrderDate) - Jeppe Stig Nielsen
显示剩余6条评论

245

如果在 .Net2.0 上不使用 LINQ,可以这样做:

List<Order> objListOrder = GetOrderList();
objListOrder.Sort(
    delegate(Order p1, Order p2)
    {
        return p1.OrderDate.CompareTo(p2.OrderDate);
    }
);

如果你使用的是 .Net3.0,那么 LukeH 的 answer 就是你需要的。

要按多个属性排序,你仍然可以在委托中完成。例如:

orderList.Sort(
    delegate(Order p1, Order p2)
    {
        int compareDate = p1.Date.CompareTo(p2.Date);
        if (compareDate == 0)
        {
            return p2.OrderID.CompareTo(p1.OrderID);
        }
        return compareDate;
    }
);

这将为您提供具有降序orderId的升序日期。但是,我不建议使用代理,因为这意味着很多地方都没有代码重用。您应该实现一个IComparer并将其传递给您的Sort方法。请参见此处
public class MyOrderingClass : IComparer<Order>
{
    public int Compare(Order x, Order y)
    {
        int compareDate = x.Date.CompareTo(y.Date);
        if (compareDate == 0)
        {
            return x.OrderID.CompareTo(y.OrderID);
        }
        return compareDate;
    }
}

然后要使用这个IComparer类,只需实例化它并将其传递给您的Sort方法:

IComparer<Order> comparer = new MyOrderingClass();
orderList.Sort(comparer);

1
非常好的答案,应该是正确的,因为它保护了重新初始化原始列表(LINQ版本总是会这样做),提供更好的封装。 - Jeb
@wonton,不是的。这个想法是能够有不同的IComparer实现,从而给我们多态行为。 - radarbob

140

对列表进行排序的最简单方法是使用 OrderBy

 List<Order> objListOrder = 
    source.OrderBy(order => order.OrderDate).ToList();

如果你想按多个列排序,就像以下SQL查询:

ORDER BY OrderDate, OrderId
为了实现这一点,您可以按照以下方式使用ThenBy
  List<Order> objListOrder = 
    source.OrderBy(order => order.OrderDate).ThenBy(order => order.OrderId).ToList();

39

按照你说的,不使用 Linq 来完成它:

public class Order : IComparable
{
    public DateTime OrderDate { get; set; }
    public int OrderId { get; set; }

    public int CompareTo(object obj)
    {
        Order orderToCompare = obj as Order;
        if (orderToCompare.OrderDate < OrderDate || orderToCompare.OrderId < OrderId)
        {
            return 1;
        }
        if (orderToCompare.OrderDate > OrderDate || orderToCompare.OrderId > OrderId)
        {
            return -1;
        }

        // The orders are equivalent.
        return 0;
    }
}

然后只需在您的订单列表上调用 .sort() 方法。


4
在进行 as 转换时,必须首先测试是否为 null。这也是使用 as 的整个意义所在,因为当转换失败时,(Order)obj 会抛出异常,而 if(orderToCompare == null) return 1; 则会判断对象是否为 null 并返回 1。 - radarbob
使用接口可以使代码更易于维护,并清晰地暴露对象的功能。 - AeonOfTime
如果比尔和泰德出现了,我会请他们带我回到2014年10月16日,这样我就可以纠正上面的错误——如果转换失败,as将返回null。但至少空值测试是正确的。 - radarbob
@radarbob 是的..糟糕。:) 但是!该函数旨在由对“List<Order>”进行排序的自动使用,因此类型应保证与“as”匹配,因此2014年您可能没有写错误,而是避免了不必要的守卫语句 :) - Jimmy Hoffa
应该得到保证。这是一个有趣的观点。如果它被封装得很好,除了传递一个 List<Order> 之外不能被调用;但你和我都遇到过自我封装的程序员,他们未经言明的假设是“我正在编写这段代码,所以它不会被错误使用”。 - radarbob

34

一个传统的面向对象解决方案

首先我要向LINQ的强大致敬...现在我们把这个问题解决了

这是JimmyHoffa答案的一个变体。使用泛型,CompareTo参数变得类型安全。

public class Order : IComparable<Order> {

    public int CompareTo( Order that ) {
        if ( that == null ) return 1;
        if ( this.OrderDate > that.OrderDate) return 1;
        if ( this.OrderDate < that.OrderDate) return -1;
        return 0;
    }
}

// in the client code
// assume myOrders is a populated List<Order>
myOrders.Sort(); 

当然,这种默认的可排序性是可重复使用的。也就是说,每个客户端不必重复编写排序逻辑。交换“1”和“-1”(或逻辑运算符,由您选择)可以颠倒排序顺序。


在List中对对象进行排序的简单方法。但我不明白为什么如果(that == null),你会返回1? 简单方法用于在List中对对象进行排序。但是如果(that == null),您返回1的原因我不理解? - Loc Huynh
4
这意味着this对象大于空值。为了排序的目的,一个空的对象引用“小于”this对象。这只是我决定如何定义空值的排序方式。 - radarbob

20

// 用于网格视图的完全通用排序

public List<T> Sort_List<T>(string sortDirection, string sortExpression, List<T> data)
    {

        List<T> data_sorted = new List<T>();

        if (sortDirection == "Ascending")
        {
            data_sorted = (from n in data
                              orderby GetDynamicSortProperty(n, sortExpression) ascending
                              select n).ToList();
        }
        else if (sortDirection == "Descending")
        {
            data_sorted = (from n in data
                              orderby GetDynamicSortProperty(n, sortExpression) descending
                              select n).ToList();

        }

        return data_sorted;

    }

    public object GetDynamicSortProperty(object item, string propName)
    {
        //Use reflection to get order type
        return item.GetType().GetProperty(propName).GetValue(item, null);
    }

4
或者,你知道的,使用 data.OrderBy()。这比重新发明轮子要容易得多。 - gunr2171
4
这个想法适用于任何类型的对象,而OrderBy()只适用于强类型对象。 - roger

13

使用 LINQ

objListOrder = GetOrderList()
                   .OrderBy(o => o.OrderDate)
                   .ToList();

objListOrder = GetOrderList()
                   .OrderBy(o => o.OrderId)
                   .ToList();

10
这里是一个通用的LINQ扩展方法,它不会创建列表的额外副本:
public static void Sort<T,U>(this List<T> list, Func<T, U> expression)
    where U : IComparable<U>
{
    list.Sort((x, y) => expression.Invoke(x).CompareTo(expression.Invoke(y)));
}

使用方式:

myList.Sort(x=> x.myProperty);

我最近建立了一个额外的对象,它可以接受一个ICompare<U>接口,使您能够自定义比较。当我需要进行自然字符串排序时,这对我非常有用:

public static void Sort<T, U>(this List<T> list, Func<T, U> expression, IComparer<U> comparer)
    where U : IComparable<U>
{    
    list.Sort((x, y) => comparer.Compare(expression.Invoke(x), expression.Invoke(y)));
}

1
我已经实现了这个功能,运行良好。我添加了一个“isAscending = true”的参数。要按降序排序,只需在两个Invoke()方法中交换x和y即可。谢谢。 - Rob L
如果你只是要编译表达式,最好一开始就接受一个委托。此外,如果选择器引起副作用、计算代价高或者不确定性很大,这种方法会带来严重的问题。基于所选表达式的适当排序需要在列表中每个项目上最多调用一次选择器。 - Servy
@Servy - 这些都是有效的观点,但说实话,我不确定如何实现你的一些建议(尤其是防止选择器引起副作用...?)。我已将它们更改为委托。如果您知道如何进行这些更改,我很乐意让您编辑我的代码。 - Peter
3
@Peter,你无法防止代理引起副作用。你能做的是确保每个对象只被调用一次,这意味着计算每个对象的值,存储对象/投影值对,然后对它们进行排序。OrderBy内部已经完成了所有这些步骤。 - Servy

8

对于使用可空类型的任何人,需要使用CompareTo函数来比较Value值。

objListOrder.Sort((x, y) => x.YourNullableType.Value.CompareTo(y.YourNullableType.Value));

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