关于C#:如何按对象中的属性对列表进行排序

How to Sort a List<T> by a property in the object

我有一个名为Order的类,它具有诸如OrderIdOrderDateQuantityTotal等属性。我有一张这门课的单子:

1
2
List<Order> objListOrder = new List<Order>();
GetOrderList(objListOrder); // fill list of orders

现在,我想基于Order对象的一个属性对列表进行排序,例如,我需要按订单日期或订单ID对其进行排序。

我怎么能用C来做这个?


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

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


如果需要对列表进行适当排序,则可以使用Sort方法,传递Comparison委托:

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

如果您喜欢创建一个新的、已排序的序列,而不是就地排序,那么您可以使用Linq的OrderBy方法,如其他答案中所述。


要在不使用linq on.net2.0的情况下执行此操作:

1
2
3
4
5
6
7
List<Order> objListOrder = GetOrderList();
objListOrder.Sort(
    delegate(Order p1, Order p2)
    {
        return p1.OrderDate.CompareTo(p2.OrderDate);
    }
);

如果你在.net3.0上,那么lukeh的答案就是你想要的。

要对多个属性进行排序,您仍然可以在委托中进行排序。例如:

1
2
3
4
5
6
7
8
9
10
11
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;
    }
);

这将为您提供按降序排列的日期。

但是,我不建议粘贴代理,因为这意味着很多地方没有代码重用。您应该实现一个IComparer并将其传递给您的Sort方法。请看这里。

1
2
3
4
5
6
7
8
9
10
11
12
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类,只需实例化它并将其传递给排序方法:

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


订购列表的最简单方法是使用OrderBy

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

如果要按多个列排序,如SQL查询。

1
ORDER BY OrderDate, OrderId

要实现这一点,您可以使用ThenBy,如下所示。

1
2
  List<Order> objListOrder =
    source.OrderBy(order => order.OrderDate).ThenBy(order => order.OrderId).ToList();

在没有Linq的情况下进行,正如您所说:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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()。


经典的面向对象解决方案

首先,我必须屈从于林肯的敬畏……既然我们已经把它弄走了

吉米霍夫答案的变化。使用泛型时,CompareTo参数成为类型安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
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"(或逻辑运算符,您的选择)将颠倒排序顺序。


//用于GridView的完全通用排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);
    }


下面是一个通用的Linq扩展方法,它不会创建列表的额外副本:

1
2
3
4
5
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)));
}

使用它:

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

我最近又建立了一个接受ICompare的附加模型,这样您就可以自定义比较了。当我需要做一个自然的字符串排序时,这很有用:

1
2
3
4
5
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)));
}


使用LINQ

1
2
3
4
5
6
7
objListOrder = GetOrderList()
                   .OrderBy(o => o.OrderDate)
                   .ToList();

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

1
2
3
4
5
6
7
//Get data from database, then sort list by staff name:

List<StaffMember> staffList = staffHandler.GetStaffMembers();

var sortedList = from staffmember in staffList
                 orderby staffmember.Name ascending
                 select staffmember;

对于属性选择,您可以做一些更一般的事情,但是对于您选择的类型,在您的案例"顺序"中要具体说明:

编写一个通用函数:

1
2
3
4
5
6
public List<Order> GetOrderList<T>(IEnumerable<Order> orders, Func<Order, T> propertySelector)
        {
            return (from order in orders
                    orderby propertySelector(order)
                    select order).ToList();
        }

然后这样使用:

1
var ordersOrderedByDate = GetOrderList(orders, x => x.OrderDate);

您可以更通用,并为要订购的产品定义一个打开的类型:

1
2
3
4
5
6
public List<T> OrderBy<T,P>(IEnumerable<T> collection, Func<T,P> propertySelector)
        {
            return (from item in collection
                    orderby propertySelector(item)
                    select item).ToList();
        }

用同样的方法:

1
var ordersOrderedByDate = OrderBy(orders, x => x.OrderDate);

这是一种愚蠢的、不必要的、复杂的执行LINQ样式"orderby"的方式,但它可能会给你一个线索,告诉你如何用一般的方式实现它。


请让我用一些示例代码@lukeh完成答案,因为我已经测试了它,我相信它可能对某些人有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Order
{
    public string OrderId { get; set; }
    public DateTime OrderDate { get; set; }
    public int Quantity { get; set; }
    public int Total { get; set; }

    public Order(string orderId, DateTime orderDate, int quantity, int total)
    {
        OrderId = orderId;
        OrderDate = orderDate;
        Quantity = quantity;
        Total = total;
    }
}

