वस्तुओं के लिए लैम्ब्डा / लाइनक का उपयोग करके एक सूची को क्रमबद्ध करना


274

मेरे पास एक स्ट्रिंग में "संपत्ति द्वारा क्रमबद्ध" का नाम है। वस्तुओं की सूची को छाँटने के लिए मुझे लैम्ब्डा / लाइनक का उपयोग करना होगा।

उदाहरण के लिए:

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. फ़ील्डनाम (सॉर्टबाय) की जांच करने के लिए आईएफएस के एक समूह का उपयोग करने के बजाय, सॉर्टिंग करने का एक क्लीनर तरीका है
  2. क्या डेटाटाइप के बारे में पता है?


मैं SortBy == "FirstName" देखता हूं । ओपी का मतलब क्या था ? इसके बजाय () ?
पीटर

3
@ पीटर वह शायद समानता की तुलना करने का मतलब था, लेकिन मुझे संदेह है कि वह "करने का मतलब है। ईक्वाल्स ()"। टाइपो आमतौर पर कोड में कार्य नहीं करता है।
C.Evenhuis

1
@ पीटर आपका सवाल केवल तभी समझ में आता है जब आपको लगता है कि कुछ गड़बड़ है ==... क्या?
जिम बेल्टर

जवाबों:


365

यह के रूप में किया जा सकता है

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

.NET फ्रेमवर्क (emp1,emp2)=>intएक के रूप में लैम्ब्डा कास्टिंग कर रहा हैComparer<Employee>.

यह दृढ़ता से टाइप किए जाने का लाभ है।


यह अक्सर मुझसे तुलनात्मक संचालकों को लिखने के लिए हुआ, जिसमें कई तुलना मापदंड और एंटीसिमेट्री सुनिश्चित करने के लिए अंत में एक असफल GUID तुलना शामिल थी। क्या आप उस तरह की जटिल तुलना के लिए लैंबडा अभिव्यक्ति का उपयोग करेंगे? यदि नहीं, तो क्या इसका मतलब यह है कि लैम्ब्डा अभिव्यक्ति की तुलना केवल साधारण मामलों तक सीमित होनी चाहिए?
सिमोन

