五、談擴充套件方法的理解

農碼一生發表於2016-07-17

為什麼要用擴充套件方法

在說什麼是擴充套件方法之前我們先來說說為什麼要用擴充套件方法。

首先我們定義一個 Person 類:

public class Person
{
    /// <summary>
    /// 出生日期
    /// </summary>
    public DateTime BirthTime { get; set; }
    /// <summary>
    /// 死亡日期
    /// </summary>
    public DateTime? DeathTime { get; set; }
    //、、、、、、
}

加入這個類來自第三方的dll引用,且現在我們需要新增一個方法 GetAge 獲取年齡。你可能會想到自己定一個子類繼承:

public class MyPerson : Person
{
    public int GetAge()
    {
        if (DeathTime.HasValue)
            return (DeathTime.Value - BirthTime).Days / 365;
        else
            return (DateTime.Now - BirthTime).Days / 365;
    }
}

是的,這樣可以實現我們的需求。不過實現新增的方法就去繼承真的是最合適的嗎(暫且不說)? 如果上面定義的密封類呢? public sealed class Person ,這個時候是不能繼承的,我們只能另想辦法。

隨意寫個靜態類:

public static class ExtensionClass
{
    public static int GetAge(Person person)
    {
        if (person.DeathTime.HasValue)
            return (person.DeathTime.Value - person.BirthTime).Days / 365;
        else
            return (DateTime.Now - person.BirthTime).Days / 365;
    }

然後呼叫  age = ExtensionClass.GetAge(p); ,是的看似不錯。可是這和我們說的擴充套件方法有什麼關係呢?下面就是見證奇蹟的時候了。

其他的任何地方都不變,唯一變化的是在引數前面加里this關鍵字。對,是的,僅僅如此它就變成了我們今天要講的擴充套件方法。

呼叫如:  var age = p.GetAge(); 相比上面的 age = ExtensionClass.GetAge(p); 更簡單明瞭。

這裡我們說的是在需要擴充套件密封類的方法時,我們可以使用到擴充套件方法。還有一種情況就是,在需要擴充套件介面的時候時候我們更加需要。比如,需要擴充套件IList的排序。我們要麼寫個擴充套件方法,要麼是繼承實現介面(會強制要求實現介面下的所有方法)。我想你心中已經有了答案選擇哪種方式。

擴充套件方法到底是什麼

我們看到上面使用的擴充套件方法,有沒有感覺很神奇。僅僅多新增了一個this關鍵字就直接可以當成擴充套件方法使用了。那擴充套件方法到底是什麼東東,看了上面程式碼好像和靜態方法有著說不清道不明的關係。下面我們繼續分析:

分別定義一個靜態方法和一個擴充套件方法

 public static class ExtensionClass
 {
     public static int GetAge2(Person person)
     {
         if (person.DeathTime.HasValue)
             return (person.DeathTime.Value - person.BirthTime).Days / 365;
         else
             return (DateTime.Now - person.BirthTime).Days / 365;
     }

