عبارت orderby

LINQ این امکان را فراهم کرده است که ترتیب نتایج پرس و جو را تغییر دهید. برای این کار از عبارت orderby استفاده می‌کنیم. در هنگام نوشتن این عبارت باید یک مقدار یا خاصیت را به عنوان کلید مرتب سازی مشخص کنیم. به عنوان مثال، فرض کنید آرایه‌ای از اعداد را ایجاد کرده‌ایم و قصد داریم روی این آرایه پرس و جویی را انجام دهیم و سپس نتیجه پرس و جو را از بزرگترین به کوچکترین عدد مرتب کنیم، می‌توانیم از پرس و جویی به شکل زیر استفاده کنیم :

int[] numbers = { 5, 1, 6, 2, 8, 10, 4, 3, 9, 7 }; 

var sortedNumbers = from number in numbers
                    orderby number
                    select number;

مرتب سازی نتایج پرس و جوی بالا را با استفاده از عبارت orderby انجام داده‌ایم و کلید مرتب سازی را خود عدد انتخاب کرده‌ایم. هنگامی که برای کلید مرتب سازی خود شیء را انتخاب می‌کنید رفتار پیشفرض آن شیء در نظر گرفته می‌شود. به عنوان مثال، عبارت بالا مقدار هر شیء پرس و جو شده را از لحاظ کوچکتر یا بزرگتر بودن با شیء پرس و جو شده دیگر مقایسه می‌کند.
مرتب سازی پیشفرض این عبارت از پایین‌ترین به بالاترین مقدار است. با استفاده از کلمه کلیدی ascending می‌توان صراحتاً تعیین کرد که نتیجه پرس و جو به صورت صعودی مرتب شود.

var sortedNumbers = from number in numbers
                    orderby number ascending
                    select number;

برای معکوس کردن نتیجه یعنی مرتب کردن نتایج به صورت نزولی از کلمه کلیدی descending استفاده می‌شود.

var sortedNumbers = from number in numbers
                    orderby number descending
                    select number;

در هنگام کار با نوع‌های پیچیده مانند کلاس‌ها می‌توانید از خصوصیت آنها به عنوان کلید مرتب سازی استفاده کنید. به عنوان مثال، فرض کنید مجموعه‌ای از اشیاء که همگی دارای نوع Person هستند در اختیار داریم. این نوع دارای خصوصیاتی به نام‌های FirstName ، LastName ، Age است. با استفاده از پرس و جوی زیر کلکسیون را از جوانترین به مسن‌ترین فرد مرتب می‌کنیم :

var sortedByAge = from person in people
                  orderby person.Age
                  select person;

اگر قصد دارید نتیجه عبارت بالا را از مسن‌ترین به جوانترین تغییر دهید در جلوی کلید از کلمه کلیدی descending استفاده کنید. همچنین می‌توان نتیجه را بر اساس حرف اول نام خانوادگی اشخاص مرتب کنیم.

var sortedByFirstName = from person in people
                        orderby person.LastName
                        select person;

اگر دو شخص با نام خانوادگی یکسان در مجموعه داشته باشیم مرتب سازی آنها به چه شکلی است؟ برای حل این مشکل می‌توانید چند کلید را برای مرتب سازی تعیین کنید.

var sortedByFnThenLn = from person in people
                       orderby person.LastName, person.FirstName
                       select person;

کلیدها را با استفاده از کاما از یکدیگر جدا می‌کنیم.
پرس و جو بالا نتیجه را بر ااساس نام خانوادگی اشخاص مرتب می‌کند و اگر دو شخص نام خانوادگی یکسانی داشته باشند با استفاده از خاصیت نام، آن‌ها را مرتب می‌کند. همچنین می‌توانید کلیدهای بیشتری را نیز مشخص کنید مثلاً برای زمانی که دو شخص نام یکسانی داشته باشند. به این نکته توجه کنید که کلمات کلیدی ascending و descending فقط روی یک مقدار در عبارت orderby عمل می‌کنند. به عنوان مثال، کوئری زیر را در نظر بگیرید :

var orderByResults = from person in people
                     orderby person.LastName, person.FirstName descending
                     select person;

کلمه کلیدی descending فقط بر روی مقدار خاصیت پیش از خود (در مثال بالا FirstName ) تأثیر می‌گذارد. مقدار خاصیت LastName به طور پیشفرض به صورت صعودی مرتب می‌شود. برای جلوگیری از آشفتگی توصیه می‌شود به طور صریح کلمه کلیدی ascending را بنویسید :

var orderByResults = from person in people
                     orderby person.LastName ascending, person.FirstName descending
                     select person;

