c# - كيفية فرز قائمة<T>بواسطة خاصية في الكائن




generics list sorting (17)

تحسن نسخة روجر.

المشكلة مع GetDynamicSortProperty هي أن فقط الحصول على أسماء الممتلكات ولكن ماذا يحدث إذا كان في GridView نستخدم NavigationProperties؟ سوف يرسل استثناء ، لأنه يجد فارغة.

مثال:

سيتحطم "Employee.Company.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;
        }


    } 

لدي فئة تسمى " Order الذي يحتوي على خصائص مثل OrderId و OrderDate و Quantity و Total . لدي قائمة من هذا الفصل الدراسي:

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

الآن أريد فرز القائمة بناءً على خاصية واحدة من كائن Order ، على سبيل المثال ، أحتاج إلى ترتيبها حسب تاريخ الطلب أو معرف الطلب.

كيف يمكنني القيام بذلك في C #؟


// فرز عام تمامًا للاستخدام مع عرض الشبكة

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

يمكنك القيام بشيء أكثر عمومية حول اختيار الخصائص مع تحديد النوع الذي تختاره من ، في حالتك 'أمر':

اكتب وظيفتك كعامل عام:

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" ، ولكنها قد تعطيك فكرة عن كيفية تنفيذها بطريقة عامة


أسهل طريقة يمكنني التفكير فيها هي استخدام Linq:

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

باستخدام LINQ

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

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

حل كائن كلاسيكي

أولا يجب أن أكون جياشة لذهول 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" (أو عوامل التشغيل المنطقية ، اختيارك) عكس ترتيب الفرز.


إذا كنت بحاجة إلى فرز القائمة في المكان ، فيمكنك استخدام طريقة Sort ، مع تمرير Comparison<T> مفوض:

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

إذا كنت تفضل إنشاء تسلسل جديد وفريد ​​بدلاً من الفرز في المكان ، فيمكنك استخدام أسلوب OrderBy الخاص بـ LINQ ، كما هو مذكور في الإجابات الأخرى.


من فضلكم دعوني أكمل الإجابة بـ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 ...
}

لم يكن أي من الإجابات المذكورة أعلاه عامًا بما فيه الكفاية بالنسبة لي ، لذا فقد صنعت هذا:

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 = # من العناصر في المجموعة

م = # خصائص داخل كائن


القيام بذلك دون 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;
    }
}

ثم اتصل فقط. فرز () على قائمة الطلبات الخاصة بك


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

للقيام بذلك دون LINQ على .Net2.0:

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

إذا كنت تستخدم .Net3.0 ، فإن answer 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;
    }
);

هذا من شأنه أن يمنحك مواعيد تصاعدية مع ترتيب تنازلي .

ومع ذلك ، فإنني لا أوصي التشبث المندوبين لأنه سيعني الكثير من الأماكن دون إعادة استخدام التعليمات البرمجية. يجب عليك تنفيذ IComparer وتمرير ذلك فقط من خلال أسلوب Sort الخاص بك. انظر here .

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 هذه ، قم فقط بتكوينها وتمريرها إلى طريقة الفرز:

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

إذا كنت تريد شيء من هذا القبيل

ORDER BY OrderDate, OrderId

ثم حاول مثل متابعة.

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

var obj = db.Items.Where...

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

بناء على مقارن GenericTypeTea :
يمكننا الحصول على مزيد من المرونة عن طريق إضافة علامات الفرز:

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

فيما يلي أسلوب ملحق 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)));
}

Just because I think the other answers pretty much go off on a tangent of whether a football team "is-a" List<FootballPlayer> or "has-a" List<FootballPlayer> , which really doesn't answer this question as written.

The OP chiefly asks for clarification on guidelines for inheriting from List<T> :

A guideline says that you shouldn't inherit from List<T> . لما لا؟

Because List<T> has no virtual methods. This is less of a problem in your own code, since you can usually switch out the implementation with relatively little pain - but can be a much bigger deal in a public API.

What is a public API and why should I care?

A public API is an interface you expose to 3rd party programmers. Think framework code. And recall that the guidelines being referenced are the ".NET Framework Design Guidelines" and not the ".NET Application Design Guidelines". There is a difference, and - generally speaking - public API design is a lot more strict.

If my current project does not and is not likely to ever have this public API, can I safely ignore this guideline? If I do inherit from List and it turns out I need a public API, what difficulties will I have?

Pretty much, yeah. You may want to consider the rationale behind it to see if it applies to your situation anyway, but if you're not building a public API then you don't particularly need to worry about API concerns like versioning (of which, this is a subset).

If you add a public API in the future, you will either need to abstract out your API from your implementation (by not exposing your List<T> directly) or violate the guidelines with the possible future pain that entails.

Why does it even matter? A list is a list. What could possibly change? What could I possibly want to change?

Depends on the context, but since we're using FootballTeam as an example - imagine that you can't add a FootballPlayer if it would cause the team to go over the salary cap. A possible way of adding that would be something like:

 class FootballTeam : List<FootballPlayer> {
     override void Add(FootballPlayer player) {
        if (this.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
 }

Ah...but you can't override Add because it's not virtual (for performance reasons).

If you're in an application (which, basically, means that you and all of your callers are compiled together) then you can now change to using IList<T> and fix up any compile errors:

 class FootballTeam : IList<FootballPlayer> {
     private List<FootballPlayer> Players { get; set; }

     override void Add(FootballPlayer player) {
        if (this.Players.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
     /* boiler plate for rest of IList */
 }

but, if you've publically exposed to a 3rd party you just made a breaking change that will cause compile and/or runtime errors.

TL;DR - the guidelines are for public APIs. For private APIs, do what you want.





c# generics list sorting