今天我來學習泛型,泛型是編程入門學習的基礎類型,從.net誕生2.0開始就出現了泛型,今天我們開始學習泛型的語法和使用。 什麼是泛型? 泛型(generic)是C#語言2.0和通用語言運行時(CLR)的一個新特性。泛型為.NET框架引入了類型參數(type parameters)的概念。類型參數使得 ...
今天我來學習泛型,泛型是編程入門學習的基礎類型,從.net誕生2.0開始就出現了泛型,今天我們開始學習泛型的語法和使用。
什麼是泛型?
泛型(generic)是C#語言2.0和通用語言運行時(CLR)的一個新特性。泛型為.NET框架引入了類型參數(type parameters)的概念。類型參數使得設計類和方法時,不必確定一個或多個具體參數,其的具體參數可延遲到客戶代碼中聲明、實現。這意味著使用泛型的類型參數T,寫一個類MyList<T>,客戶代碼可以這樣調用:MyList<int>, MyList<string>或 MyList<MyClass>。這避免了運行時類型轉換或裝箱操作的代價和風險。
上面是官方腔調,我說人話:泛型就是為了滿足不同類型,相同代碼的重用!
為什麼要有泛型?
下麵我們舉例子來講解為什麼會要泛型,以下我列舉了三個例子來講解:
我們列舉了ShowInt,ShowString,ShowDatatime三個方法,如果我們在程式中每封裝一個方法有不同參數,就要像下麵這樣寫一個函數的話,代碼會變得很累贅,在調用的時候必須傳遞吻合的參數,所以不得不寫出了3個方法,那有沒有好的方法來解決這樣的問題呢?答案是當然有,微軟是很聰明的,傾聽小滿哥慢慢來講。
/// <summary> /// 列印個int值/// </summary> /// <param name="iParameter"></param> public static void ShowInt(int iParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(CommonMethod).Name, iParameter.GetType().Name, iParameter); } /// <summary> /// 列印個string值/// </summary> /// <param name="sParameter"></param> public static void ShowString(string sParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(CommonMethod).Name, sParameter.GetType().Name, sParameter); } /// <summary> /// 列印個DateTime值/// </summary> /// <param name="oParameter"></param> public static void ShowDateTime(DateTime dtParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(CommonMethod).Name, dtParameter.GetType().Name, dtParameter); }
定義一些測試變數
int iValue = 123;
string sValue = "456";
DateTime dtValue = DateTime.Now;
object oValue = "789";
普通方式調用演示
ShowInt(iValue);
ShowInt(sValue);//這樣不行類型必須吻合
ShowString(sValue);
ShowDateTime(dtValue);
在.net 1.0的時候微軟出現了object這個概念,下麵有一個方法ShowObject,你們就會發現不管參數是int srtring datetime 我們都可以調用ShowObject來操作實現,那為什麼會這樣呢?
1:Object類型是一切類型的父類。
2:任何父類出現的地方,都可以用子類來代替。
出現這個基本滿足了開發人員的一些需求,在.net1.0和1.1的時候,這個時候還沒有泛型就用object代替。
/// <summary> /// 列印個object值 /// 1 object類型是一切類型的父類 /// 2 任何父類出現的地方,都可以用子類來代替 /// .Net Framework 1.0 1.1 /// </summary> /// <param name="oParameter"></param> public static void ShowObject(object oParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(CommonMethod), oParameter.GetType().Name, oParameter); }
定義一些測試變數
int iValue = 123;
string sValue = "456";
DateTime dtValue = DateTime.Now;
object oValue = "789";
object方式調用演示
ShowObject(oValue);
ShowObject(iValue);
ShowObject(sValue);
ShowObject(dtValue);
接著吹牛比,勞動人民的智慧還是很屌的,經過之前的經歷在.net2.0的時候,微軟讓主角登場了"泛型",當然同時出現的還有“線程池”這個我們先不講,回到正軌什麼是泛型?你們在開發的時候有沒有用過List<T>這個集合?這個就是泛型,深化下直接舉個慄子吧,下麵我寫一個例子Show<T>(T tParameter)看下泛型的寫法:
有個毛用?下麵這個方法也可以向上面ShowObject一樣,你們會發現不管參數是int srtring datetime 我們也可以調用Show<T>(T tParameter)來操作實現,替換了ShowObject這個方法的實現,具備了滿足了不同參數類型的方法實現,更適用性,泛型方法聲明的時候,沒有指定類型,而是調用的時候指定,具有延遲聲明和延遲思想,這個思想對我們在開發框架的時候灰常有用,不得不說老外這點還是很幾把厲害(還是我們被洗腦了?也許吧,我相信等中文編程語言出來估計會更屌,中華文化博大精深嘛),現在小伙伴們是不是大概瞭解泛型的基礎作用了?
/// <summary> /// 泛型方法聲明的時候,沒有指定類型,而是調用的時候指定 /// 延遲聲明:把參數類型的聲明推遲到調用 /// 延遲思想:推遲一切可以推遲的 /// /// 2.0泛型不是語法糖,而是由框架升級提供的功能 /// 泛型方法性能上和普通方法差不多的/// </summary> /// <typeparam name="T"></typeparam> /// <param name="tParameter"></param> public static void Show<T>(T tParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString()); }
調用演示
Show<object>(oValue);
Show<int>(iValue);
Show(iValue);//可以去掉,自動推算
Show<string>(iValue);//必須匹配
Show<string>(sValue);
Show<DateTime>(dtValue);
那問題來了,既然都可以實現為什麼要用這個呢?我們做事凡事都要帶著疑問去看待,有些事別人說好,但真的好不好我們要自己親自試試才知道,我們最關註的的效率問題,下麵是測試代碼:
public static void Show() { Console.WriteLine("****************Monitor******************"); { int iValue = 12345; long commonSecond = 0; long objectSecond = 0; long genericSecond = 0; { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 100000000; i++) { ShowInt(iValue); } watch.Stop(); commonSecond = watch.ElapsedMilliseconds; } { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 100000000; i++) { ShowObject(iValue); } watch.Stop(); objectSecond = watch.ElapsedMilliseconds; } { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 100000000; i++) { Show<int>(iValue); } watch.Stop(); genericSecond = watch.ElapsedMilliseconds; } Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}" , commonSecond, objectSecond, genericSecond); } }
結果如下:commonSecond=508,objectSecond=916,genericSecond=452 你會發現普通方法508ms,object方法916ms,泛型是452ms,其中object最慢,為什麼最慢呢?因為object會經過一個裝箱拆箱的過程,所以性能上會損失一些,但是在我看來這樣上億次這點損耗,算不了什麼,但是可以證明泛型和普通類型速度是差不多的,這一點可以認可泛型還是性能挺好的,這個可以推薦使用泛型的理由之一。
但泛型僅僅表現在這個層面嗎?遠遠不止,我們用泛型遠遠不是為了提升剛剛那點性能,為什麼要用泛型?答案來了,我們要用泛型就是為了滿足不同類型,相同代碼的重用,下麵我繼續舉慄子:
泛型的一些用法,泛型只有四種用途,泛型類,泛型介面,泛型委托,泛型方法,如下:
public class GenericClass<T> { public T Property { get; set; } }
public interface IGenericInterface<T>
{
}
public delegate void DoNothing<T>();
調用演示
List<int> intList = new List<int>();//原生態List類
List<string> stringList = new List<string>();
GenericClass<int> iGenericClass = new GenericClass<int>();
iGenericClass.Property = 1;
GenericClass<string> sGenericClass = new GenericClass<string>();
sGenericClass.Property = "1233";
DoNothing<int> method = new DoNothing<int>(() => { });
還有一種,別被嚇到:T,S,Xiaomange這些語法,只是占位符別怕,你可以自己定義的,在你調用的時候確定類型就OK了,好了差不多能理解泛型了吧?再說一次泛型就是為了滿足不同類型,相同代碼的重用。
public class ChildClassGeneric<T, S, XiaoManGe> : GenericClass<T>, IGenericInterface<S> { }
接下來我們來聊一聊拓展的一部分,好像泛型很弔的樣子感覺什麼都能用泛型類型代替,但是天下哪有那麼好的事情,雙刃劍的道理都懂,所以出現了泛型的約束這個緊箍咒。
泛型的約束
直接來代碼:
很簡單的一個例子,介面ISports,IWork,People類,Japanese類等簡單繼承了一下,目前準備的一些代碼。
public interface ISports { void Pingpang(); } public interface IWork { void Work(); } public class People { public int Id { get; set; } public string Name { get; set; } public void Hi() { } }
public class Chinese : People, ISports, IWork { public void Tradition() { Console.WriteLine("仁義禮智信,溫良恭儉讓"); } public void SayHi() { Console.WriteLine("吃了麽?"); } public void Pingpang() { Console.WriteLine("打乒乓球..."); } public void Work() { throw new NotImplementedException(); } }
public class Hubei : Chinese { public Hubei(int id) { } public string Changjiang { get; set; } public void Majiang() { Console.WriteLine("打麻將啦。。"); } }
public class Japanese : ISports { public int Id { get; set; } public string Name { get; set; } public void Pingpang() { Console.WriteLine("打乒乓球..."); } public void Hi() { } }
再來個調用類Constraint
public class Constraint { /// <summary> /// 代碼編譯沒問題,執行的時候才報錯 /// 代碼安全問題 /// </summary> /// <param name="oParameter"></param> public static void ShowObject(object oParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(CommonMethod), oParameter.GetType().Name, oParameter); People people = (People)oParameter; Console.WriteLine(people.Id);//這裡就不行了 代碼安全問題,調用不到,但編譯不會報錯。 Console.WriteLine(people.Name); } /// <summary> /// 基類約束: /// 1 帶來權力,可以使用基類裡面的屬性和方法 /// 2 帶來義務,類型參數必須是基類或者其子類 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="tParameter"></param> public static void Show<T>(T tParameter) where T : People, ISports, new()//都是and 關係 { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString()); Console.WriteLine(tParameter.Id); Console.WriteLine(tParameter.Name); tParameter.Hi(); tParameter.Pingpang(); T t = new T(); } public static void ShowPeople(People tParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString()); Console.WriteLine(tParameter.Id); Console.WriteLine(tParameter.Name); tParameter.Hi(); //tParameter.Pingpang(); } public static T DoNothing<T>(T tParameter) //where T : ISports//介面約束 //where T : class//引用類型約束 //where T : struct//值類型約束 where T : new()//無參數構造函數約束 { //tParameter.Pingpang(); //return null; T t = new T(); return default(T);//會根據T的類型,去產生一個預設值 } }
有興趣的可以測試下,用ShowObject的方法和泛型Show<T>(T tParameter)調用來看差異,如果不加入約束,想調用參數的屬性和方法,代碼安全問題是調用不了的,會報錯,但是加入基類約束之後是可以調用到的,所以泛型約束帶來了權利,可以使用基類的屬性和方法,但也帶來義務,參數只能是基類和子類,又想馬兒跑,又想馬兒不吃草的事情是沒有的,權利和義務是相對的,在享受權利的同時也會帶來義務。
其次,約束可以多重約束,然後即可作為參數約束也可以作為返回值約束,例如default(T)會根據泛型類型返回一個預設值,如果是無參數構造約束就可以類似這樣寫返回值T t=new T()。
總之,我覺得泛型約束為了更靈活的滿足不同條件的需求而產生的,就是我們在寫一些固定的需求,約束疊加來完成我們的功能,同時不讓泛型肆無忌憚。
泛型約束範圍如下:
約束 |
描述 |
where T: struct |
類型參數必須為值類型。 |
where T : class |
類型參數必須為引用類型。 |
where T : new() |
類型參數必須有一個公有、無參的構造函數。當於其它約束聯合使用時,new()約束必須放在最後。 |
where T : <base class name> |
類型參數必須是指定的基類型或是派生自指定的基類型。 |
where T : <interface name> |
類型參數必須是指定的介面或是指定介面的實現。可以指定多個介面約束。介面約束也可以是泛型的。 |
好了泛型的約束的先說到這,繼續套底子,瞭解到的都倒出來。
逆變和協變
逆變和協變不知道有沒有小伙伴熟悉的,我開始是不知道這個的,第一次看到也是一臉懵逼,到現在也有點迷糊,能不能講清楚看造化了,哈哈
繼續舉慄子:
//協變
public interface IEnumerable<out T> : IEnumerable
//逆變
public delegate void Action<in T>(T obj);
這段代碼是不是很熟悉?裡面有個Out T 還有 in T,這裡的Out 不是我們熟悉的參數返回Out,ref的作用,是協變專用的,逆變和協變指出現在介面或者委托泛型前面,
In只能作為參數傳入,Out只能作為參數傳出。
下麵來個代碼
public class Bird { public int Id { get; set; } } public class Sparrow : Bird { public string Name { get; set; } }
調用實例 IEnumerable<int> intList = new List<int>(); Action<int> iAction = null; Bird bird1 = new Bird(); Bird bird2 = new Sparrow();//左邊是父類 右邊是子類 Sparrow sparrow1 = new Sparrow(); //Sparrow sparrow2 = new Bird();//不是所有的鳥,都是麻雀 List<Bird> birdList1 = new List<Bird>();//一群鳥 是一群鳥 //List<Bird> birdList2 = new List<Sparrow>();//一群麻雀難道不是一群鳥 ? 不是的,沒有父子關係 List<Bird> birdList3 = new List<Sparrow>().Select(c => (Bird)c).ToList();//這裡使用彆扭了,明明知道但就是不能這樣寫
以上代碼發現問題了嗎?很明顯出現了一些不和諧的地方,我們換個方式如下:
/// <summary> /// 逆變:只能修飾傳入參數 /// </summary> /// <typeparam name="T"></typeparam> public interface ICustomerListIn<in T> { //T Get(); void Show(T t); } public class CustomerListIn<T> : ICustomerListIn<T> { //public T Get() //{ // return default(T); //} public void Show(T t) { } } /// <summary> /// out 協變 只能是返回結果 /// </summary> /// <typeparam name="T"></typeparam> public interface ICustomerListOut<out T> { T Get(); //void Show(T t);//不能做參數 } public class CustomerListOut<T> : ICustomerListOut<T> { public T Get() { return default(T); } //public void Show(T t) //{ //} }
{//協變:介面泛型參數加了個out,就是為瞭解決剛纔的不和諧 IEnumerable<Bird> birdList1 = new List<Bird>(); IEnumerable<Bird> birdList2 = new List<Sparrow>(); //Func<Bird> func = new Func<Sparrow>(() => null); ICustomerListOut<Bird> customerList1 = new CustomerListOut<Bird>(); ICustomerListOut<Bird> customerList2 = new CustomerListOut<Sparrow>(); } {//逆變 ICustomerListIn<Sparrow> customerList2 = new CustomerListIn<Sparrow>(); ICustomerListIn<Sparrow> customerList1 = new CustomerListIn<Bird>(); //customerList1.Show() ICustomerListIn<Bird> birdList1 = new CustomerListIn<Bird>(); birdList1.Show(new Sparrow()); birdList1.Show(new Bird()); Action<Sparrow> act = new Action<Bird>((Bird i) => { }); }
這樣可以了,協變IEnumerable加入協變Out 左邊是個父類,右邊可以是子類,逆變In 左邊是個字類,右邊也可以是父類,下麵這段就更暈了,稍微看下吧。
{ IMyList<Sparrow, Bird> myList1 = new MyList<Sparrow, Bird>(); IMyList<Sparrow, Bird> myList2 = new MyList<Sparrow, Sparrow>();//協變 IMyList<Sparrow, Bird> myList3 = new MyList<Bird, Bird>();//逆變 IMyList<Sparrow, Bird> myList4 = new MyList<Bird, Sparrow>();//協變+逆變 }
總結下原理:泛型在JIT編譯時指定具體類型,同一個泛型類,不同的類型參數,其實會變成不用的類型。
我走的很慢,但從不後退!