مقایسه اشیاء با استفاده از رابط های IComparable و IComparer

دو رابط مفید برای مقایسه اشیایی که توسط کاربر تعریف شده‌اند وجود دارد. این دو رابط IComparable<T> و IComparer<T> می‌باشند. رابط IComparable<T> در یک کلاس پیاده سازی می‌شود و اجازه می‌دهد کلاس یا شیء ایجاد شده از کلاس با اشیاء دیگر از همین کلاس مقایسه شوند. IComparer<T> در یک کلاس جداگانه پیاده سازی می‌شود. همانطور که از نام این رابط پیداست، پیاده سازی آن باعث ایجاد یک کلاس مقایسه پذیر می‌شود. به این نکته توجه کنید که، نسخه‌های غیر جنریک این دو رابط نیز وجود دارد ولی کار کردن با نسخه‌های جنریک آن‌ها بسیار راحت‌تر بوده و شما نیاز به تبدیل برای مقایسه اشیاء ندارید.

رابط <IComparable<T

حال به نحوه استفاده از رابط IComparable<T> می‌پردازیم. در مثال زیر یک کلاس نشان داده شده است که رابط IComparable<T> را پیاده سازی می‌کند:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Collections;
   4: 
   5: namespace IComparableInterface
   6: {
   7:     public 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 int CompareTo(Person other)
  14:         {                                 
  15:             if (this.Age > other.Age)     
  16:                 return 1;                 
  17:             else if (this.Age < other.Age)
  18:                 return -1;                
  19:             else                          
  20:                 return 0;                 
  21:         }                                 
  22:     }
  23: 
  24:     public class Program
  25:     {
  26:         static void Main(string[] args)
  27:         {
  28:             Person person1 = new Person { FirstName = "John", LastName = "Smith", Age = 21 };
  29:             Person person2 = new Person { FirstName = "Mark", LastName = "Logan", Age = 19 };
  30:             Person person3 = new Person { FirstName = "Luke", LastName = "Adams", Age = 20 };
  31: 
  32:             Person youngest = GetYoungest(person1, person2, person3);
  33:             Person oldest = GetOldest(person1, person2, person3);
  34: 
  35:             Console.WriteLine("The youngest person is {0} {1}.", 
  36:                 youngest.FirstName, youngest.LastName);
  37:             Console.WriteLine("The oldest person is {0} {1}.", 
  38:                 oldest.FirstName, oldest.LastName);
  39:             Console.ReadKey();
  40:         }
  41: 
  42:         private static Person GetYoungest(Person person1, Person person2, Person person3)
  43:         {
  44:             Person youngest = person1; 
  45: 
  46:             if (person2.CompareTo(youngest) == -1) 
  47:                 youngest = person2;
  48: 
  49:             if (person3.CompareTo(youngest) == -1) 
  50:                 youngest = person3;
  51: 
  52:             return youngest;
  53:         }
  54: 
  55:         private static Person GetOldest(Person person1, Person person2, Person person3)
  56:         {
  57:             Person oldest = person1; 
  58: 
  59:             if (person2.CompareTo(oldest) == 1) 
  60:                 oldest = person2;
  61: 
  62:             if (person3.CompareTo(oldest) == 1) 
  63:                 oldest = person3;
  64: 
  65:             return oldest;
  66:         }
  67:     }
  68: }
The youngest person is Mark Logan.
The oldest person is John Smith.

