[C#] فرز قائمة باستخدام Lambda / Linq إلى الكائنات



Answers

شيء واحد يمكنك القيام به هو تغيير Sort بحيث يستفيد بشكل أفضل من lambdas.

public enum SortDirection { Ascending, Descending }
public void Sort<TKey>(ref List<Employee> list,
                       Func<Employee, TKey> sorter, SortDirection direction)
{
  if (direction == SortDirection.Ascending)
    list = list.OrderBy(sorter);
  else
    list = list.OrderByDescending(sorter);
}

يمكنك الآن تحديد الحقل للفرز عند استدعاء طريقة Sort .

Sort(ref employees, e => e.DOB, SortDirection.Descending);
Question

لدي اسم "الفرز حسب الخاصية" في سلسلة. سوف أحتاج إلى استخدام Lambda / Linq لفرز قائمة الكائنات.

مثلا:

public class Employee
{
  public string FirstName {set; get;}
  public string LastName {set; get;}
  public DateTime DOB {set; get;}
}


public void Sort(ref List<Employee> list, string sortBy, string sortDirection)
{
  //Example data:
  //sortBy = "FirstName"
  //sortDirection = "ASC" or "DESC"

  if (sortBy == "FirstName")
  {
    list = list.OrderBy(x => x.FirstName).toList();    
  }

}
  1. بدلا من استخدام مجموعة من ifs للتحقق من fieldname (sortBy) ، هل هناك طريقة أنظف للقيام الفرز
  2. هو نوع من علم نوع البيانات؟



هكذا حللت مشكلتي:

List<User> list = GetAllUsers();  //Private Method

if (!sortAscending)
{
    list = list
           .OrderBy(r => r.GetType().GetProperty(sortBy).GetValue(r,null))
           .ToList();
}
else
{
    list = list
           .OrderByDescending(r => r.GetType().GetProperty(sortBy).GetValue(r,null))
           .ToList();
}



يمكنك استخدام الانعكاس للوصول إلى الخاصية.

public List<Employee> Sort(List<Employee> list, String sortBy, String sortDirection)
{
   PropertyInfo property = list.GetType().GetGenericArguments()[0].
                                GetType().GetProperty(sortBy);

   if (sortDirection == "ASC")
   {
      return list.OrderBy(e => property.GetValue(e, null));
   }
   if (sortDirection == "DESC")
   {
      return list.OrderByDescending(e => property.GetValue(e, null));
   }
   else
   {
      throw new ArgumentOutOfRangeException();
   }
}

ملاحظات

  1. لماذا تجتاز القائمة بالإشارة؟
  2. يجب عليك استخدام تعداد لاتجاه الفرز.
  3. يمكنك الحصول على حل أنظف بكثير إذا قمت بتمرير تعبير lambda الذي يحدد الخاصية للفرز بدلاً من اسم الخاصية كسلسلة.
  4. في قائمة المثال الخاصة بي == null سيؤدي إلى NullReferenceException ، يجب عليك التقاط هذه الحالة.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;

public static class EnumerableHelper
{

    static MethodInfo orderBy = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderBy" && x.GetParameters().Length == 2).First();

    public static IEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, string propertyName)
    {
        var pi = typeof(TSource).GetProperty(propertyName, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
        var selectorParam = Expression.Parameter(typeof(TSource), "keySelector");
        var sourceParam = Expression.Parameter(typeof(IEnumerable<TSource>), "source");
        return 
            Expression.Lambda<Func<IEnumerable<TSource>, IOrderedEnumerable<TSource>>>
            (
                Expression.Call
                (
                    orderBy.MakeGenericMethod(typeof(TSource), pi.PropertyType), 
                    sourceParam, 
                    Expression.Lambda
                    (
                        typeof(Func<,>).MakeGenericType(typeof(TSource), pi.PropertyType), 
                        Expression.Property(selectorParam, pi), 
                        selectorParam
                    )
                ), 
                sourceParam
            )
            .Compile()(source);
    }

    public static IEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, string propertyName, bool ascending)
    {
        return ascending ? source.OrderBy(propertyName) : source.OrderBy(propertyName).Reverse();
    }

}

واحد آخر ، هذه المرة لأي IQueryable:

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

public static class IQueryableHelper
{

    static MethodInfo orderBy = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderBy" && x.GetParameters().Length == 2).First();
    static MethodInfo orderByDescending = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderByDescending" && x.GetParameters().Length == 2).First();

    public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, params string[] sortDescriptors)
    {
        return sortDescriptors.Length > 0 ? source.OrderBy(sortDescriptors, 0) : source;
    }

    static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string[] sortDescriptors, int index)
    {
        if (index < sortDescriptors.Length - 1) source = source.OrderBy(sortDescriptors, index + 1);
        string[] splitted = sortDescriptors[index].Split(' ');
        var pi = typeof(TSource).GetProperty(splitted[0], BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.IgnoreCase);
        var selectorParam = Expression.Parameter(typeof(TSource), "keySelector");
        return source.Provider.CreateQuery<TSource>(Expression.Call((splitted.Length > 1 && string.Compare(splitted[1], "desc", StringComparison.Ordinal) == 0 ? orderByDescending : orderBy).MakeGenericMethod(typeof(TSource), pi.PropertyType), source.Expression, Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(TSource), pi.PropertyType), Expression.Property(selectorParam, pi), selectorParam)));
    }

}

يمكنك تمرير معايير فرز متعددة ، مثل هذا:

var q = dc.Felhasznalos.OrderBy(new string[] { "Email", "FelhasznaloID desc" });



الإجابة عن 1:

يجب أن تكون قادرًا على إنشاء شجرة تعبير يدويًا يمكن تمريرها إلى OrderBy باستخدام الاسم كسلسلة. أو يمكنك استخدام التأمل كما هو مقترح في إجابة أخرى ، والتي قد تكون أقل من العمل.

تحرير : هذا مثال عملي لبناء شجرة تعبير يدويًا. (الفرز على X.Value ، عند معرفة اسم "قيمة" الخاصية فقط). يمكنك (يجب) بناء طريقة عامة للقيام بذلك.

using System;
using System.Linq;
using System.Linq.Expressions;

class Program
{
    private static readonly Random rand = new Random();
    static void Main(string[] args)
    {
        var randX = from n in Enumerable.Range(0, 100)
                    select new X { Value = rand.Next(1000) };

        ParameterExpression pe = Expression.Parameter(typeof(X), "value");
        var expression = Expression.Property(pe, "Value");
        var exp = Expression.Lambda<Func<X, int>>(expression, pe).Compile();

        foreach (var n in randX.OrderBy(exp))
            Console.WriteLine(n.Value);
    }

    public class X
    {
        public int Value { get; set; }
    }
}

بناء شجرة التعبير يتطلب منك معرفة أنواع الجسيمات. قد يكون هذا أو لا يكون مشكلة في سيناريو الاستخدام الخاص بك. إذا كنت لا تعرف نوع ما يجب أن تقوم بالفرز ، فسيكون من الأسهل استخدام التفكير.

الإجابة عن 2:

نعم ، حيث سيتم استخدام المقارنة <T> .Default للمقارنة ، إذا لم تقم بتعريف المقارنة بشكل صريح.






Links