4
हाँ, मैं इसे या तो इस तरह से कुछ नहीं देख रहा हूँ? list.Sort ((emp1, emp2) => emp1.GetType ()। GetProperty (SortBy) .GetValue (emp1, null) .CompareTo (emp2.GetType) (GetProperty (sortBy) .GetValue (emp2, null))। ;
शनि

1
रिवर्स में सॉर्ट कैसे करें?
जेरी गोयल

1
@ जेरेयल गोयल ने परमों की अदला-बदली की ... emp2.FirstName.CompareTo (emp1.FirstName) आदि
क्रिस हाइन्स

3
सिर्फ इसलिए कि यह एक फ़ंक्शन संदर्भ है, इसमें एक लाइनर होना आवश्यक नहीं है। आप बस लिख सकते हैंlist.sort(functionDeclaredElsewhere)
द हॉफ

74

एक चीज जो आप कर सकते हैं वह है बदलाव 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
चूंकि सॉर्ट कॉलम एक स्ट्रिंग में है, इसलिए आपको यह निर्धारित करने के लिए कौन से फ़ंक्शन को पास करना है, इसके लिए आपको स्विच / इफ-ब्लॉक की आवश्यकता होगी।
tvanfosson

1
आप यह अनुमान नहीं लगा सकते। कौन जानता है कि उसका कोड इसे कैसे कहता है।
शमूएल

3
उन्होंने सवाल में कहा कि "संपत्ति द्वारा क्रमबद्ध" एक स्ट्रिंग में है। मैं सिर्फ उसके सवाल से जा रहा हूं।
tvanfosson

6
मुझे लगता है कि यह अधिक संभावना है क्योंकि यह वेब पेज पर एक तरह के नियंत्रण से आ रहा है जो सॉर्ट कॉलम को स्ट्रिंग पैरामीटर के रूप में वापस करता है। वैसे भी यह मेरा उपयोग मामला होगा।
tvanfosson

2
@tvanfosson - आप सही हैं, मेरे पास एक कस्टम नियंत्रण है जिसमें एक स्ट्रिंग के रूप में ऑर्डर और फ़ील्ड का नाम है
DotnetDude

55

आप संपत्ति के मूल्य को प्राप्त करने के लिए परावर्तन का उपयोग कर सकते हैं।

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

जहां टाइप हेल्पर के पास एक स्थिर तरीका है:

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

आप VS2008 सैंपल लाइब्रेरी से डायनामिक LINQ भी देखना चाह सकते हैं । आप सूची को IQuery के रूप में कास्ट करने के लिए IEnumerable एक्सटेंशन का उपयोग कर सकते हैं और फिर डायनामिक लिंक ऑर्डरबी एक्सटेंशन का उपयोग कर सकते हैं।

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

1
हालांकि यह उसकी समस्या को हल करता है, हम इसे सॉर्ट करने के लिए स्ट्रिंग का उपयोग करने से दूर करना चाहते हैं। अच्छा जवाब कोई कम नहीं।
शमूएल

आप Linq के बिना गतिशील
लाइनक का

ज़रूर। आप इसे IQueryable में बदल सकते हैं। उस बारे में नहीं सोचा था। मेरे उत्तर को अपडेट करना।
tvanfosson

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

इससे जुड़ी समस्याएं हैं: डेटटाइम सॉर्ट।
CrazyEnigma

यह भी कि कैसे समग्र वर्गों के बारे में, यानी Person.Employer.CompanyName?
डेवविलियम्स 459

मैं अनिवार्य रूप से एक ही काम कर रहा था और इस जवाब ने इसे हल कर दिया।
जेसन। नेट

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 का कारण बनेगी, आपको इस मामले को पकड़ना चाहिए।

क्या किसी और ने कभी ध्यान दिया है कि यह एक रिटर्न प्रकार शून्य है लेकिन रिटर्न सूची?
ईएमडी

कम से कम किसी ने इसे ठीक करने की परवाह नहीं की और मैंने इस पर ध्यान नहीं दिया क्योंकि मैंने आईडीई का उपयोग करके कोड नहीं लिखा था। यह बात बताने के लिए धन्यवाद।
डैनियल ब्रुकनर

6

सॉर्ट IComparable इंटरफ़ेस का उपयोग करता है, यदि प्रकार इसे लागू करता है। और आप एक कस्टम IComparer लागू करके इफ़्स से बच सकते हैं:

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

FYI करें: सॉर्ट लिस्ट <T> का एक तरीका है और एक Linq एक्सटेंशन नहीं है।
सेर्गेई

5

1 का उत्तर:

आपको मैन्युअल रूप से एक अभिव्यक्ति ट्री बनाने में सक्षम होना चाहिए जिसे एक स्ट्रिंग के रूप में नाम का उपयोग करके ऑर्डरबाय में पारित किया जा सकता है। या आप किसी अन्य उत्तर में सुझाए गए प्रतिबिंब का उपयोग कर सकते हैं, जो कम काम हो सकता है।

संपादित करें : यहां एक अभिव्यक्ति ट्री को मैन्युअल रूप से बनाने का एक कार्यशील उदाहरण है। (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 का उपयोग तुलना के लिए किया जाएगा, यदि आप स्पष्ट रूप से तुलना करने वाले को परिभाषित नहीं करते हैं।


क्या आपके पास ऑर्डरबी में पारित होने के लिए एक अभिव्यक्ति पेड़ बनाने का एक उदाहरण है?
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();
    }

}

एक और, इस बार किसी भी 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

राशेक द्वारा प्रदान किया गया समाधान दुर्भाग्य से मूल्य प्रकार (इंट, एनम आदि) के लिए काम नहीं करता है।

किसी भी प्रकार की संपत्ति के साथ काम करने के लिए, यह मेरे द्वारा पाया गया समाधान है:

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 में अनुवादित हो जाता है!
जेवियर पोइनास

1

@Samuel और @bluish ने क्या जोड़ा। यह बहुत कम है क्योंकि एनम इस मामले में अनावश्यक था। प्लस के रूप में एक अतिरिक्त बोनस के रूप में जब आरोही वांछित परिणाम है, तो आप 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

यदि आपको कॉलम नाम और सॉर्ट दिशा स्ट्रिंग के रूप में मिलती है और आप स्विच का उपयोग नहीं करना चाहते हैं या कॉलम को निर्धारित करने के लिए सिंटैक्स, तो यह उदाहरण आपके लिए दिलचस्प हो सकता है:

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

डिक्शनरी> और उसके कुंजी स्ट्रिंग के माध्यम से सॉर्ट कॉलम के लिए आवश्यक कनेक्ट करने वाले शब्दकोश का उपयोग करने पर आधारित समाधान।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.