برای اینکه رفتار پیشفرض یک کلاس را هنگام مرتب سازی تعیین کنید باید رابط IComparable<T> را در کلاس پیاده سازی نمایید. در مثال زیر کلاس Person رابط IComparable<T> را پیاده سازی کرده است :

   1: using System;
   2: using System.Linq;
   3: using System.Collections.Generic;
   4: 
   5: namespace OrderByClause
   6: {
   7:     class Person : IComparable<Person>
   8:     {
   9:         public string FirstName { get; set; }
  10:         public string LastName  { get; set; }
  11:         public int    Age       { get; set; }
  12:     
  13:         public Person(string f, string l, int a)
  14:         {
  15:             FirstName = f;
  16:             LastName  = l;
  17:             Age       = a;
  18:         }
  19:     
  20:         public int CompareTo(Person other)
  21:         {                                 
  22:             if (this.Age > other.Age)       
  23:                 return 1;                 
  24:             else if (this.Age < other.Age)  
  25:                 return -1;                
  26:             else                          
  27:                 return 0;                 
  28:         }                                 
  29:     }
  30: 
  31:     class Program
  32:     {
  33:         static void Main(string[] args)
  34:         {
  35:             List<Person> people = new List<Person>() 
  36:             { 
  37:                 new Person("Peter" , "Redfield", 32), 
  38:                 new Person("Marvin", "Monrow"  , 17),
  39:                 new Person("Aaron" , "Striver" , 25) 
  40:             };
  41: 
  42:             var defaultSort = from    person in people
  43:                               orderby person
  44:                               select  person;
  45: 
  46:             foreach (var person in defaultSort)
  47:             {
  48:                 Console.WriteLine(String.Format("{0} {1} {2}", person.Age, person.FirstName, person.LastName));
  49:             }
  50:         }
  51:     }
  52: }
17 Marvin Monrow
25 Aaron Striver
32 Peter Redfield

خط 7 بیانگر پیاده سازی رابط IComparable<T> است. باید نوع اشیایی که با یکدیگر مقایسه می‌شوند را به جای T قرار دهیم در مثال این نوع برابر Person است. پیاده سازی این رابط مستلزم پیاده سازی یک متد با نام CompareTo() است. این متد یک شیء به عنوان پارامتر قبول می‌کند که باید با شیء جاری مقایسه شود سپس یک عدد صحیح به عنوان نتیجه بر می‌گرداند.
در مثال بالا این متد در خطوط 20 تا 28 پیاده سازی شده است. در داخل متد، مقدار خاصیت Age شیء جاری با خاصیت مشابه شیء دیگر (که در این مثال از نوع Person است) مقایسه شده است. اگر مقدار شیء جاری از مقدار دیگری بزرگتر باشد عددی بزرگتری از صفر، اگر مساوی باشد مقدار صفر و اگر کوچکتر از صفر باشد مقداری منفی به عنوان نتیجه برگشت داده می‌شود. با توحه به اینکه این کلاس رابطه IComparable<T> را پیاده سازی کرده است می‌توانیم کوئری خود را ساده‌تر بنویسیم. چون در داخل متد CompareTo از خاصیت Age برای مقایسه استفاده کرده‌ایم اگر در داخل کوئری کلید مرتب سازی نوشته نشود به طور پیشفرض خاصیت Age به عنوان کلید مرتب سازی در نظر گرفته می‌شود.
متدهای OrderBy() و OrderByDescending() موجود در فضای نامی System.Linq معادل عبارت orderby هستند. متد OrderBy() نتیجه پرس و جو را بر اساس یک کلید به صورت صعودی و OrderByDescending نتیجه را به صورت نزولی مرتب می‌کنند. برای مشخص کردن کلید مرتب سازی از یک عبارت لامبدا استفاده می‌کنیم.

using System;
using System.Linq;
using System.Collections.Generic;

namespace OrderByClause
{
    class Person
    {
        public string FirstName { get; set; }
        public string LastName  { get; set; }
        public int    Age       { get; set; }
    
        public Person(string f, string l, int a)
        {
            FirstName = f;
            LastName  = l;
            Age       = a;
        }                               
    }

    class Program
    {
        private static List<Person> GetPersonList()
        {
            List<Person> people = new List<Person>() 
            { 
                new Person("Peter" , "Redfield", 32), 
                new Person("Marvin", "Monrow"  , 17),
                new Person("Aaron" , "Striver" , 25) 
            };

            return people;
        }

        static void Main(string[] args)
        {
            List<Person> people = GetPersonList();

            Console.WriteLine("Persons OrderBy FirstName : ");

            var orderByQuery3 = people.OrderBy(person => person.FirstName);
            foreach (var person in orderByQuery3)
            {
                Console.WriteLine(String.Format("{0}", person.FirstName));
            }

            Console.WriteLine("\nPersons OrderByDescending Age : ");

            var orderByQuery4 = people.OrderByDescending(person => person.Age);
            foreach (var person in orderByQuery4)
            {
                Console.WriteLine(String.Format("{0}", person.Age));
            }
        }
    }
}

