【備戰面試之】五、談擴展方法的理解

分類:IT技術 時間:2016-10-11

為什麽要用擴展方法

在說什麽是擴展方法之前我們先來說說為什麽要用擴展方法。

首先我們定義一個 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

 

以上都是胡說八道。

好了,今天的擴展方法就分析到這裏。感謝閱讀,希望對您有一點點作用!

文章首鏈:http://www.cnblogs.com/zhaopei/p/5678842.html

 


Tags: 出生日期 person public return sealed

文章來源:


ads
ads

相關文章
ads

相關文章

ad