5、談擴展方法的理解

爲何要用擴展方法

在說什麼是擴展方法以前咱們先來講說爲何要用擴展方法。html

首先咱們定義一個 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 ,這個時候是不能繼承的,咱們只能另想辦法。編程

隨意寫個靜態類:ide

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

擴展方法究竟是什麼

咱們看到上面使用的擴展方法,有沒有感受很神奇。僅僅多添加了一個this關鍵字就直接能夠當成擴展方法使用了。那擴展方法究竟是什麼東東,看了上面代碼好像和靜態方法有着說不清道不明的關係。下面咱們繼續分析:spa

分別定義一個靜態方法和一個擴展方法

 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#基礎知識鞏固系列

相關文章
相關標籤/搜索