it-swarm.com.de

Sortieren einer Liste mit Lambda / Linq nach Objekten

Ich habe den Namen der "sort by property" in einer Zeichenfolge. Ich muss Lambda/Linq verwenden, um die Liste der Objekte zu sortieren.

Ex:

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. Anstatt eine Reihe von ifs zum Überprüfen des Feldnamens (sortBy) zu verwenden, gibt es eine sauberere Methode zum Sortieren
  2. Kennt sort den Datentyp?
261
DotnetDude

Dies kann so gemacht werden

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

Das .NET Framework wirft das Lambda (emp1,emp2)=>int Als ein Comparer<Employee>.

Dies hat den Vorteil einer starken Typisierung.

345
gls123

Eine Sache, die Sie tun können, ist, Sort zu ändern, damit Lambdas besser genutzt werden.

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

Jetzt können Sie das Feld angeben, nach dem sortiert werden soll, wenn die Sort -Methode aufgerufen wird.

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

Sie können Reflection verwenden, um den Wert der Eigenschaft abzurufen.

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

Wobei TypeHelper eine statische Methode hat wie:

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

Möglicherweise möchten Sie sich auch Dynamic LINQ aus der VS2008-Beispielbibliothek ansehen. Sie können die IEnumerable-Erweiterung verwenden, um die Liste als IQueryable umzuwandeln, und dann die Dynamic Link OrderBy-Erweiterung verwenden.

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

So habe ich mein Problem gelöst:

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

Das Bilden der Reihenfolge durch Ausdruck kann gelesen werden hier

Schamlos von der Seite im Link gestohlen:

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

Sie könnten Reflektion verwenden, um auf die Eigenschaft zuzugreifen.

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

Notizen

  1. Warum übergeben Sie die Liste als Referenz?
  2. Sie sollten eine Aufzählung für die Sortierrichtung verwenden.
  3. Sie könnten eine viel sauberere Lösung erhalten, wenn Sie einen Lambda-Ausdruck übergeben würden, der die zu sortierende Eigenschaft anstelle des Eigenschaftsnamens als Zeichenfolge angibt.
  4. In meiner Beispielliste verursacht == null eine NullReferenceException, Sie sollten diesen Fall abfangen.
8

Sort verwendet die IComparable-Schnittstelle, wenn der Typ sie implementiert. Und Sie können das Wenn vermeiden, indem Sie einen benutzerdefinierten IComparer implementieren:

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

und dann

list.Sort(new EmpComp(sortBy));
6
Serguei

Antwort für 1.:

Sie sollten in der Lage sein, manuell einen Ausdrucksbaum zu erstellen, der unter Verwendung des Namens als Zeichenfolge an OrderBy übergeben werden kann. Oder Sie könnten Reflexion verwenden, wie in einer anderen Antwort vorgeschlagen, die möglicherweise weniger Arbeit ist.

Bearbeiten : Hier ist ein funktionierendes Beispiel für die manuelle Erstellung eines Ausdrucksbaums. (Sortierung nach X.Value, wenn nur der Name "Value" der Eigenschaft bekannt ist). Sie könnten (sollten) eine generische Methode dafür erstellen.

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

Um einen Ausdrucksbaum zu erstellen, müssen Sie jedoch die beteiligten Typen kennen. Dies könnte in Ihrem Nutzungsszenario ein Problem sein oder auch nicht. Wenn Sie nicht wissen, nach welchem ​​Typ Sie sortieren sollen, ist die Verwendung der Reflexion wahrscheinlich einfacher.

Antwort für 2.:

Ja, da Comparer <T> .Default für den Vergleich verwendet wird, wenn Sie den Comparer nicht explizit definieren.

5
driis

Die von Rashack bereitgestellte Lösung funktioniert leider nicht für Werttypen (int, enums, etc.).

Damit es mit jeder Art von Immobilie funktioniert, habe ich folgende Lösung gefunden:

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

}

Eine andere, diesmal für jeden 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)));
    }

}

Sie können mehrere Sortierkriterien wie folgt übergeben:

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