متدهای ThenBy() و ThenByDescending()

اگر چندین کلید مرتب سازی باشید باید از متدهای ThenBy() و ThenByDescending() استفاده کنید. به عنوان مثال کوئری Linq زیر :

var orderByQuery5 = from p in people
                    orderby p.LastName, p.FirstName
                    select p;

معادل کوئری به شکل زیر است :

people.OrderBy(p => p.LastName).ThenBy(p => p.FirstName);

اگر فصد داشته باشید با استفاده از کلید دوم نتیجه را به صورت نزولی مرتب کنید باید از متد ()ThenByDescending استفاده کنید :

var orderByQuery6 = people.OrderBy(p=>p.LastName).ThenByDescending(p=>FirstName);

var orderByQuery7 = people.OrderByDescending(p=>p.LastName)
                          .ThenByDescending(p=>p.FirstName);

به این نکته توجه کنید که متدهای ThenBy() و ThenByDescending() عضوی از رابط IOrderedEnumerable<T> هستند. متد OrderBy کلکسیونی را بر می‌گرداند که این رابط را پیاده سازی می‌کند. پس قبل از فراخوانی متدهای ThenBy() و ThenByDescending() باید متد OrderBy() یا OrderByDescending() را فراخوانی کنید .

استفاده از رابط IComparer<T>

شکل دیگری از 4 متد مرتب سازی پیشین نیز وجود دارد که شی ای از نوع IComparer<T> را به عنوان آرگومان قبول می‌کنند. یک کلاس را ایجاد و رابط IComparer<TKey> را در آن پیاده سازی می‌کنیم. با این کار کلاس ایجاد شده مقایسه پذیر خواهد بود.

   1: using System;
   2: using System.Linq;
   3: using System.Collections.Generic;
   4: 
   5: namespace OrederByClause
   6: {
   7:     class Person
   8:     {
   9:         public string FirstName { get; set; }
  10:         public string LastName  { get; set; }
  11:         public int    Age       { get; set; }
  12: 
  13:         public Person(string f, string l, int a)
  14:         {
  15:             FirstName = f;
  16:             LastName  = l;
  17:             Age       = a;
  18:         }
  19:     }
  20: 
  21:     class FirstNameComparer : IComparer<Person>       
  22:     {                                                 
  23:         public int Compare(Person x, Person y)        
  24:         {                                             
  25:             return x.FirstName.CompareTo(y.FirstName);
  26:         }                                             
  27:     }                                                 
  28: 
  29:     class Program
  30:     {
  31:         static void Main(string[] args)
  32:         {
  33:             List<Person> people = new List<Person>() 
  34:             { 
  35:                 new Person("Peter" , "Redfield", 32), 
  36:                 new Person("Marvin", "Monrow"  , 17),
  37:                 new Person("Aaron" , "Striver" , 25) 
  38:             };
  39: 
  40:             var orderByQuery8 = people.OrderBy(person => person, new FirstNameComparer());
  41: 
  42:             foreach (var person in orderByQuery8)
  43:             {
  44:                 Console.WriteLine(String.Format("{0}", person.FirstName));
  45:             }
  46:         }
  47:     }
  48: }

پیاده سازی این رابط مستلزم پیاده سازی متدی به نام Compare() است که عملکرد آن بسیار شبیه به متد ()CompareTo در رابط IComparable<T> است با این تفاوت که این متد، دو شیء را به عنوان آرگومان قبول می‌کند (خط 23). در مثال بالا با استفاده از متد ()CompareTo مربوط به کلاس String که یک عدد صحیح را برمی گرداند، خاصیت FirstName اشیاء را با یکدیگر مقایسه کرده‌ایم (خط 25). در انتها می‌توانیم یک نمونه از این کلاس را به عنوان آرگومان به متدهای OrderBy() ، OrderByDescending() ، ThenBy() ، ThenByDescending() ارسال کنیم (خط 40).

var orderByQuery8 = people.OrderBy(person => person, new FirstNameComparer());

آرگومان دوم کلید مرتب سازی را مشخص می‌کند. در متد بالا کلید person است و با کمک شئ FirstNameComparer متد به صورت خودکار نتایج را بوسیله خاصیت FirstName مرتب می‌کند. راه دیگر برای مرتب سازی نزولی، استفاده از متد Reverse() بر روی مجموعه‌ای است که به صورت صودی مرتب سازی شده است.

var orderByQuery9 = (from person in people
                     orderby person.FirstName
                     select person).Reverse();

در مثال اول، کل پرس و جو را داخل پرانتز قرار داده و سپس متد Reverse() را فراخوانی کرده‌ایم. مثال دوم شکل متدی پرس و جوی موجود در مثال اول است.