オブジェクトへのLambda / Linqを使用したリストの並べ替え


276

文字列に「プロパティで並べ替え」の名前があります。オブジェクトのリストを並べ替えるには、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を使用してフィールド名(sortBy)をチェックする代わりに、並べ替えを行うためのより明確な方法があります。
  2. ソートはデータ型を認識していますか?


私が見SORTBY ==「姓」を。OPは代わりに.Equals()を実行することを意味しましたか?
Pieter

3
@Pieter彼はおそらく等号を比較するつもりでしたが、私は彼が「.Equals()を実行するつもりだった」ことを疑っています。タイプミスは通常、機能するコードにはなりません。
C.Evenhuis 2014

1
@ピーターあなたの質問はあなたが何かに問題があると思う場合にのみ意味があります==...何ですか?
ジムバルター2016

回答:


367

これは次のように行うことができます

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

.NETフレームワークはラムダ(emp1,emp2)=>intComparer<Employee>.

これには、強く型付けされるという利点があります。


複雑な比較演算子を作成することがよくありました。最終的には、複数の比較基準とフェイルセーフGUID比較を使用して、対称性を確保しました。そのような複雑な比較にラムダ式を使用しますか?そうでない場合、これはラムダ式の比較が単純なケースにのみ限定されるべきであることを意味しますか?
Simone

4
ええ、私はそれもこのようなものを見ませんか?list.Sort((emp1、emp2)=> emp1.GetType()。GetProperty(sortBy).GetValue(emp1、null).CompareTo(emp2.​​GetType()。GetProperty(sortBy).GetValue(emp2、null)))) ;
2014年

1
逆に並べ替える方法は?
JerryGoyal

1
@JerryGoyalはパラメータを交換します... emp2.​​FirstName.CompareTo(emp1.FirstName)など
Chris Hynes

3
関数リファレンスだからといって、1つのライナーである必要はありません。あなたはただ書くことができますlist.sort(functionDeclaredElsewhere)
Hoff

74

あなたができることの1つは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);

7
ソート列は文字列内にあるため、渡す関数を決定するためにswitch / if-elseブロックが必要です。
tvanfosson 2009

1
その仮定をすることはできません。誰が彼のコードがそれを呼び出すかを知っています。
サミュエル

3
彼は質問で、「プロパティによるソート」は文字列であると述べました。私は彼の質問に答えるだけです。
tvanfosson 2009

6
文字列パラメーターとして並べ替え列を返すWebページの並べ替えコントロールからのものであるため、より可能性が高いと思います。とにかく、それは私のユースケースでしょう。
tvanfosson 2009

2
@tvanfosson-あなたは正しい、私は文字列として順序とフィールド名を持つカスタムコントロールを持っています
DotnetDude

55

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拡張機能を使用してリストをIQueryableとしてキャストし、ダイナミックリンクOrderBy拡張機能を使用できます。

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

1
これで彼の問題は解決しますが、文字列を使用してソートしないようにすることができます。それでもなお良い答えです。
サミュエル

Linq to
Sql

承知しました。IQueryableに変換できます。それについて考えていませんでした。回答を更新しています。
tvanfosson 2009

@Samuelソートがルート変数として受信される場合、それをソートする他の方法はありません。
Chev

1
@ChuckD-使用する前にコレクションをメモリに持ち込む。例collection.ToList().OrderBy(x => TypeHelper.GetPropertyValue( x, sortBy)).ToList();
tvanfosson

20

これは私が私の問題を解決した方法です:

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

式による順序の作成については、こちらをご覧ください。

リンクのページから恥知らずに盗まれました:

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

これに関連する問題があります:DateTimeソート。
CrazyEnigma 2010

また、複合クラス、つまりPerson.Employer.CompanyNameはどうですか?
davewilliams459 '18 / 10/18

私は本質的に同じことをしていたが、この答えはそれを解決した。
Jason.Net

8

リフレクションを使用してプロパティにアクセスできます。

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. 私の例のリストでは、== nullはNullReferenceExceptionを引き起こすため、このケースをキャッチする必要があります。

他の誰かがこれが戻り型voidであることに気付いたことがありますが、リストを返しますか?
emd

IDEを使用してコードを記述しなかったため、少なくとも誰もそれを修正することに関心がなく、気づきませんでした。ご指摘いただきありがとうございます。
DanielBrückner2013年

6

型で実装されている場合、SortはIComparableインターフェイスを使用します。また、カスタムIComparerを実装することでifsを回避できます。

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

参考:ソートはList <T>のメソッドであり、Linq拡張ではありません。
セルゲイ

5

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.の回答:

はい、Comparer <T> .Defaultが比較に明示的に定義されていない場合、比較に使用されます。


OrderByに渡される式ツリーを構築する例はありますか?
DotnetDude 2009

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

}

もう1つは、今回は任意の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" });

4

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

これは素晴らしく、適切にSQLに変換されます!
ザビエルポイナス2018年

1

@Samuelと@bluishが行ったことに追加します。この場合、Enumは不要だったので、これははるかに短いです。さらに、昇順が望ましい結果である場合の追加のボーナスとして、trueが3番目のパラメーターのデフォルトの回答であるため、3ではなく2つのパラメーターのみを渡すことができます。

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

0

ソート列名とソート方向を文字列として取得し、スイッチまたは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>とそのキー文字列を介してソート列に必要な接続するディクショナリの使用に基づくソリューション。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.