Lambda / Linq kullanarak bir listeyi nesnelere göre sıralama


276

Bir dizede "sıralama ölçütü" adı var. Nesnelerin listesini sıralamak için Lambda / Linq kullanmam gerekecek.

Ör:

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. Alan adını (sortBy) kontrol etmek için ifs bir demet kullanmak yerine, sıralama yapmanın daha temiz bir yolu var mı
  2. Veri türünün farkında mı?


SortBy == "Adı" görüyorum . OP bunun yerine .Equals () yapmak anlamına mı geliyordu ?
Pieter

3
@Pieter muhtemelen eşitliği karşılaştırmak istiyordu, ama "yapmak istediği .Equals ()" şüpheliyim. Yazım hataları genellikle işlev gören kodla sonuçlanmaz.
C.Evenhuis

1
@Pieter Sorunuz yalnızca, yanlış bir şey olduğunu düşünüyorsanız ==... ne?
Jim Balter

Yanıtlar:


367

Bu şu şekilde yapılabilir

list.Sort( (emp1,emp2)=>emp1.FirstName.CompareTo(emp2.FirstName) );

NET çerçeve lambda döküm olan (emp1,emp2)=>inta olarakComparer<Employee>.

Bunun güçlü bir şekilde yazılması avantajı vardır.


Çoğu zaman, antisimetri sağlamak için çoklu karşılaştırma kriterleri ve sonunda güvenli bir GUID karşılaştırması içeren karmaşık karşılaştırma operatörleri yazmak bana geldi. Bunun gibi karmaşık bir karşılaştırma için lambda ifadesi kullanır mısınız? Değilse, bu lambda ifade karşılaştırmalarının sadece basit vakalarla sınırlı olması gerektiği anlamına mı geliyor?
Simone

4
evet ben de böyle bir şey görmüyorum? list.Sort ((emp1, emp2) => emp1.GetType (). GetProperty (sortBy) .GetValue (emp1, null) .CompareTo (emp2.GetType (). GetProperty (sortBy) .GetValue (emp2, null))) ;
Cmt

1
ters sıralamak nasıl?
JerryGoyal

1
@JerryGoyal params takas ... emp2.FirstName.CompareTo (emp1.FirstName) vb.
Chris Hynes

3
Bunun bir işlev referansı olması nedeniyle tek bir astar olması gerekmez. Sadece yazabilirsinizlist.sort(functionDeclaredElsewhere)
Hoff

74

Yapabileceğiniz şeylerden biri Sortde lambdaları daha iyi kullanmaktır.

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

Şimdi Sortyöntemi çağırırken sıralanacak alanı belirleyebilirsiniz .

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

7
Sıralama sütunu bir dizede olduğundan, hangi işlevi geçireceğini belirlemek için yine de bir switch / if-else bloklarına ihtiyacınız olacaktır.
tvanfosson

1
Bu varsayımı yapamazsınız. Kodunu nasıl çağırdığını kim bilebilir?
Samuel

3
Soruda "mülke göre sırala" nın bir dizede olduğunu belirtti. Sadece onun sorusundan geçiyorum.
tvanfosson

6
Ben sıralama dizesi bir dize parametresi olarak geçiren bir web sayfasında bir sıralama denetimi geliyor çünkü daha olası olduğunu düşünüyorum. Zaten bu benim kullanım durumum olurdu.
tvanfosson

2
@tvanfosson - Haklısın, dizge olarak sipariş ve alan adına sahip özel bir kontrole
sahibim

55

Özelliğin değerini almak için Yansıma kullanabilirsiniz.

list = list.OrderBy( x => TypeHelper.GetPropertyValue( x, sortBy ) )
           .ToList();

TypeHelper'ın statik bir yöntemi olduğu durumlarda:

public static class TypeHelper
{
    public static object GetPropertyValue( object obj, string name )
    {
        return obj == null ? null : obj.GetType()
                                       .GetProperty( name )
                                       .GetValue( obj, null );
    }
}

