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


1247

我有一个名为类Order具有的属性,例如OrderIdOrderDateQuantity,和Total。我有这个Order班的清单:

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

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

我该如何在C#中做到这一点?


9
等待-您是否要同时按订单日期订单ID 排序?因为大多数答案似乎都假设您只是按一个或另一个排序。
GalacticCowboy

@ GalacticCowboy,@ GenericTypeTea:问题说:“我想基于Order对象的一个属性对列表进行排序”,但是我同意在其他地方还不清楚。无论哪种方式,您是否需要按一个或多个属性进行排序都没有太大的区别。
路加福音

@LukeH-重新阅读一遍,我同意。“我想根据一个属性对列表进行排序” ...和“订单日期或订单ID”。在所有答案之间,我们涵盖了所有基础:)。
djdd87 2010年

Answers:


1805

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

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

22
我该如何按降序排序。
可爱熊

229
@BonusKun List <Order> SortedList = objListOrder。OrderByDescending(o => o.OrderDate).ToList();
javajavajavajavajava

77
请注意,这会创建一个完整的新列表,其中所有项目都在内存中,这可能会在性能方面造成问题。
2013年

14
@staafl listWithObjects = listWithObjects.OrderByDescending(o => o.Status).ToList();足以满足要求吗?
Andrew Grinder 2014年

28
@staafl我们正在订购对象引用的列表,据我所知,并没有重复对象本身。尽管这确实使引用列表使用的内存增加了一倍,但还不如实际复制所有对象本身那么糟糕,因此在大多数情况下,在那些我们要处理巨大数据集并将其保存在内存中的场景之外,这已经成为问题,它应该足够了。
拉撒路

798

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

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

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


30
是的,这是“正确”的答案,比创建新的IEnumerable然后将其转换为新列表要有效得多。
乔纳森·伍德

45
当然,如果您需要降序排序,请在箭头的右侧交换xy=>
Jeppe Stig Nielsen

15
真正对清单进行排序的真正答案
Mitchell Currie

3
@PimBrouwers这是更好的选择期。即使您使用的内存量不是问题,该解决方案也可以避免不必要的内存分配,这是非常昂贵的。此选项与代码方式一样简单,并且速度快一个数量级。
克达拉贡

12
您可以使用@JonSchneider For Nullable<>(以DateTime?示例为例).Sort((x, y) => Nullable.Compare(x.OrderDate, y.OrderDate)),它将把null视为所有非null值的前面。与完全相同.Sort((x, y) => Comparer<DateTime?>.Default.Compare(x.OrderDate, y.OrderDate)
Jeppe Stig Nielsen,

219

要在.Net2.0上不使用LINQ的情况下执行此操作:

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

如果您使用的是.Net3.0,那么LukeH的答案就是您所追求的。

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

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

这会给你 提供降序 orderIds的升序日期。

但是,我不建议坚持使用委托,因为这将意味着很多地方没有代码重用。您应该实现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版本将始终这样做),从而提供更好的封装。
2014年

@wonton,不。想法是能够有不同的IComparer实现,从而为我们提供多态行为。
Radarbob 2014年

为我工作。我发布了带有排序选项的版本。
杰克·格里芬

109

订购清单的最简单方法是使用 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();

32

如您所说,无需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()


3
必须首先nullas演员表进行测试。这是的全部要点as,因为(ha,ha)(Order)obj失败时会引发异常。if(orderToCompare == null) return 1;
radarbob

+1用于使用该接口,这使代码更易于维护,并清楚地展示了对象的功能。
AeonOfTime

如果Bill和Ted出现过,我会请他们带我回到2014年10月16日,以便我可以纠正上述错误- 如果投放失败,则as返回null。但是至少空测试是正确的。
–radarbob

@radarbob是的。:)但是!该功能旨在按列表排序自动使用,List<Order>因此应确保类型与上一个列表匹配,as因此2014年您可能没有写过bug,而避免了不必要的保护声明:)
Jimmy Hoffa

应该保证 有趣的一点。如果封装得好,则只能通过List<Order>; 但你和我都已经满足了自封装的程序员谁的潜假设是:“我写这个代码,以便它不会被用于错误的”
radarbob

26

经典的面向对象解决方案

首先,我必须对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”(或您选择的逻辑运算符)将颠倒排序顺序。


对列表中的对象进行排序的简单方法。但是我不明白为什么您要返回1 if(那== null)?
Loc Huynh

4
这意味着this对象大于null。为了排序,空对象引用“小于” this对象。这就是我决定定义null排序方式的方式。
–radarbob

18

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

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()仅适用于强类型对象。
罗杰,

1
非常感谢。我认为,最简单和可用的解决方案是对网格数据进行排序!
科斯塔斯

6

这是不创建列表的额外副本的通用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)));
}

我已经实现了,效果很好。我添加了一个“ isAscending = true”参数。要按降序排序,只需在两个Invoke()方法内交换x和y即可。谢谢。
罗伯L

如果您只是要编译表达式,则最好也接受一个委托。此外,如果选择器引起副作用,计算成本高昂或不确定,则此方法会遇到重大问题。基于选定表达式的正确排序需要对列表中的每个项目最多调用一次选择器。
Servy 2014年