public void SampleDataAndTest()
{
    List<Order> objListOrder = new List<Order>();

    objListOrder.Add(new Order("tu me paulo", Convert.ToDateTime("01/06/2016"), 1, 44));
    objListOrder.Add(new Order("ante laudabas", Convert.ToDateTime("02/05/2016"), 2, 55));
    objListOrder.Add(new Order("ad ordinem", Convert.ToDateTime("03/04/2016"), 5, 66));
    objListOrder.Add(new Order("collocationem", Convert.ToDateTime("04/03/2016"), 9, 77));
    objListOrder.Add(new Order("que rerum ac", Convert.ToDateTime("05/02/2016"), 10, 65));
    objListOrder.Add(new Order("locorum ; cuius", Convert.ToDateTime("06/01/2016"), 1, 343));


    Console.WriteLine("Sort the list by date ascending:");
    objListOrder.Sort((x, y) => x.OrderDate.CompareTo(y.OrderDate));

    foreach (Order o in objListOrder)
        Console.WriteLine("OrderId =" + o.OrderId +" OrderDate =" + o.OrderDate.ToString() +" Quantity =" + o.Quantity +" Total =" + o.Total);

    Console.WriteLine("Sort the list by date descending:");
    objListOrder.Sort((x, y) => y.OrderDate.CompareTo(x.OrderDate));
    foreach (Order o in objListOrder)
        Console.WriteLine("OrderId =" + o.OrderId +" OrderDate =" + o.OrderDate.ToString() +" Quantity =" + o.Quantity +" Total =" + o.Total);

    Console.WriteLine("Sort the list by OrderId ascending:");
    objListOrder.Sort((x, y) => x.OrderId.CompareTo(y.OrderId));
    foreach (Order o in objListOrder)
        Console.WriteLine("OrderId =" + o.OrderId +" OrderDate =" + o.OrderDate.ToString() +" Quantity =" + o.Quantity +" Total =" + o.Total);

    //etc ...
}

罗杰版本的改进。

GetDynamicSortProperty的问题在于,它只获取属性名,但如果在GridView中使用NavigationProperties,会发生什么?它将发送一个异常,因为它发现空值。

例子:

"employee.company.name;"将崩溃…因为只允许将"name"作为参数来获取其值。

这里有一个改进的版本,允许我们按导航属性排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public object GetDynamicSortProperty(object item, string propName)
    {
        try
        {                
            string[] prop = propName.Split('.');

            //Use reflection to get order type                  
            int i = 0;                    
            while (i < prop.Count())
            {
                item = item.GetType().GetProperty(prop[i]).GetValue(item, null);
                i++;
            }                    

            return item;
        }
        catch (Exception ex)
        {
            throw ex;
        }


    }

1
2
3
var obj = db.Items.Where...

var orderBYItemId = obj.OrderByDescending(c => Convert.ToInt32(c.ID));

基于GenericTypeTea的比较器:我们可以通过添加排序标志来获得更多的灵活性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyOrderingClass : IComparer<Order> {  
    public int Compare(Order x, Order y) {  
        int compareDate = x.Date.CompareTo(y.Date);  
        if (compareDate == 0) {  
            int compareOrderId = x.OrderID.CompareTo(y.OrderID);  

            if (OrderIdDescending) {  
                compareOrderId = -compareOrderId;  
            }  
            return compareOrderId;  
        }  

        if (DateDescending) {  
            compareDate = -compareDate;  
        }  
        return compareDate;  
    }  

    public bool DateDescending { get; set; }  
    public bool OrderIdDescending { get; set; }  
}

在这个场景中,您必须显式地将它实例化为MyOrderingClass(而不是IComparer)。要设置其排序属性:

1
2
3
4
MyOrderingClass comparer = new MyOrderingClass();  
comparer.DateDescending = ...;  
comparer.OrderIdDescending = ...;  
orderList.Sort(comparer);

上面的答案对我来说都不够一般,所以我做了一个:

1
2
3
4
5
6
7
8
var someUserInputStringValue ="propertyNameOfObject i.e. 'Quantity' or 'Date'";
var SortedData = DataToBeSorted
                   .OrderBy(m => m.GetType()
                                  .GetProperties()
                                  .First(n =>
                                      n.Name == someUserInputStringValue)
                   .GetValue(m, null))
                 .ToList();

不过,在海量数据集上要小心。这是一个简单的代码,但如果集合很大,并且集合的对象类型具有大量字段,则可能会给您带来麻烦。运行时间为nxm,其中:

n=集合中的元素

m=对象内的属性


任何使用可以为空类型的Value的人都需要使用CompareTo

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


利用Linq OrderBy

1
2
List<Order> objListOrder=new List<Order> ();
    objListOrder=GetOrderList().OrderBy(o=>o.orderid).ToList();

从性能角度来看,最好是使用排序列表,以便在数据添加到结果时对其进行排序。其他方法至少需要对数据进行一次额外的迭代,而且大多数方法都会创建数据的副本,因此不仅会影响性能,还会影响内存的使用。可能不是几百个元素的问题,而是数千个元素的问题,特别是在许多并发请求可能同时进行排序的服务中。查看system.collections.generic命名空间,选择一个具有排序功能的类,而不是列表。

并且尽可能避免使用反射的一般实现,这也会导致性能问题。