وقتی که یک کلاس از رابط IComparable<T> استفاده می‌کند، لازم است که تنها متد آن یعنی متد CompareTo() را نیز پیاده سازی کند. متد CompareTo() یک مقدار صحیح را بر می‌گرداند. این متد یک آرگومان قبول می‌کند که همان شیی است که قرار است با شی جاری مقایسه شود. در داخل متد CompareTo() در مثال بالا ما سن (age) شخص فعلی را با سن شخص دیگر مقایسه کرده‌ایم. طبق قرارداد اگر سن شخص مورد نظر ما از سن شخص دیگر بیشتر بود مقداری بزرگتر از صفر، اگر کمتر بود مقداری کمتر از صفر و اگر مساوی بود مقدار صفر برگشت داده می‌شود. خطوط 22-7 پیاده سازی رابط IComparable<T> توسط شیء Person را نشان می‌دهد. برنامه جوانترین و پیرترین شخص را تشخیص می‌دهد. در خطوط 30-28 سه شیء Person با مقادیر کاملاً اختیاری ایجاد شده است. در خطوط 33-32 متغیرهایی برای نگهداری جوانترین و پیرترین شخص تعریف شده‌اند. در خط 32 متد GetYoungest() را فراخوانی کرده‌ایم. این متد در خطوط 53-42 تعریف شده است و سه شخص را که قرار است از لحاظ سنی با هم مقایسه شوند را قبول می‌کند. در خط 21 فرض را بر این گذاشته‌ایم که اولین شخص (person1) جوان‌ترین شخص است. سپس با استفاده از پیاده سازی متد CompareTo() تست می‌کنیم که آیا شخص دوم (person2) از شخص اول جوانتر است یا نه. در داخل متد مذکور سن شخص دوم و اول را با هم مقایسه می‌کنیم. اگر سن شخص دوم کمتر بود، باید مقدار 1- برگشت داده شده و در خط 47، person2 به عنوان جوانترین شخص معرفی شود. در خطوط 50-49 از تکنیکی مشابه برای شخص سوم استفاده کرده‌ایم. بعد از مقایسه جوانترین شخص در خط 52 به عنوان نتیجه برگشت داده می‌شود. در خط 33 متد GetOldest() که در خطوط 66-55 تعریف شده است فراخوانی می‌شود. کدهای داخل این متد شبیه به متد GetYoungest() است با این تفاوت که تست می‌شود که آیا سن شخص دیگر بزرگتر از سن شخص مورد نظر ماست یا نه؟ بنابراین باید انتظار داشته باشیم که مقدار 1 به جای 1- توسط متد برگشت داده شود. در خطوط 38-35 نام جوانترین و پیرترین شخص چاپ می‌شود.

رابط <IComparer<T

IComparer<T> در یک کلاس جداگانه پیاده سازی می‌شود. همانطور که از نام این رابط پیداست، پیاده سازی آن باعث ایجاد یک کلاس مقایسه پذیر می‌شود. به وسیله این رابط می‌توان چندین مقایسه برای کلاس Person ایجاد کرد. مثلاً در مثال کلاس Person، اشیاء ایجاد شده بر اساس سن (Age)، نام (FirstName) و یا نام خانوادگی (LastName) مورد مقایسه قرار می‌گیرند. هنگام استفاده از این رابط لازم است یک متد به نام ()Compare که دو شیء قبول می‌کند و یک عدد صحیح را به عنوان نتیجه بر می‌گرداند را پیاده سازی کند. از آنجاییکه از رابط IComparer<Person> استفاده کرده‌ایم متد ()Compare به طور خودکار دو شئ Person قبول می‌کند :

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Collections;
   4: 
   5: namespace IComparerInterface
   6: {
   7:     public class Person
   8:     {
   9:         public string FirstName { get; set; }
  10:         public string LastName  { get; set; }
  11:         public int    Age       { get; set; }
  12:     }
  13: 
  14:     public class FirstNameComparer : IComparer<Person>            
  15:     {                                                             
  16:         public int Compare(Person person1, Person person2)        
  17:         {                                                         
  18:             return person1.FirstName.CompareTo(person2.FirstName);
  19:         }                                                         
  20:     }                                                             
  21: 
  22:     public class LastNameComparer : IComparer<Person>             
  23:     {                                                             
  24:         public int Compare(Person person1, Person person2)        
  25:         {                                                         
  26:             return person1.LastName.CompareTo(person2.LastName);  
  27:         }                                                         
  28:     }                                                             
  29: 
  30:     public class AgeComparer : IComparer<Person>                  
  31:     {                                                             
  32:         public int Compare(Person person1, Person person2)        
  33:         {                                                         
  34:             return person1.Age.CompareTo(person2.Age);            
  35:         }                                                         
  36:     }                                                             
  37: 
  38:     class Program
  39:     {
  40:         static void Main(string[] args)
  41:         {
  42:             List<Person> persons = new List<Person> 
  43:             { 
  44:                     new Person { FirstName = "John", LastName = "Smith", Age = 21 },
  45:                     new Person { FirstName = "Mark", LastName = "Logan", Age = 19 },
  46:                     new Person { FirstName = "Luke", LastName = "Adams", Age = 20 }
  47:             };
  48: 
  49:             Console.WriteLine("Original Order");
  50:             foreach (Person p in persons)
  51:                 Console.WriteLine("{0} {1}, Age: {2}", p.FirstName, p.LastName, p.Age);
  52: 
  53:             Console.WriteLine("\nSort persons based on their:");
  54:             Console.WriteLine("[1] FirstName\n[2] LastName\n[3]Age");
  55: 
  56:             Console.Write("Enter your choice: ");
  57:             int choice = Int32.Parse(Console.ReadLine());
  58: 
  59:             ReorderPersons(choice, persons);
  60: 
  61:             Console.WriteLine("New Order");
  62:             foreach (Person p in persons)
  63:                 Console.WriteLine("{0} {1}, Age: {2}", p.FirstName, p.LastName, p.Age);
  64:         }
  65: 
  66:         private static void ReorderPersons(int choice, List<Person> persons)
  67:         {
  68:             IComparer<Person> comparer;
  69: 
  70:             if (choice == 1)
  71:                 comparer = new FirstNameComparer();
  72:             else if (choice == 2)
  73:                 comparer = new LastNameComparer();
  74:             else
  75:                 comparer = new AgeComparer();
  76: 
  77:             persons.Sort(comparer);
  78:         }
  79:     }
  80: }