     public static int GetAge(this Person person)
     {
         if (person.DeathTime.HasValue)
             return (person.DeathTime.Value - person.BirthTime).Days / 365;
         else
             return (DateTime.Now - person.BirthTime).Days / 365;
     }

分別呼叫:

var p = new Person() { BirthTime = DateTime.Parse("1990-07-19") };
var age = p.GetAge();
age = ExtensionClass.GetAge2(p);

編譯後的IL程式碼:

我們看到反編譯成IL之後發現兩者並無不同。所以,我理解成(擴充套件方法本質上就是靜態方法,之所以出現擴充套件方法是C#以另外一種形式表現靜態方法而已。只有有何妙用下面會繼續講解)。且 編譯後同樣帶上了靜態類名。

擴充套件方法可以做些什麼

  • 把已有的靜態方法轉成擴充套件方法:如:
public static bool IsNullOrEmpty(this string str)
{
    return string.IsNullOrEmpty(str);
}

呼叫: 

string str = null;
var isNull = str.IsNullOrEmpty();

 感覺相比期靜態方法呼叫要優雅,更接近我們的自然語言。

  •  可以編寫很多的幫助類,如(以string為例):
/// <summary>
        /// 轉DateTime 
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static DateTime? MyToDateTime(this string str)
        {
            if (string.IsNullOrEmpty(str))
                return null;
            else
                return DateTime.Parse(str);
        }

        /// <summary>
        /// 轉double
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static double MyToDouble(this string str)
        {
            if (string.IsNullOrEmpty(str))
                return -1;
            else
                return double.Parse(str);
        }

        /// <summary>
        /// 轉int
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static int MyToInt(this string str)
        {
            if (string.IsNullOrEmpty(str))
                return -1;
            else
                return int.Parse(str);
        }

        /// <summary>
        /// 指示指定的字串是 null 還是 System.String.Empty 字串。
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static bool IsNullOrEmpty(this string str)
        {
            return string.IsNullOrEmpty(str);
        }

        /// <summary>
        /// 如果字串為null,則返回空字串。(否則返回原字串)
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string GetValueOrEmpty(this string str)
        {
            if (str.IsNullOrEmpty())
                return string.Empty;
            return str;
        }
View Code

上面所有的都只是擴充套件方法的附加用處,擴充套件方法真正的威力是為Linq服務的(主要體現於IEnumerable和IQueryable),實現鏈式程式設計。下面我們自己來實現所謂的鏈式程式設計:

初始化 Person 集合。

List<Person> persons = new List<Person>() 
{
     new Person(){ BirthTime=DateTime.Parse("1990-01-19")},
     new Person(){ BirthTime=DateTime.Parse("1993-04-17")},
     new Person(){ BirthTime=DateTime.Parse("1992-07-19"), DeathTime=DateTime.Parse("2010-08-18")},
     new Person(){ BirthTime=DateTime.Parse("1990-03-14")},
     new Person(){ BirthTime=DateTime.Parse("1991-08-15")},
     new Person(){ BirthTime=DateTime.Parse("1993-07-29")},
     new Person(){ BirthTime=DateTime.Parse("1991-06-19")}
};

需求:1.查詢活人。2.按出生日期排序

public static class ExtensionClass
    {
        /// <summary>
        /// 按條件查詢
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static IList<T> MyWhere<T>(this IList<T> list, Func<T, bool> func)
        {
            List<T> newList = new List<T>();
            foreach (var item in list)
            {
                if (func(item))
                    newList.Add(item);
            }
            return newList;
        }

        /// <summary>
        /// 升序排序
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static IList<T> MyOrderBy<T>(this IList<T> list, Func<T, DateTime> func)
        {
            if (list.Count() <= 1)
                return list;

            for (int i = 0; i < list.Count(); i++)
            {
                for (int j = i + 1; j < list.Count(); j++)
                {
                    var item1 = list[j - 1];
                    var item2 = list[j];
                    if ((func(item1) - func(item2)).Ticks > 0)
                    {
                        list[j - 1] = item2;
                        list[j] = item1;
                    }
                }
            }
            return list;
        }
        /// <summary>
        /// 降序排序
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static IList<T> MyOrderByDescending<T>(this IList<T> list, Func<T, DateTime> func)
        {
            if (list.Count() <= 1)
                return list;

            for (int i = 0; i < list.Count(); i++)
            {
                for (int j = 1; j < list.Count() - i; j++)
                {
                    var item1 = list[j - 1];
                    var item2 = list[j];
                    if ((func(item1) - func(item2)).Ticks < 0)
                    {
                        list[j - 1] = item2;
                        list[j] = item1;
                    }
                }
            }
            return list;
        }
    }

呼叫:(這裡僅僅為了演示,所以不要討論實現是否合理、演算法是否高效。

var newPersons = persons.MyWhere(t => t.DeathTime == null).MyOrderByDescending(t => t.BirthTime);
foreach (var item in newPersons)
{
    Console.WriteLine(item.BirthTime);
}

就是如此簡單的實現了所謂的函數語言程式設計。結果圖如下:

這樣一句程式碼搞定所有邏輯,像自然語言般的流暢。其實.net為IEnumerable實現了這樣的擴充套件,如:

執行結構和上面一模一樣。

 

其實擴充套件方法也可以當成靜態方法來使用:

 var p1 = ExtensionClass.MyWhere(persons, t => t.DeathTime == null);
 var p2 = ExtensionClass.MyOrderByDescending(p1, t => t.BirthTime);
 var p3 = ExtensionClass.MyOrderBy(p2, t => t.BirthTime);

(不信?繼續看,有圖有真相)

 

C#程式碼:

 

反編譯C#的程式碼:(你是不是看到了,編譯後直接就是使用的擴充套件方法的形式。

反編譯的IL程式碼:

雖然編譯後的程式碼是一樣的,但是做為程式設計師的我們更喜歡哪種方式呢?

 

總結:

我們在對擴充套件方法的怎麼使用疑惑或者忘記了規則的時候,我們不用去查詢資料說:

  1. 第一個引數是要擴充套件或者要操作的型別,這稱為"被擴充套件的型別"
  2. 為了指定擴充套件方法,要在被擴充套件的型別名稱前面附加this修飾符
  3. 要將方法作為一個擴充套件方法來訪問,要用using指令匯入擴充套件型別的名稱空間,或者使擴充套件型別和呼叫程式碼在同一個名稱空間中.

我們只需記住,當你不知道怎麼編寫或使用擴充套件方法時,你先把它當成靜態方法編寫或使用。如果可行,一般都可以轉成擴充套件方法的形式。

 

全部程式碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity.Utilities;
using System.Diagnostics.CodeAnalysis;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using System.IO;

namespace test
{


    class Program
    {
        static void Main(string[] args)
        {
            /*             
             * 1.工具類
             * 2.鏈式程式設計
             */
            string str = null;
            var isNull = str.IsNullOrEmpty();

            var p = new Person() { BirthTime = DateTime.Parse("1990-07-19") };
            var age = p.GetAge();
            age = ExtensionClass.GetAge2(p);

            List<Person> persons = new List<Person>() 
            {
                 new Person(){ BirthTime=DateTime.Parse("1990-01-19")},
                 new Person(){ BirthTime=DateTime.Parse("1993-04-17")},
                 new Person(){ BirthTime=DateTime.Parse("1992-07-19"), DeathTime=DateTime.Parse("2010-08-18")},
                 new Person(){ BirthTime=DateTime.Parse("1990-03-14")},
                 new Person(){ BirthTime=DateTime.Parse("1991-08-15")},
                 new Person(){ BirthTime=DateTime.Parse("1993-07-29")},
                 new Person(){ BirthTime=DateTime.Parse("1991-06-19")}
            };

            var newPersons = persons.MyWhere(t => t.DeathTime == null).MyOrderByDescending(t => t.BirthTime);


            var p1 = ExtensionClass.MyWhere(persons, t => t.DeathTime == null);
            var p2 = ExtensionClass.MyOrderByDescending(p1, t => t.BirthTime);
            var p3 = ExtensionClass.MyOrderBy(p2, t => t.BirthTime);

            foreach (var item in newPersons)
            {
                Console.WriteLine(item.BirthTime);
            }
            Console.ReadKey();
        }
    }

    public sealed class Person
    {
        /// <summary>
        /// 出生日期
        /// </summary>
        public DateTime BirthTime { get; set; }
        /// <summary>
        /// 死亡日期
        /// </summary>
        public DateTime? DeathTime { get; set; }
    }

    //public class MyPerson : Person
    //{
    //    public int GetAge()
    //    {
    //        if (DeathTime.HasValue)
    //            return (DeathTime.Value - BirthTime).Days / 365;
    //        else
    //            return (DateTime.Now - BirthTime).Days / 365;
    //    }
    //}
    public static class ExtensionClass
    {
        /// <summary>
        /// 按條件查詢
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static IList<T> MyWhere<T>(this IList<T> list, Func<T, bool> func)
        {
            List<T> newList = new List<T>();
            foreach (var item in list)
            {
                if (func(item))
                    newList.Add(item);
            }
            return newList;
        }

        /// <summary>
        /// 升序排序
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static IList<T> MyOrderBy<T>(this IList<T> list, Func<T, DateTime> func)
        {
            if (list.Count() <= 1)
                return list;

            for (int i = 0; i < list.Count(); i++)
            {
                for (int j = i + 1; j < list.Count(); j++)
                {
                    var item1 = list[j - 1];
                    var item2 = list[j];
                    if ((func(item1) - func(item2)).Ticks > 0)
                    {
                        list[j - 1] = item2;
                        list[j] = item1;
                    }
                }
            }
            return list;
        }
        /// <summary>
        /// 降序排序
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static IList<T> MyOrderByDescending<T>(this IList<T> list, Func<T, DateTime> func)
        {
            if (list.Count() <= 1)
                return list;

            for (int i = 0; i < list.Count(); i++)
            {
                for (int j = 1; j < list.Count() - i; j++)
                {
                    var item1 = list[j - 1];
                    var item2 = list[j];
                    if ((func(item1) - func(item2)).Ticks < 0)
                    {
                        list[j - 1] = item2;
                        list[j] = item1;
                    }
                }
            }
            return list;
        }

        public static int GetAge2(Person person)
        {
            if (person.DeathTime.HasValue)
                return (person.DeathTime.Value - person.BirthTime).Days / 365;
            else
                return (DateTime.Now - person.BirthTime).Days / 365;
        }

        public static int GetAge(this Person person)
        {
            if (person.DeathTime.HasValue)
                return (person.DeathTime.Value - person.BirthTime).Days / 365;
            else
                return (DateTime.Now - person.BirthTime).Days / 365;
        }

        public static bool IsNullOrEmpty(this string str)
        {
            return string.IsNullOrEmpty(str);
        }
    } 
}
View Code

 

本文以同步至《C#基礎知識鞏固系列

相關文章