Ayrıca VS2008 Samples kütüphanesinden Dynamic LINQ'a bakmak isteyebilirsiniz . Listeyi bir IQueryable olarak yayınlamak için IEnumerable uzantısını ve ardından Dinamik bağlantı OrderBy uzantısını kullanabilirsiniz.

 list = list.AsQueryable().OrderBy( sortBy + " " + sortDirection );

1
Bu sorununu çözse de, onu sıralamak için bir dize kullanmaktan uzaklaştırmak isteyebiliriz. İyi cevap hiç yok.
Samuel

Dinamik
linq'i

Elbette. IQueryable'a dönüştürebilirsiniz. Bunu düşünmedim. Cevabım güncelleniyor.
tvanfosson

@Samuel Sıralama bir rota değişkeni olarak geliyorsa, bunu sıralamanın başka bir yolu yoktur.
Chev

1
@ChuckD - Kullanmaya çalışmadan önce koleksiyonu hafızaya alın, örneğincollection.ToList().OrderBy(x => TypeHelper.GetPropertyValue( x, sortBy)).ToList();
tvanfosson

20

Sorunumu şu şekilde çözdüm:

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

16

Sırayı ifadeyle oluşturmak burada okunabilir

Utanmadan bağlantıdaki sayfadan çalındı:

// First we define the parameter that we are going to use
// in our OrderBy clause. This is the same as "(person =>"
// in the example above.
var param = Expression.Parameter(typeof(Person), "person");

// Now we'll make our lambda function that returns the
// "DateOfBirth" property by it's name.
var mySortExpression = Expression.Lambda<Func<Person, object>>(Expression.Property(param, "DateOfBirth"), param);

// Now I can sort my people list.
Person[] sortedPeople = people.OrderBy(mySortExpression).ToArray();

Bununla ilgili sorunlar var: DateTime sıralaması.
CrazyEnigma

Ayrıca kompozit sınıflar, yani Person.Employer.CompanyName ne dersiniz?
davewilliams459

Aslında aynı şeyi yapıyordum ve bu cevap çözdü.
Jason.Net

8

Özelliğe erişmek için yansıma kullanabilirsiniz.

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

notlar

  1. Listeyi neden referans olarak geçiyorsunuz?
  2. Sıralama yönü için bir numaralandırma kullanmalısınız.
  3. Özellik adı yerine dize olarak sıralanacak özelliği belirten bir lambda ifadesini iletirseniz çok daha temiz bir çözüm elde edebilirsiniz.
  4. Örnek listemde == null bir NullReferenceException özelliğine neden olacak, bu durumu yakalamalısınız.

Başka hiç kimse bunun bir dönüş türü geçersiz olduğunu ancak listeler döndürdüğünü fark etti mi?
emd

En azından hiç kimse bunu düzeltmek için bakım ve bir IDE kullanarak kod yazmadı çünkü ben fark etmedi. Bunu işaret ettiğiniz için teşekkürler.
Daniel Brückner

6

Sort, tür uygularsa IComparable arabirimini kullanır. Özel bir IComparer uygulayarak ifs'den kaçınabilirsiniz:

class EmpComp : IComparer<Employee>
{
    string fieldName;
    public EmpComp(string fieldName)
    {
        this.fieldName = fieldName;
    }

    public int Compare(Employee x, Employee y)
    {
        // compare x.fieldName and y.fieldName
    }
}

ve sonra

list.Sort(new EmpComp(sortBy));

FYI: Sort, List <T> yöntemidir ve bir Linq uzantısı değildir.
Serguei

5

Cevap 1:

Ad olarak dize olarak kullanılarak OrderBy'ye iletilebilen bir ifade ağacını el ile oluşturabilmeniz gerekir. Ya da başka bir cevapta önerildiği gibi yansımayı kullanabilirsiniz, ki bu daha az iş olabilir.

Düzenleme : İşte el ile bir ifade ağacı oluşturmak için çalışan bir örnek. (Mülkün yalnızca "Değer" adını bildiğinde X.Value üzerinde sıralama). Bunu yapmak için genel bir yöntem oluşturabilirsiniz.

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