Original Order
John Smith, Age: 21
Mark Logan, Age: 19
Luke Adams, Age: 20

Sort persons based on their:
[1] FirstName
[2] LastName
[3]Age
Enter your choice: 1
New Order
John Smith, Age: 21
Luke Adams, Age: 20
Mark Logan, Age: 19
Original Order
John Smith, Age: 21
Mark Logan, Age: 19
Luke Adams, Age: 20

Sort persons based on their:
[1] FirstName
[2] LastName
[3]Age
Enter your choice: 2
New Order
Luke Adams, Age: 20
Mark Logan, Age: 19
John Smith, Age: 21
Original Order
John Smith, Age: 21
Mark Logan, Age: 19
Luke Adams, Age: 20

Sort persons based on their:
[1] FirstName
[2] LastName
[3]Age
Enter your choice: 3
New Order
Mark Logan, Age: 19
Luke Adams, Age: 20
John Smith, Age: 21

در مثال بالا با استفاده از کلاس FirstNameComparer که رابط IComparer را پیاده سازی می‌کند، دو شئ Person بر اساس نام مقایسه می‌شوند (خطوط 20-14). در متد ()Compare به سادگی و با استفاده از متد از پیش تعریف شده ()CompareTo از کلاس String استفاده کرده‌ایم (چون خاصیت FirstName یک رشته است) و یک مقدار را به عنوان نتیجه بر می‌گردانیم.

به روشی مشابه از کلاس‌های LastNameComparer و AgeComparer برای مقایسه اشیاء بر اساس نام خانوادگی و سن استفاده می‌کنیم (خطوط 36-22). متد ()Compare در صورتی که دو پارامتر با هم برابر باشند، مقدار 0، اگر پارامتر اول از پارامتر دوم بزرگتر باشد، مقداری بزرگتر از 0 و اگر پارامتر اول از پارامتر دوم کوچکتر باشد مقداری کوچکتر از 0 را بر می‌گرداند. در خطوط 18، 26 و 34 نحوه مقایسه اشیاء بر اساس خواص مختلف را مشخص کرده‌ایم.
در خطوط 47-42 اشیاء با مقادیر از پیش تعریف شده‌ای برای هر یک از خاصیت‌هایشان ایجاد شده است. در خطوط 51-50 ترتیب عادی و اصلی این اشیاء نمایش داده شده است. در خطوط 54-53 لیستی از انتخاب‌هایی که کاربر بر اساس آن‌ها می‌تواند عملیات مرتب سازی را انجام دهد آورده شده است. در خطوط 57-56 از کاربر در مورد انتخابش سؤال می‌شود. در خط 59 متد ()ReorderPersons متد از پیش تعریف شده‌ی خطوط 78-66 را فراخوانی می‌کنیم. این متد انتخاب کاربر و لیستی از اشیاء که قرار است بر اساس خاصیتی که کاربر انتخاب کرده است مرتب شوند را قبول می‌کند. در داخل متد یک متغیر تعریف کرده‌ایم که از نوع IComparer<Person> است و در نتیجه می‌تواند هر نوع کلاسی که رابط مذکور را پیاده سازی می‌کند را شامل شود. در خطوط 75-70 چک می‌کنیم که اگر کاربر یک مقدار عددی خاص را انتخاب کرد، چه کارهایی انجام شود. در خط 77 از متد ()Sort کلاس List<T> استفاده کرده‌ایم. این متد دارای یک نسخه سربارگذاری شده است که یک شیء IComparer<T> را قبول می‌کند. ما در خط 68 کلاس مقایسه کننده بر اساس نوع انتخاب کاربر را به این متد می‌دهیم و سپس متد ()Sort شیء Person را بر اساس این کلاس مرتب می‌کند.