@Servy-这些都是有效的观点,但是老实说,我不确定如何实现您的一些建议(尤其是阻止选择器引起副作用...?)。我已将他们更改为代表。如果您知道如何进行一些更改,很高兴请您编辑代码。
彼得

3
@Peter您不能阻止代表引起副作用。你可以做的是确保他们不会被调用超过每一次的对象,这种方式计算每个对象的值,存储对象/预测值对,然后排序OrderBy内部完成所有这些操作。
Servy 2014年

void本着这种精神编写扩展方法的另一种不错的方法:static class Extensions { public static void SortBy<TSource, TKey>(this List<TSource> self, Func<TSource, TKey> keySelector) { self.SortBy(keySelector, Comparer<TKey>.Default); } public static void SortBy<TSource, TKey>(this List<TSource> self, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) { self.Sort((x, y) => comparer.Compare(keySelector(x), keySelector(y))); } }可以通过SortByDescending方法进行补充。@Servy的注释也适用于我的代码!
杰普·斯蒂格·尼尔森

4

使用LINQ

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

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

3
//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;

3

罗杰版本的改进。

GetDynamicSortProperty的问题在于,仅获取属性名称,但是如果在GridView中使用NavigationProperties会发生什么情况?由于发现null,因此它将发送异常。

例:

“ Employee.Company.Name;”将崩溃……因为仅允许“ Name”作为参数来获取其值。

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

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


    } 

3

您可以对属性选择做一些更通用的操作,但要具体针对要选择的类型(例如“订单”):

将您的函数编写为通用函数:

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

然后像这样使用它:

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

您可以更加通用,并为要订购的商品定义开放类型:

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

并以相同的方式使用它:

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

这是执行LINQ样式“ OrderBy”的愚蠢且不必要的复杂方法,但它可能为您提供了如何以通用方式实现的线索


3

请让我用一些示例代码来完成@LukeH的答案,因为我已经对其进行了测试,我认为它可能对某些用户有用:

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 ...
}



1

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

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
以设置其排序属性:

MyOrderingClass comparer = new MyOrderingClass();  
comparer.DateDescending = ...;  
comparer.OrderIdDescending = ...;  
orderList.Sort(comparer);  

1

上述答案对我来说都不是通用的,所以我做了这个:

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 =对象中的属性数


1

使用可空类型的任何人都Value必须使用CompareTo

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


0

从性能的角度来看,最好的方法是使用排序列表,以便在将数据添加到结果中时对其进行排序。其他方法至少需要对数据进行一次额外的迭代,并且大多数方法都会创建数据的副本,因此不仅性能也会受到内存使用的影响。可能不是成百上千个元素的问题,而是成千上万个元素的问题,尤其是在许多并发请求可能同时进行排序的服务中。看一下System.Collections.Generic命名空间,然后选择一个带有排序的类而不是List。

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


如何才能表现得更好?将数据插入已排序列表的次数为O(n),因此添加n个项目的次数为O(n ^ 2)。许多并发项试图添加什么?在某些情况下,添加项目时有额外的CPU周期要刻录,但这不是一个好的常规解决方案。
John La Rooy

0

假设您有以下代码,在此代码中,我们有一个Passenger类,其中包含一些我们要基于其排序的属性。

public class Passenger
{
    public string Name { get; }
    public string LastName { get; }
    public string PassportNo { get; }
    public string Nationality { get; }

    public Passenger(string name, string lastName, string passportNo, string nationality)
    {
        this.Name = name;
        this.LastName = lastName;
        this.PassportNo = passportNo;
        this.Nationality = nationality;
    }

    public static int CompareByName(Passenger passenger1, Passenger passenger2)
    {
        return String.Compare(passenger1.Name, passenger2.Name);
    }

    public static int CompareByLastName(Passenger passenger1, Passenger passenger2)
    {
        return String.Compare(passenger1.LastName, passenger2.LastName);
    }

    public static int CompareNationality(Passenger passenger1, Passenger passenger2)
    {
        return String.Compare(passenger1.Nationality, passenger2.Nationality);
    }
}

public class TestPassengerSort
{
    Passenger p1 = new Passenger("Johon", "Floid", "A123456789", "USA");
    Passenger p2 = new Passenger("Jo", "Sina", "A987463215", "UAE");
    Passenger p3 = new Passenger("Ped", "Zoola", "A987855215", "Italy");

    public void SortThem()
    {
        Passenger[] passengers = new Passenger[] { p1, p2, p3 };
        List<Passenger> passengerList = new List<Passenger> { p1, p2, p3 };

        Array.Sort(passengers, Passenger.CompareByName);
        Array.Sort(passengers, Passenger.CompareByLastName);
        Array.Sort(passengers, Passenger.CompareNationality);

        passengerList.Sort(Passenger.CompareByName);
        passengerList.Sort(Passenger.CompareByLastName);
        passengerList.Sort(Passenger.CompareNationality);

    }
}

因此,您可以使用Composition委托来实现排序结构。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.