Bununla birlikte, bir ifade ağacı oluşturmak, parçacık türlerini bilmenizi gerektirir. Kullanım senaryosunuzda bir sorun olabilir veya olmayabilir. Hangi tipte sıralamanız gerektiğini bilmiyorsanız, yansıma kullanmak muhtemelen daha kolay olacaktır.

Cevap 2:

Evet, karşılaştırıcıyı açıkça tanımlamazsanız, Karşılaştırma <T> .Default karşılaştırma için kullanılacaktır.


OrderBy'ye iletilecek bir ifade ağacı oluşturma örneğiniz var mı?
DotnetDude

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

}

Başka bir, bu sefer herhangi bir IQueryable için:

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

}

Bunun gibi birden fazla sıralama ölçütü iletebilirsiniz:

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

4

Rashack tarafından sağlanan çözüm maalesef değer türleri (int, numaralar vb.)

Herhangi bir mülk ile çalışabilmesi için bulduğum çözüm:

public static Expression<Func<T, object>> GetLambdaExpressionFor<T>(this string sortColumn)
    {
        var type = typeof(T);
        var parameterExpression = Expression.Parameter(type, "x");
        var body = Expression.PropertyOrField(parameterExpression, sortColumn);
        var convertedBody = Expression.MakeUnary(ExpressionType.Convert, body, typeof(object));

        var expression = Expression.Lambda<Func<T, object>>(convertedBody, new[] { parameterExpression });

        return expression;
    }

Bu harika ve hatta düzgün SQL'e çevrilir!
Xavier Poinas

1

@Samuel ve @bluish'in yaptıklarına ekleme. Bu, Enum'un bu durumda gereksiz olduğu için çok daha kısadır. Ayrıca Artan istenen sonuç olduğunda ek bir bonus olarak, true üçüncü parametreye varsayılan yanıt olduğundan 3 yerine yalnızca 2 parametre iletebilirsiniz.

public void Sort<TKey>(ref List<Person> list, Func<Person, TKey> sorter, bool isAscending = true)
{
    list = isAscending ? list.OrderBy(sorter) : list.OrderByDescending(sorter);
}

0

Sütun adını sıralamak ve yönü dize olarak sıralamak ve switch'i kullanmak için switch veya \ else sözdizimini kullanmak istemiyorsanız, bu örnek sizin için ilginç olabilir:

private readonly Dictionary<string, Expression<Func<IuInternetUsers, object>>> _sortColumns = 
        new Dictionary<string, Expression<Func<IuInternetUsers, object>>>()
    {
        { nameof(ContactSearchItem.Id),             c => c.Id },
        { nameof(ContactSearchItem.FirstName),      c => c.FirstName },
        { nameof(ContactSearchItem.LastName),       c => c.LastName },
        { nameof(ContactSearchItem.Organization),   c => c.Company.Company },
        { nameof(ContactSearchItem.CustomerCode),   c => c.Company.Code },
        { nameof(ContactSearchItem.Country),        c => c.CountryNavigation.Code },
        { nameof(ContactSearchItem.City),           c => c.City },
        { nameof(ContactSearchItem.ModifiedDate),   c => c.ModifiedDate },
    };

    private IQueryable<IuInternetUsers> SetUpSort(IQueryable<IuInternetUsers> contacts, string sort, string sortDir)
    {
        if (string.IsNullOrEmpty(sort))
        {
            sort = nameof(ContactSearchItem.Id);
        }

        _sortColumns.TryGetValue(sort, out var sortColumn);
        if (sortColumn == null)
        {
            sortColumn = c => c.Id;
        }

        if (string.IsNullOrEmpty(sortDir) || sortDir == SortDirections.AscendingSort)
        {
            contacts = contacts.OrderBy(sortColumn);
        }
        else
        {
            contacts = contacts.OrderByDescending(sortColumn);
        }

        return contacts;
    }

İfade> ve anahtar dizesi aracılığıyla sıralama sütunu için gerekli olan bağları bağlayan Sözlüğü kullanmaya dayalı çözüm.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.