咱們都知道泛型在C#的重要性,泛型是OOP語言中三大特徵的多態的最重要的體現,幾乎泛型撐起了整個.NET框架,在講泛型以前,咱們能夠拋出一個問題,咱們如今須要一個可擴容的數組類,且知足全部類型,不論是值類型仍是引用類型,那麼在沒有用泛型方法實現,如何實現?java
咱們確定會想到用object
來做爲類型參數,由於在C#中,全部類型都是基於Object
類型的。所以Object是全部類型的最基類,那麼咱們的可擴容數組類以下:git
public class ArrayExpandable { private object?[] _items = null; private int _defaultCapacity = 4; private int _size; public object? this[int index] { get { if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException(nameof(index)); return _items[index]; } set { if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException(nameof(index)); _items[index] = value; } } public int Capacity { get => _items.Length; set { if (value < _size) { throw new ArgumentOutOfRangeException(nameof(value)); } if (value != _items.Length) { if (value > 0) { object[] newItems = new object[value]; if (_size > 0) { Array.Copy(_items, newItems, _size); } _items = newItems; } else { _items = new object[_defaultCapacity]; } } } } public int Count => _size; public ArrayExpandable() { _items = new object?[0]; } public ArrayExpandable(int capacity) { _items = new object?[capacity]; } public void Add(object? value) { //數組元素爲0或者數組元素容量滿 if (_size == _items.Length) EnsuresCapacity(_size + 1); _items[_size] = value; _size++; } private void EnsuresCapacity(int size) { if (_items.Length < size) { int newCapacity = _items.Length == 0 ? _defaultCapacity : _items.Length * 2; if (newCapacity < size) newCapacity = size; Capacity = newCapacity; } }
而後咱們來驗證下:github
var arrayStr = new ArrayExpandable(); var strs = new string[] { "ryzen", "reed", "wymen" }; for (int i = 0; i < strs.Length; i++) { arrayStr.Add(strs[i]); string value = (string)arrayStr[i];//改成int value = (int)arrayStr[i] 運行時報錯 Console.WriteLine(value); } Console.WriteLine($"Now {nameof(arrayStr)} Capacity:{arrayStr.Capacity}"); var array = new ArrayExpandable(); for (int i = 0; i < 5; i++) { array.Add(i); int value = (int)array[i]; Console.WriteLine(value); } Console.WriteLine($"Now {nameof(array)} Capacity:{array.Capacity}");
輸出:編程
ryzen reed wymen gavin Now arrayStr Capacity:4 0 1 2 3 4 Now array Capacity:8
貌似輸出結果是正確的,可以動態進行擴容,一樣的支持值類型Struct
的int32
和引用類型的字符串,可是其實這裏會發現一些問題,那就是c#
string
進行了類型轉換的驗證int32
進行了裝箱和拆箱操做,同時進行類型轉換類型的檢驗大體執行模型以下:數組
引用類型:安全
值類型:app
那麼有沒有一種方法可以避免上面遇到的三種問題呢?在借鑑了cpp的模板和java的泛型經驗,在C#2.0的時候推出了更適合.NET體系下的泛型框架
public class ArrayExpandable<T> { private T[] _items; private int _defaultCapacity = 4; private int _size; public T this[int index] { get { if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException(nameof(index)); return _items[index]; } set { if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException(nameof(index)); _items[index] = value; } } public int Capacity { get => _items.Length; set { if (value < _size) { throw new ArgumentOutOfRangeException(nameof(value)); } if (value != _items.Length) { if (value > 0) { T[] newItems = new T[value]; if (_size > 0) { Array.Copy(_items, newItems, _size); } _items = newItems; } else { _items = new T[_defaultCapacity]; } } } } public int Count => _size; public ArrayExpandable() { _items = new T[0]; } public ArrayExpandable(int capacity) { _items = new T[capacity]; } public void Add(T value) { //數組元素爲0或者數組元素容量滿 if (_size == _items.Length) EnsuresCapacity(_size + 1); _items[_size] = value; _size++; } private void EnsuresCapacity(int size) { if (_items.Length < size) { int newCapacity = _items.Length == 0 ? _defaultCapacity : _items.Length * 2; if (newCapacity < size) newCapacity = size; Capacity = newCapacity; } } }
那麼測試代碼則改寫爲以下:ide
var arrayStr = new ArrayExpandable<string>(); var strs = new string[] { "ryzen", "reed", "wymen", "gavin" }; for (int i = 0; i < strs.Length; i++) { arrayStr.Add(strs[i]); string value = arrayStr[i];//改成int value = arrayStr[i] 編譯報錯 Console.WriteLine(value); } Console.WriteLine($"Now {nameof(arrayStr)} Capacity:{arrayStr.Capacity}"); var array = new ArrayExpandable<int>(); for (int i = 0; i < 5; i++) { array.Add(i); int value = array[i]; Console.WriteLine(value); } Console.WriteLine($"Now {nameof(array)} Capacity:{array.Capacity}");
輸出:
ryzen reed wymen gavin Now arrayStr Capacity:4 0 1 2 3 4 Now array Capacity:8
咱們經過截取部分ArrayExpandable<T>
的IL查看其本質是個啥:
//聲明類 .class public auto ansi beforefieldinit MetaTest.ArrayExpandable`1<T> extends [System.Runtime]System.Object { .custom instance void [System.Runtime]System.Reflection.DefaultMemberAttribute::.ctor(string) = ( 01 00 04 49 74 65 6D 00 00 ) } //Add方法 .method public hidebysig instance void Add(!T 'value') cil managed { // 代碼大小 69 (0x45) .maxstack 3 .locals init (bool V_0) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldfld int32 class MetaTest.ArrayExpandable`1<!T>::_size IL_0007: ldarg.0 IL_0008: ldfld !0[] class MetaTest.ArrayExpandable`1<!T>::_items IL_000d: ldlen IL_000e: conv.i4 IL_000f: ceq IL_0011: stloc.0 IL_0012: ldloc.0 IL_0013: brfalse.s IL_0024 IL_0015: ldarg.0 IL_0016: ldarg.0 IL_0017: ldfld int32 class MetaTest.ArrayExpandable`1<!T>::_size IL_001c: ldc.i4.1 IL_001d: add IL_001e: call instance void class MetaTest.ArrayExpandable`1<!T>::EnsuresCapacity(int32) IL_0023: nop IL_0024: ldarg.0 IL_0025: ldfld !0[] class MetaTest.ArrayExpandable`1<!T>::_items IL_002a: ldarg.0 IL_002b: ldfld int32 class MetaTest.ArrayExpandable`1<!T>::_size IL_0030: ldarg.1 IL_0031: stelem !T IL_0036: ldarg.0 IL_0037: ldarg.0 IL_0038: ldfld int32 class MetaTest.ArrayExpandable`1<!T>::_size IL_003d: ldc.i4.1 IL_003e: add IL_003f: stfld int32 class MetaTest.ArrayExpandable`1<!T>::_size IL_0044: ret } // end of method ArrayExpandable`1::Add
原來定義的時候就是用了個T
做爲佔位符,起一個模板的做用,咱們對其實例化類型參數的時候,補足那個佔位符,咱們能夠在編譯期就知道了其類型,且不用在運行時進行類型檢測,而咱們也能夠對比ArrayExpandable
和ArrayExpandable<T>
在類型爲值類型中的IL,查看是否進行拆箱和裝箱操做,如下爲IL截取部分:
ArrayExpandable:
IL_0084: newobj instance void GenericSample.ArrayExpandable::.ctor() IL_0089: stloc.2 IL_008a: ldc.i4.0 IL_008b: stloc.s V_6 IL_008d: br.s IL_00bc IL_008f: nop IL_0090: ldloc.2 IL_0091: ldloc.s V_6 IL_0093: box [System.Runtime]System.Int32 //box爲裝箱操做 IL_0098: callvirt instance void GenericSample.ArrayExpandable::Add(object) IL_009d: nop IL_009e: ldloc.2 IL_009f: ldloc.s V_6 IL_00a1: callvirt instance object GenericSample.ArrayExpandable::get_Item(int32) IL_00a6: unbox.any [System.Runtime]System.Int32 //unbox爲拆箱操做
ArrayExpandable
IL_007f: newobj instance void class GenericSample.ArrayExpandable`1<int32>::.ctor() IL_0084: stloc.2 IL_0085: ldc.i4.0 IL_0086: stloc.s V_6 IL_0088: br.s IL_00ad IL_008a: nop IL_008b: ldloc.2 IL_008c: ldloc.s V_6 IL_008e: callvirt instance void class GenericSample.ArrayExpandable`1<int32>::Add(!0) IL_0093: nop IL_0094: ldloc.2 IL_0095: ldloc.s V_6 IL_0097: callvirt instance !0 class GenericSample.ArrayExpandable`1<int32>::get_Item(int32)
咱們從IL也能看的出來,ArrayExpandable<T>
的T
做爲一個類型參數,在編譯後在IL已經肯定了其類型,所以固然也就不存在裝拆箱的狀況,在編譯期的時候IDE可以檢測類型,所以也就不用在運行時進行類型檢測,但並不表明不能經過運行時檢測類型(可經過is和as),還能經過反射體現出泛型的靈活性,後面會講到
其實有了解ArrayList
和List
的朋友就知道,ArrayExpandable
和ArrayExpandable<T>
其實現大體就是和它們同樣,只是簡化了不少的版本,咱們這裏能夠經過 BenchmarkDotNet 來測試其性能對比,代碼以下:
[SimpleJob(RuntimeMoniker.NetCoreApp31,baseline:true)] [SimpleJob(RuntimeMoniker.NetCoreApp50)] [MemoryDiagnoser] public class TestClass { [Benchmark] public void EnumAE_ValueType() { ArrayExpandable array = new ArrayExpandable(); for (int i = 0; i < 10000; i++) { array.Add(i);//裝箱 int value = (int)array[i];//拆箱 } array = null;//確保進行垃圾回收 } [Benchmark] public void EnumAE_RefType() { ArrayExpandable array = new ArrayExpandable(); for (int i = 0; i < 10000; i++) { array.Add("r"); string value = (string)array[i]; } array = null;//確保進行垃圾回收 } [Benchmark] public void EnumAE_Gen_ValueType() { ArrayExpandable<int> array = new ArrayExpandable<int>(); for (int i = 0; i < 10000; i++) { array.Add(i); int value = array[i]; } array = null;//確保進行垃圾回收; } [Benchmark] public void EnumAE_Gen_RefType() { ArrayExpandable<string> array = new ArrayExpandable<string>(); for (int i = 0; i < 10000; i++) { array.Add("r"); string value = array[i]; } array = null;//確保進行垃圾回收; } [Benchmark] public void EnumList_ValueType() { List<int> array = new List<int>(); for (int i = 0; i < 10000; i++) { array.Add(i); int value = array[i]; } array = null;//確保進行垃圾回收; } [Benchmark] public void EnumList_RefType() { List<string> array = new List<string>(); for (int i = 0; i < 10000; i++) { array.Add("r"); string value = array[i]; } array = null;//確保進行垃圾回收; } [Benchmark(Baseline =true)] public void EnumAraayList_valueType() { ArrayList array = new ArrayList(); for (int i = 0; i < 10000; i++) { array.Add(i); int value = (int)array[i]; } array = null;//確保進行垃圾回收; } [Benchmark] public void EnumAraayList_RefType() { ArrayList array = new ArrayList(); for (int i = 0; i < 10000; i++) { array.Add("r"); string value = (string)array[i]; } array = null;//確保進行垃圾回收; } }
我還加入了.NETCore3.1和.NET5的對比,且以.NETCore3.1的EnumAraayList_valueType
方法爲基準,性能測試結果以下:
用更直觀的柱形圖來呈現:
咱們能看到在這裏List
的性能在引用類型和值類型中都是因此當中是最好的,不論是執行時間、GC次數,分配的內存空間大小,都是最優的,同時.NET5在幾乎全部的方法中性能都是優於.NETCore3.1,這裏還提一句,我實現的ArrayExpandable
和ArrayExpandable<T>
性能都差於ArrayList
和List
,我還沒實現IList
和各類方法,只能說句dotnet基金會牛逼
類、結構、接口、方法、和委託能夠聲明一個或者多個類型參數,咱們直接看代碼:
interface IFoo<InterfaceT> { void InterfaceMenthod(InterfaceT interfaceT); } class Foo<ClassT, ClassT1>: IFoo<StringBuilder> { public ClassT1 Field; public delegate void MyDelegate<DelegateT>(DelegateT delegateT); public void DelegateMenthod<DelegateT>(DelegateT delegateT, MyDelegate<DelegateT> myDelegate) { myDelegate(delegateT); } public static string operator +(Foo<ClassT, ClassT1> foo,string s) { return $"{s}:{foo.GetType().Name}"; } public List<ClassT> Property{ get; set; } public ClassT1 Property1 { get; set; } public ClassT this[int index] => Property[index];//沒判斷越界 public Foo(List<ClassT> classT, ClassT1 classT1) { Property = classT; Property1 = classT1; Field = classT1; Console.WriteLine($"構造函數:parameter1 type:{Property.GetType().Name},parameter2 type:{Property1.GetType().Name}"); } //方法聲明瞭多個新的類型參數 public void Method<MenthodT, MenthodT1>(MenthodT menthodT, MenthodT1 menthodT1) { Console.WriteLine($"Method<MenthodT, MenthodT1>:{(menthodT.GetType().Name)}:{menthodT.ToString()}," + $"{menthodT1.GetType().Name}:{menthodT1.ToString()}"); } public void Method(ClassT classT) { Console.WriteLine($"{nameof(Method)}:{classT.GetType().Name}:classT?.ToString()"); } public void InterfaceMenthod(StringBuilder interfaceT) { Console.WriteLine(interfaceT.ToString()); } }
控制檯測試代碼:
static void Main(string[] args) { Test(); Console.ReadLine(); } static void Test() { var list = new List<int>() { 1, 2, 3, 4 }; var foo = new Foo<int, string>(list, "ryzen"); var index = 0; Console.WriteLine($"索引:索引{index}的值:{foo[index]}"); Console.WriteLine($"Filed:{foo.Field}"); foo.Method(2333); foo.Method<DateTime, long>(DateTime.Now, 2021); foo.DelegateMenthod<string>("this is a delegate", DelegateMenthod); foo.InterfaceMenthod(new StringBuilder().Append("InterfaceMenthod:this is a interfaceMthod")); Console.WriteLine(foo+"重載+運算符"); } static void DelegateMenthod(string str) { Console.WriteLine($"{nameof(DelegateMenthod)}:{str}"); }
輸出以下:
構造函數:parameter1 type:List`1,parameter2 type:String 索引:索引0的值:1 Filed:ryzen Method:Int32:classT?.ToString() Method<MenthodT, MenthodT1>:DateTime:2021/03/02 11:45:40,Int64:2021 DelegateMenthod:this is a delegate InterfaceMenthod:this is a interfaceMthod 重載+運算符:Foo`2
咱們經過例子能夠看到的是:
父類和實現類或接口的接口均可以是實例化類型,直接看代碼:
interface IFooBase<IBaseT>{} interface IFoo<InterfaceT>: IFooBase<string> { void InterfaceMenthod(InterfaceT interfaceT); } class FooBase<ClassT> { } class Foo<ClassT, ClassT1>: FooBase<ClassT>,IFoo<StringBuilder>{}
咱們能夠經過例子看出:
Foo
的基類FooBase
定義的和Foo
有着共享的類型參數ClassT
,所以能夠在繼承的時候不實例化類型Foo
和IFoo
接口沒定義相同的類型參數,所以能夠在繼承的時候實例化出接口的類型參數StringBuild
出來IFoo
和IFooBase
沒定義相同的類型參數,所以能夠在繼承的時候實例化出接口的類型參數string
出來咱們定義以下一個類和一個方法,且不會報錯:
class D<T> { } class C<T> : D<C<C<T>>> { void Foo() { var foo = new C<C<T>>(); Console.WriteLine(foo.ToString()); } }
由於T
能在實例化的時候肯定其類型,所以也支持這種循環套用本身的類和方法的定義
咱們先上代碼:
class FooBase{ } class Foo : FooBase { } class someClass<T,K> where T:struct where K :FooBase,new() { } static void TestConstraint() { var someClass = new someClass<int, Foo>();//經過編譯 //var someClass = new someClass<string, Foo>();//編譯失敗,string不是struct類型 //var someClass = new someClass<string, long>();//編譯失敗,long不是FooBase類型 }
再改動下Foo類:
class Foo : FooBase { public Foo(string str) { } } static void TestConstraint() { var someClass = new someClass<int, Foo>();//編譯失敗,由於new()約束必須類含有一個無參構造器,能夠再給Foo類加上個無參構造器就能編譯經過 }
咱們能夠看到,經過where
語句,能夠對類型參數進行約束,並且一個類型參數支持多個約束條件(例如K),使其在實例化類型參數的時候,必須按照約束的條件對應實例符合條件的類型,而where
條件約束的做用就是起在編譯期約束類型參數的做用
說到out
和in
以前,咱們能夠說下協變和逆變,在C#中,只有泛型接口和泛型委託能夠支持協變和逆變
咱們先看下代碼:
class FooBase{ } class Foo : FooBase { } interface IBar<T> { T GetValue(T t); } class Bar<T> : IBar<T> { public T GetValue(T t) { return t; } } static void Test() { var foo = new Foo(); FooBase fooBase = foo;//編譯成功 IBar<Foo> bar = new Bar<Foo>(); IBar<FooBase> bar1 = bar;//編譯失敗 }
這時候你可能會有點奇怪,爲啥那段代碼會編譯失敗,明明Foo
類能夠隱式轉爲FooBase
,但做爲泛型接口類型參數實例化卻並不能呢?使用out
約束泛型接口IBar
的T,那段代碼就會編譯正常,可是會引出另一段編譯報錯:
interface IBar<out T> { T GetValue(string str);//編譯成功 //T GetValue(T t);//編譯失敗 T不能做爲形參輸入,用out約束T支持協變,T能夠做爲返回值輸出 } IBar<Foo> bar = new Bar<Foo>(); IBar<FooBase> bar1 = bar;//編譯正常
所以咱們能夠得出如下結論:
Foo
繼承FooBase
,自己子類Foo
包含着父類容許訪問的成員,所以能隱式轉換父類,這是類型安全的轉換,所以叫協變out
標識其類型參數支持協變後,約束其方法的返回值和屬性的Get(本質也是個返回值的方法)才能引用所聲明的類型參數,也就是做爲輸出值,用out
很明顯的突出了這一意思而支持迭代的泛型接口IEnumerable
也是這麼定義的:
public interface IEnumerable<out T> : IEnumerable { new IEnumerator<T> GetEnumerator(); }
咱們將上面代碼改下:
class FooBase{ } class Foo : FooBase { } interface IBar<T> { T GetValue(T t); } class Bar<T> : IBar<T> { public T GetValue(T t) { return t; } } static void Test1() { var fooBase = new FooBase(); Foo foo = (Foo)fooBase;//編譯經過,運行時報錯 IBar<FooBase> bar = new Bar<FooBase>(); IBar<Foo> bar1 = (IBar<Foo>)bar;//編譯經過,運行時報錯 }
咱們再改動下IBar,發現出現另一處編譯失敗
interface IBar<in T> { void GetValue(T t);//編譯成功 //T GetValue(T t);//編譯失敗 T不能做爲返回值輸出,用in約束T支持逆變,T能夠做爲返回值輸出 } IBar<FooBase> bar = new Bar<FooBase>(); IBar<Foo> bar1 = (IBar<Foo>)bar;//編譯經過,運行時不報錯 IBar<Foo> bar1 = bar;//編譯經過,運行時不報錯
所以咱們能夠得出如下結論:
FooBase
是Foo
的父類,並不包含子類的自由的成員,轉爲爲子類Foo
是類型不安全的,所以在運行時強式轉換的報錯了,但編譯期是不可以確認的in
標識其類型參數支持逆變後,in
約束其接口成員不能將其做爲返回值(輸出值),咱們會發現協變和逆變正是一對反義詞一樣的泛型委託Action
就是個逆變的例子:
public delegate void Action<in T>(T obj);
咱們先來看看如下代碼:
static void Main(string[] args) { var lsInt = new ArrayExpandable<int>(); lsInt.Add(1); var lsStr = new ArrayExpandable<string>(); lsStr.Add("ryzen"); var lsStr1 = new ArrayExpandable<string>(); lsStr.Add("ryzen"); }
而後經過ildasm查看其IL,開啓視圖-》顯示標記值,查看Main方法:
void Main(string[] args) cil managed { .entrypoint // 代碼大小 52 (0x34) .maxstack 2 .locals /*11000001*/ init (class MetaTest.ArrayExpandable`1/*02000003*/<int32> V_0, class MetaTest.ArrayExpandable`1/*02000003*/<string> V_1, class MetaTest.ArrayExpandable`1/*02000003*/<string> V_2) IL_0000: nop IL_0001: newobj instance void class MetaTest.ArrayExpandable`1/*02000003*/<int32>/*1B000001*/::.ctor() /* 0A00000C */ IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldc.i4.1 IL_0009: callvirt instance void class MetaTest.ArrayExpandable`1/*02000003*/<int32>/*1B000001*/::Add(!0) /* 0A00000D */ IL_000e: nop IL_000f: newobj instance void class MetaTest.ArrayExpandable`1/*02000003*/<string>/*1B000002*/::.ctor() /* 0A00000E */ IL_0014: stloc.1 IL_0015: ldloc.1 IL_0016: ldstr "ryzen" /* 70000001 */ IL_001b: callvirt instance void class MetaTest.ArrayExpandable`1/*02000003*/<string>/*1B000002*/::Add(!0) /* 0A00000F */ IL_0020: nop IL_0021: newobj instance void class MetaTest.ArrayExpandable`1/*02000003*/<string>/*1B000002*/::.ctor() /* 0A00000E */ IL_0026: stloc.2 IL_0027: ldloc.1 IL_0028: ldstr "ryzen" /* 70000001 */ IL_002d: callvirt instance void class MetaTest.ArrayExpandable`1/*02000003*/<string>/*1B000002*/::Add(!0) /* 0A00000F */ IL_0032: nop IL_0033: ret } // end of method Program::Main
打開元數據表將上面所涉及到的元數據定義表和類型規格表列出:
metainfo:
-----------定義部分 TypeDef #2 (02000003) ------------------------------------------------------- TypDefName: MetaTest.ArrayExpandable`1 (02000003) Flags : [Public] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit] (00100001) Extends : 0100000C [TypeRef] System.Object 1 Generic Parameters (0) GenericParamToken : (2a000001) Name : T flags: 00000000 Owner: 02000003 Method #8 (0600000a) ------------------------------------------------------- MethodName: Add (0600000A) Flags : [Public] [HideBySig] [ReuseSlot] (00000086) RVA : 0x000021f4 ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: Var!0 1 Parameters (1) ParamToken : (08000007) Name : value flags: [none] (00000000) ------類型規格部分 TypeSpec #1 (1b000001) ------------------------------------------------------- TypeSpec : GenericInst Class MetaTest.ArrayExpandable`1< I4> //14表明int32 MemberRef #1 (0a00000c) ------------------------------------------------------- Member: (0a00000c) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. MemberRef #2 (0a00000d) ------------------------------------------------------- Member: (0a00000d) Add: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: Var!0 TypeSpec #2 (1b000002) ------------------------------------------------------- TypeSpec : GenericInst Class MetaTest.ArrayExpandable`1< String> MemberRef #1 (0a00000e) ------------------------------------------------------- Member: (0a00000e) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. MemberRef #2 (0a00000f) ------------------------------------------------------- Member: (0a00000f) Add: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: Var!0
這時候咱們就能夠看出,元數據爲泛型類ArrayExpandable<T>
定義一份定義表,生成兩份規格,也就是當你實例化類型參數爲int
和string
的時候,分別生成了兩份規格代碼,同時還發現如下的現象:
var lsInt = new ArrayExpandable<int>();//引用的是類型規格1b000001的成員0a00000c .ctor構造 lsInt.Add(1);//引用的是類型規格1b000001的成員0a00000d Add var lsStr = new ArrayExpandable<string>();//引用的是類型規格1b000002的成員0a00000e .ctor構造 lsStr.Add("ryzen");//引用的是類型規格1b000002的成員0a00000f Add var lsStr1 = new ArrayExpandable<string>();//和lsStr同樣 lsStr.Add("ryzen");//和lsStr同樣
很是妙的是,當你實例化兩個同樣的類型參數string
,是共享一份類型規格的,也就是同享一份本地代碼,所以上面的代碼在線程堆棧和託管堆的大體是這樣的:
因爲泛型也有元數據的存在,所以能夠對其作反射:
Console.WriteLine($"-----------{nameof(lsInt)}---------------"); Console.WriteLine($"{nameof(lsInt)} is generic?:{lsInt.GetType().IsGenericType}"); Console.WriteLine($"Generic type:{lsInt.GetType().GetGenericArguments()[0].Name}"); Console.WriteLine("---------Menthods:"); foreach (var method in lsInt.GetType().GetMethods()) { Console.WriteLine(method.Name); } Console.WriteLine("---------Properties:"); foreach (var property in lsInt.GetType().GetProperties()) { Console.WriteLine($"{property.PropertyType.ToString()}:{property.Name}"); } Console.WriteLine($"\n-----------{nameof(lsStr)}---------------"); Console.WriteLine($"{nameof(lsStr)} is generic?:{lsStr.GetType().IsGenericType}"); Console.WriteLine($"Generic type:{lsStr.GetType().GetGenericArguments()[0].Name}"); Console.WriteLine("---------Menthods:"); foreach (var method in lsStr.GetType().GetMethods()) { Console.WriteLine(method.Name); } Console.WriteLine("---------Properties:"); foreach (var property in lsStr.GetType().GetProperties()) { Console.WriteLine($"{property.PropertyType.ToString()}:{property.Name}"); }
輸出:
-----------lsInt--------------- lsInt is generic?:True Generic type:Int32 ---------Menthods: get_Item set_Item get_Capacity set_Capacity get_Count Add GetType ToString Equals GetHashCode ---------Properties: System.Int32:Item System.Int32:Capacity System.Int32:Count -----------lsStr--------------- lsStr is generic?:True Generic type:String ---------Menthods: get_Item set_Item get_Capacity set_Capacity get_Count Add GetType ToString Equals GetHashCode ---------Properties: System.String:Item System.Int32:Capacity System.Int32:Count
泛型編程做爲.NET體系中一個很重要的編程思想,主要有如下亮點:
is
和as
進行類型檢驗Design and Implementation of Generics for the .NET Common Language Runtime
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/
《CLR Via C# 第四版》
《你必須知道的.NET(第二版)》