Programing

Lambda / Linq를 사용하여 객체에 대한 목록 정렬

lottogame 2020. 3. 30. 08:44
반응형

Lambda / Linq를 사용하여 객체에 대한 목록 정렬


문자열에 "속성 정렬"의 이름이 있습니다. 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. 필드 이름 (sortBy)을 확인하기 위해 많은 if를 사용하는 대신 정렬을 수행하는 더 확실한 방법이 있습니까?
  2. 정렬이 데이터 유형을 알고 있습니까?

이것은 다음과 같이 수행 할 수 있습니다

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

.NET 프레임 워크는 람다 (emp1,emp2)=>intComparer<Employee>.

이것은 강력하게 타이핑되는 장점이 있습니다.


당신이 할 수있는 한 가지는 Sort람다를 더 잘 사용하도록 변화 입니다.

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

Reflection을 사용하여 속성 값을 얻을 수 있습니다.

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

TypeHelper에는 다음과 같은 정적 메소드가 있습니다.

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

VS2008 샘플 라이브러리 에서 Dynamic LINQ를 볼 수도 있습니다 . IEnumerable 확장을 사용하여 List를 IQueryable로 캐스팅 한 다음 Dynamic link OrderBy 확장을 사용할 수 있습니다.

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

이것이 내 문제를 해결 한 방법입니다.

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

식으로 주문 작성은 여기에서 읽을 수 있습니다

링크의 페이지에서 뻔뻔스럽게 도난당했습니다.

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

리플렉션을 사용하여 속성에 액세스 할 수 있습니다.

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. 속성 이름 대신 문자열로 정렬 할 속성을 지정하는 람다 식을 전달하면 훨씬 깨끗한 솔루션을 얻을 수 있습니다.
  4. 내 예제 list == null에서 NullReferenceException이 발생 하므로이 경우를 포착해야합니다.

유형이 구현하는 경우 Sort는 IComparable 인터페이스를 사용합니다. 또한 사용자 정의 IComparer를 구현하여 if를 피할 수 있습니다.

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

그리고

list.Sort(new EmpComp(sortBy));

1에 대한 답변 ::

이름을 문자열로 사용하여 OrderBy에 전달할 수있는 표현식 트리를 수동으로 빌드 할 수 있어야합니다. 또는 다른 대답에서 제안한 것처럼 반사를 사용하면 작업이 적을 수 있습니다.

편집 : 식 트리를 수동으로 작성하는 실제 예입니다. (속성의 이름 "Value"만 알고있을 때 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가 비교에 사용됩니다.


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

Rashack이 제공하는 솔루션은 불행히도 값 유형 (int, enums 등)에는 작동하지 않습니다.

모든 유형의 속성에서 작동하려면 이것이 내가 찾은 해결책입니다.

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

@Samuel과 @bluish가 한 일에 추가. 이 경우 Enum이 필요하지 않으므로 훨씬 짧습니다. 또한 오름차순이 원하는 결과 일 때 추가 보너스로 true는 세 번째 매개 변수에 대한 기본 응답이므로 3 대신 3 개의 매개 변수 만 전달할 수 있습니다.

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

정렬 열 이름과 정렬 방향을 문자열로 가져 와서 switch 또는 if \ else 구문을 사용하여 열을 결정하지 않으려면이 예제가 흥미로울 수 있습니다.

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

Expression> 및 해당 키 문자열을 통해 정렬 열에 필요한 Dictionary를 사용하는 솔루션.

참고 URL : https://stackoverflow.com/questions/722868/sorting-a-list-using-lambda-linq-to-objects

반응형