[C#] 010. 建立物件時需要考慮是否實作比較器(擴充ICompareable增加自訂義的排序演算法)

讀【157個完美化C#的建議】一書的理解筆記 - 010

重點:ICompareable 的用法

流程說明
1. ICompareable實作與用途
2. ICompareable擴充實作器
3. LinQ 與 ICompareable效能比較 (LinQ快兩倍)
4. 結論

 

1. ICompareable實作與用途

我們建立一個薪資的類別如下 ,並且擴充ICompareable

/// <summary>
/// 薪資類別
/// </summary>
public class Salary : IComparable
{
    /// <summary>
    /// 國家
    /// </summary>
    public string Country { get; set;}
    /// <summary>
    /// 基本薪資
    /// </summary>
    public int BaseSalary { get; set; }
    /// <summary>
    /// 紅利
    /// </summary>
    public int Bonus { get; set; }

    public int CompareTo(object obj)
    {
        Salary staff = obj as Salary;
        #region 這段如同下面 BaseSalary.CompareTo(staff.BaseSalary)

        if (BaseSalary > staff.BaseSalary)
            return 1;
        else if (BaseSalary == staff.BaseSalary)
            return 0;
        else
            return -1;
        #endregion
        //return BaseSalary.CompareTo(staff.BaseSalary);//當被呼叫 .sort()時優先以BaseSalary資料做排序

        //return Bonus.CompareTo(staff.Bonus);//當被呼叫 .sort()Bonus
    }

}

建立完成後,於主程式宣告以下內容

List<Salary> salaryList = new List<Salary>();
salaryList.Add(new Salary() { BaseSalary = 22000, Country = "B-Taiwan" , Bonus = 50});
salaryList.Add(new Salary() { BaseSalary = 18000, Country = "C-China" , Bonus = 70});
salaryList.Add(new Salary() { BaseSalary = 9000, Country = "A-India" , Bonus =60});
salaryList.Add(new Salary() { BaseSalary = 31000, Country = "D-DUSA" ,Bonus =55});

因為我們覆寫了 ICompareable 呼叫.sort()會執行BaseSalary 的比較,並且由小到大

//排序做法1. IComaparable
salaryList.Sort();//Sort預設內建的排序功能,我們覆寫IComparable

排序後會如下的結果

{ BaseSalary = 9000, Country = "A-India" , Bonus =60}
{ BaseSalary = 18000, Country = "C-China" , Bonus = 70})
{ BaseSalary = 22000, Country = "B-Taiwan" , Bonus = 50})
{ BaseSalary = 31000, Country = "D-DUSA" ,Bonus =55})

 

2. ICompareable擴充實作器

我們也可以將自訂義的擴充器,實作完成,讓.sort()呼叫,先建立以下

※可以將第1步驟的 Salary類別傳進去比較

/// <summary>
/// 建立一個自訂義的比較器 
/// </summary>
public class BonusComparer : IComparer<Salary>
{
    /// <summary>
    /// 針對Salary 的 Bonus 進行比較
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <returns></returns>
    public int Compare(Salary x, Salary y)
    {
        //將Bonus變為Sort()的參考物件
        return x.Bonus.CompareTo(y.Bonus);
    }
}

就可以在主程式呼叫以下

 //排序做法2. IComparer => 非預設性(可擴充的) .sort() 排序
  salaryList.Sort(new BonusComparer());

產生的結果就會如下(因為比較器是比較Bonus屬性)

​{ BaseSalary = 22000, Country = "B-Taiwan" , Bonus = 50})
{ BaseSalary = 31000, Country = "D-DUSA" ,Bonus =55})
{ BaseSalary = 9000, Country = "A-India" , Bonus =60}
{ BaseSalary = 18000, Country = "C-China" , Bonus = 70})

 

3. LinQ 與 ICompareable效能比較 (LinQ快兩倍)

我們對List<T> 系列如果想要排序,通常都會用 .OrderBy()的方式,所以我們這邊進行兩種排序的效能比較

以下先將值放進 A、B、C 三項List<T> 中

salaryListTestA : ICompareable 比較器排序

salaryListTestB : Linq 排序,然後執行物件.ToList() (※要進行值的存取,必須ToList())

salaryListTestC : Linq 排序 (※不執行物件.ToList())

//效能比較
List<Salary> salaryListTestA = new List<Salary>();
List<Salary> salaryListTestB = new List<Salary>();
List<Salary> salaryListTestC = new List<Salary>();
Random Dice = new Random();
int DiceVar = 0;//骰子變數
string resultMessage = "";
for (int i = 0; i < 10000000; i++)
{
    DiceVar = Dice.Next(0, 10000000);
    //兩者存放資料相同才有比較意義
    salaryListTestA.Add(new Salary() { Bonus = DiceVar });
    salaryListTestB.Add(new Salary() { Bonus = DiceVar });
    salaryListTestC.Add(new Salary() { Bonus = DiceVar });
}
Stopwatch sw = Stopwatch.StartNew();

排序的三種作法

//排序做法1. IComparer => 非預設性(可擴充的) .sort() 排序
//計時開始
sw.Restart();
salaryListTestA.Sort(new BonusComparer());
sw.Stop();//計時結束
resultMessage += string.Format("IComparer => 非預設性(可擴充的) .sort() 排序: {0}{1}", sw.Elapsed.ToString(), "\r\n");


//排序做法2. LinQ 轉ToList
sw.Restart();
List<Salary> tempB = salaryListTestB.OrderBy(o => o.Bonus).ToList();
sw.Stop();//計時結束
resultMessage += string.Format("排序做法2. LinQ 轉ToList: {0}{1}", sw.Elapsed.ToString(), "\r\n");

//排序做法3. LinQ 不轉換
sw.Restart();
salaryListTestC.OrderBy(o => o.Bonus);
sw.Stop();//計時結束
resultMessage += string.Format("排序做法3. LinQ 不轉換: {0}{1}", sw.Elapsed.ToString(), "\r\n");

textBox1.Text += resultMessage;

最後印出如下的數據,LinQ的效能在簡單的排序是優化過的。

4. 結論

隨著C# 的進步,LinQ 考量的情境愈來愈多,在很多地方LinQ的效能已經比自己簡單定義的比較器來得快。

如果是大型系統有優化的必要性才會更需要考量ICompareable 的做法。

 

github連結(Vs2015) : 點我下載