<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文看懂C#泛型運(yùn)作原理!

          共 24560字,需瀏覽 50分鐘

           ·

          2021-03-30 21:32

          前言

          ?我們都知道泛型在C#的重要性,泛型是OOP語(yǔ)言中三大特征的多態(tài)的最重要的體現(xiàn),幾乎泛型撐起了整個(gè).NET框架,在講泛型之前,我們可以拋出一個(gè)問(wèn)題,我們現(xiàn)在需要一個(gè)可擴(kuò)容的數(shù)組類,且滿足所有類型,不管是值類型還是引用類型,那么在沒(méi)有用泛型方法實(shí)現(xiàn),如何實(shí)現(xiàn)?

          一.泛型之前的故事

          ?我們肯定會(huì)想到用object來(lái)作為類型參數(shù),因?yàn)樵贑#中,所有類型都是基于Object類型的。因此Object是所有類型的最基類,那么我們的可擴(kuò)容數(shù)組類如下:

          Copy
          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)
          {
          //數(shù)組元素為0或者數(shù)組元素容量滿
          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;
          }
          }

          然后我們來(lái)驗(yàn)證下:

          Copy
          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] 運(yùn)行時(shí)報(bào)錯(cuò)
          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}");

          輸出:

          Copy
          ryzen
          reed
          wymen
          gavin
          Now arrayStr Capacity:4
          0
          1
          2
          3
          4
          Now array Capacity:8

          ?貌似輸出結(jié)果是正確的,能夠動(dòng)態(tài)進(jìn)行擴(kuò)容,同樣的支持值類型Structint32和引用類型的字符串,但是其實(shí)這里會(huì)發(fā)現(xiàn)一些問(wèn)題,那就是

          1. 引用類型string進(jìn)行了類型轉(zhuǎn)換的驗(yàn)證

          2. 值類型int32進(jìn)行了裝箱和拆箱操作,同時(shí)進(jìn)行類型轉(zhuǎn)換類型的檢驗(yàn)

          3. 發(fā)生的這一切都是在運(yùn)行時(shí)的,假如類型轉(zhuǎn)換錯(cuò)誤,得在運(yùn)行時(shí)才能報(bào)錯(cuò)

          大致執(zhí)行模型如下:

          引用類型:

          值類型:

          ?那么有沒(méi)有一種方法能夠避免上面遇到的三種問(wèn)題呢?在借鑒了cpp的模板和java的泛型經(jīng)驗(yàn),在C#2.0的時(shí)候推出了更適合.NET體系下的泛型

          二.用泛型實(shí)現(xiàn)

          Copy
          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)
          {
          //數(shù)組元素為0或者數(shù)組元素容量滿
          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;
          }
          }
          }

          那么測(cè)試代碼則改寫(xiě)為如下:

          Copy
          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] 編譯報(bào)錯(cuò)
          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}");

          輸出:

          Copy
          ryzen
          reed
          wymen
          gavin
          Now arrayStr Capacity:4
          0
          1
          2
          3
          4
          Now array Capacity:8

          我們通過(guò)截取部分ArrayExpandable<T>的IL查看其本質(zhì)是個(gè)啥:

          Copy
          //聲明類
          .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



          ?原來(lái)定義的時(shí)候就是用了個(gè)T作為占位符,起一個(gè)模板的作用,我們對(duì)其實(shí)例化類型參數(shù)的時(shí)候,補(bǔ)足那個(gè)占位符,我們可以在編譯期就知道了其類型,且不用在運(yùn)行時(shí)進(jìn)行類型檢測(cè),而我們也可以對(duì)比ArrayExpandableArrayExpandable<T>在類型為值類型中的IL,查看是否進(jìn)行拆箱和裝箱操作,以下為IL截取部分:

          ArrayExpandable:

          Copy
          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<T>:

          Copy
          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也能看的出來(lái),ArrayExpandable<T>T作為一個(gè)類型參數(shù),在編譯后在IL已經(jīng)確定了其類型,因此當(dāng)然也就不存在裝拆箱的情況,在編譯期的時(shí)候IDE能夠檢測(cè)類型,因此也就不用在運(yùn)行時(shí)進(jìn)行類型檢測(cè),但并不代表不能通過(guò)運(yùn)行時(shí)檢測(cè)類型(可通過(guò)is和as),還能通過(guò)反射體現(xiàn)出泛型的靈活性,后面會(huì)講到

          ?其實(shí)有了解ArrayListList的朋友就知道,ArrayExpandableArrayExpandable<T>其實(shí)現(xiàn)大致就是和它們一樣,只是簡(jiǎn)化了很多的版本,我們這里可以通過(guò) BenchmarkDotNet 來(lái)測(cè)試其性能對(duì)比,代碼如下:

          Copy
          [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;//確保進(jìn)行垃圾回收
          }

          [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;//確保進(jìn)行垃圾回收
          }

          [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;//確保進(jìn)行垃圾回收;
          }

          [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;//確保進(jìn)行垃圾回收;
          }

          [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;//確保進(jìn)行垃圾回收;
          }


          [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;//確保進(jìn)行垃圾回收;
          }

          [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;//確保進(jìn)行垃圾回收;
          }


          [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;//確保進(jìn)行垃圾回收;
          }
          }

          ?我還加入了.NETCore3.1和.NET5的對(duì)比,且以.NETCore3.1的EnumAraayList_valueType方法為基準(zhǔn),性能測(cè)試結(jié)果如下:

          用更直觀的柱形圖來(lái)呈現(xiàn):

          ?我們能看到在這里List的性能在引用類型和值類型中都是所以當(dāng)中是最好的,不管是執(zhí)行時(shí)間、GC次數(shù),分配的內(nèi)存空間大小,都是最優(yōu)的,同時(shí).NET5在幾乎所有的方法中性能都是優(yōu)于.NETCore3.1,這里還提一句,我實(shí)現(xiàn)的ArrayExpandableArrayExpandable<T>性能都差于ArrayListList,我還沒(méi)實(shí)現(xiàn)IList和各種方法,只能說(shuō)句dotnet基金會(huì)牛逼

          三.泛型的多態(tài)性

          多態(tài)的聲明

          類、結(jié)構(gòu)、接口、方法、和委托可以聲明一個(gè)或者多個(gè)類型參數(shù),我們直接看代碼:

          Copy
          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];//沒(méi)判斷越界


          public Foo(List<ClassT> classT, ClassT1 classT1)
          {
          Property = classT;
          Property1 = classT1;
          Field = classT1;
          Console.WriteLine($"構(gòu)造函數(shù):parameter1 type:{Property.GetType().Name},parameter2 type:{Property1.GetType().Name}");
          }

          //方法聲明了多個(gè)新的類型參數(shù)
          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());
          }
          }

          控制臺(tái)測(cè)試代碼:

          Copy
          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+"重載+運(yùn)算符");
          }

          static void DelegateMenthod(string str)
          {
          Console.WriteLine($"{nameof(DelegateMenthod)}:{str}");
          }


          輸出如下:

          Copy
          構(gòu)造函數(shù):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
          重載+運(yùn)算符:Foo`2

          我們通過(guò)例子可以看到的是:

          • 類(結(jié)構(gòu)也可以),接口,委托,方法都可以聲明一個(gè)或多個(gè)類型參數(shù),體現(xiàn)了聲明的多態(tài)性

          • 類的函數(shù)成員:屬性,字段,索引,構(gòu)造器,運(yùn)算符只能引入類聲明的類型參數(shù),不能夠聲明,唯有方法這一函數(shù)成員具備聲明和引用類型參數(shù)兩種功能,由于具備聲明功能,因此可以聲明和委托一樣的類型參數(shù)并且引用它,這也體現(xiàn)了方法的多態(tài)性

          多態(tài)的繼承

          父類和實(shí)現(xiàn)類或接口的接口都可以是實(shí)例化類型,直接看代碼:

          Copy
          interface IFooBase<IBaseT>{}

          interface IFoo<InterfaceT>: IFooBase<string>
          {
          void InterfaceMenthod(InterfaceT interfaceT);
          }

          class FooBase<ClassT>
          {

          }

          class Foo<ClassT, ClassT1>: FooBase<ClassT>,IFoo<StringBuilder>{}

          我們可以通過(guò)例子看出:

          • 由于Foo的基類FooBase定義的和Foo有著共享的類型參數(shù)ClassT,因此可以在繼承的時(shí)候不實(shí)例化類型

          • FooIFoo接口沒(méi)定義相同的類型參數(shù),因此可以在繼承的時(shí)候?qū)嵗鼋涌诘念愋蛥?shù)StringBuild出來(lái)

          • IFooIFooBase沒(méi)定義相同的類型參數(shù),因此可以在繼承的時(shí)候?qū)嵗鼋涌诘念愋蛥?shù)string出來(lái)

          • 上述都體現(xiàn)出繼承的多態(tài)性

          多態(tài)的遞歸

          我們定義如下一個(gè)類和一個(gè)方法,且不會(huì)報(bào)錯(cuò):

          Copy
          class D<T> { }
          class C<T> : D<C<C<T>>>
          {
          void Foo()
          {
          var foo = new C<C<T>>();
          Console.WriteLine(foo.ToString());
          }
          }

          因?yàn)?code style="margin-right: 3px;margin-left: 3px;line-height: 1;vertical-align: middle;display: inline-block;overflow-x: auto;padding: 0.2em 0.3em !important;font-family: consolas !important;font-size: 14px !important;background: rgb(242, 244, 245) !important;border-width: 1px !important;border-style: solid !important;border-color: rgb(238, 238, 238) !important;border-radius: 3px !important;">T能在實(shí)例化的時(shí)候確定其類型,因此也支持這種循環(huán)套用自己的類和方法的定義

          四.泛型的約束

          where的約束

          我們先上代碼:

          Copy
          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>();//通過(guò)編譯
          //var someClass = new someClass<string, Foo>();//編譯失敗,string不是struct類型
          //var someClass = new someClass<string, long>();//編譯失敗,long不是FooBase類型
          }


          再改動(dòng)下Foo類:

          Copy
          class Foo : FooBase
          {
          public Foo(string str)
          {

          }
          }

          static void TestConstraint()
          {
          var someClass = new someClass<int, Foo>();//編譯失敗,因?yàn)閚ew()約束必須類含有一個(gè)無(wú)參構(gòu)造器,可以再給Foo類加上個(gè)無(wú)參構(gòu)造器就能編譯通過(guò)
          }

          ?我們可以看到,通過(guò)where語(yǔ)句,可以對(duì)類型參數(shù)進(jìn)行約束,而且一個(gè)類型參數(shù)支持多個(gè)約束條件(例如K),使其在實(shí)例化類型參數(shù)的時(shí)候,必須按照約束的條件對(duì)應(yīng)實(shí)例符合條件的類型,而where條件約束的作用就是起在編譯期約束類型參數(shù)的作用

          out和in的約束

          ?說(shuō)到outin之前,我們可以說(shuō)下協(xié)變和逆變,在C#中,只有泛型接口和泛型委托可以支持協(xié)變和逆變

          協(xié)變

          我們先看下代碼:

          Copy
          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;//編譯失敗
          }

          ?這時(shí)候你可能會(huì)有點(diǎn)奇怪,為啥那段代碼會(huì)編譯失敗,明明Foo類可以隱式轉(zhuǎn)為FooBase,但作為泛型接口類型參數(shù)實(shí)例化卻并不能呢?使用out約束泛型接口IBar的T,那段代碼就會(huì)編譯正常,但是會(huì)引出另外一段編譯報(bào)錯(cuò):

          Copy
          interface IBar<out T>
          {
          T GetValue(string str);//編譯成功
          //T GetValue(T t);//編譯失敗 T不能作為形參輸入,用out約束T支持協(xié)變,T可以作為返回值輸出

          }

          IBar<Foo> bar = new Bar<Foo>();
          IBar<FooBase> bar1 = bar;//編譯正常

          因此我們可以得出以下結(jié)論:

          • 由于Foo繼承FooBase,本身子類Foo包含著父類允許訪問(wèn)的成員,因此能隱式轉(zhuǎn)換父類,這是類型安全的轉(zhuǎn)換,因此叫協(xié)變

          • 在為泛型接口用out標(biāo)識(shí)其類型參數(shù)支持協(xié)變后,約束其方法的返回值和屬性的Get(本質(zhì)也是個(gè)返回值的方法)才能引用所聲明的類型參數(shù),也就是作為輸出值,用out很明顯的突出了這一意思

          而支持迭代的泛型接口IEnumerable也是這么定義的:

          Copy
          public interface IEnumerable<out T> : IEnumerable
          {
          new IEnumerator<T> GetEnumerator();
          }

          逆變

          我們將上面代碼改下:

          Copy
          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;//編譯通過(guò),運(yùn)行時(shí)報(bào)錯(cuò)

          IBar<FooBase> bar = new Bar<FooBase>();
          IBar<Foo> bar1 = (IBar<Foo>)bar;//編譯通過(guò),運(yùn)行時(shí)報(bào)錯(cuò)
          }

          我們?cè)俑膭?dòng)下IBar,發(fā)現(xiàn)出現(xiàn)另外一處編譯失敗

          Copy
          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;//編譯通過(guò),運(yùn)行時(shí)不報(bào)錯(cuò)
          IBar<Foo> bar1 = bar;//編譯通過(guò),運(yùn)行時(shí)不報(bào)錯(cuò)

          因此我們可以得出以下結(jié)論:

          • 由于FooBaseFoo的父類,并不包含子類的自由的成員,轉(zhuǎn)為為子類Foo是類型不安全的,因此在運(yùn)行時(shí)強(qiáng)式轉(zhuǎn)換的報(bào)錯(cuò)了,但編譯期是不能夠確認(rèn)的

          • 在為泛型接口用in標(biāo)識(shí)其類型參數(shù)支持逆變后,in約束其接口成員不能將其作為返回值(輸出值),我們會(huì)發(fā)現(xiàn)協(xié)變和逆變正是一對(duì)反義詞

          • 這里提一句,值類型是不支持協(xié)變和逆變的

          同樣的泛型委托Action就是個(gè)逆變的例子:

          Copy
          public delegate void Action<in T>(T obj);

          五.泛型的反射

          我們先來(lái)看看以下代碼:

          Copy
          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");
          }

          然后通過(guò)ildasm查看其IL,開(kāi)啟視圖-》顯示標(biāo)記值,查看Main方法:

          Copy
          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

          打開(kāi)元數(shù)據(jù)表將上面所涉及到的元數(shù)據(jù)定義表和類型規(guī)格表列出:

          metainfo:

          Copy
          -----------定義部分
          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)


          ------類型規(guī)格部分
          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

          ?這時(shí)候我們就可以看出,元數(shù)據(jù)為泛型類ArrayExpandable<T>定義一份定義表,生成兩份規(guī)格,也就是當(dāng)你實(shí)例化類型參數(shù)為intstring的時(shí)候,分別生成了兩份規(guī)格代碼,同時(shí)還發(fā)現(xiàn)以下的現(xiàn)象:

          Copy
          var lsInt = new ArrayExpandable<int>();//引用的是類型規(guī)格1b000001的成員0a00000c .ctor構(gòu)造
          lsInt.Add(1);//引用的是類型規(guī)格1b000001的成員0a00000d Add

          var lsStr = new ArrayExpandable<string>();//引用的是類型規(guī)格1b000002的成員0a00000e .ctor構(gòu)造
          lsStr.Add("ryzen");//引用的是類型規(guī)格1b000002的成員0a00000f Add
          var lsStr1 = new ArrayExpandable<string>();//和lsStr一樣
          lsStr.Add("ryzen");//和lsStr一樣


          ?非常妙的是,當(dāng)你實(shí)例化兩個(gè)一樣的類型參數(shù)string,是共享一份類型規(guī)格的,也就是同享一份本地代碼,因此上面的代碼在線程堆棧和托管堆的大致是這樣的:

          由于泛型也有元數(shù)據(jù)的存在,因此可以對(duì)其做反射:

          Copy
          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}");
          }

          輸出:

          Copy
          -----------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

          六.總結(jié)

          ?泛型編程作為.NET體系中一個(gè)很重要的編程思想,主要有以下亮點(diǎn):

          • 編譯期確定類型,避免值類型的拆裝箱和不必要的運(yùn)行時(shí)類型檢驗(yàn),同樣運(yùn)行時(shí)也能通過(guò)isas進(jìn)行類型檢驗(yàn)

          • 通過(guò)約束進(jìn)行對(duì)類型參數(shù)實(shí)例化的范圍

          • 同時(shí)在IL層面,實(shí)例化相同類型參數(shù)的時(shí)候共享一份本地代碼

          • 由于元數(shù)據(jù)的存在,也能在運(yùn)行時(shí)進(jìn)行反射,增強(qiáng)其靈活性

          參考

          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(第二版)》

          回復(fù) 【關(guān)閉】學(xué)關(guān)
          回復(fù) 【實(shí)戰(zhàn)】獲取20套實(shí)戰(zhàn)源碼
          回復(fù) 【被刪】學(xué)個(gè)
          回復(fù) 【訪客】學(xué)
          回復(fù) 【小程序】學(xué)獲取15套【入門(mén)+實(shí)戰(zhàn)+賺錢】小程序源碼
          回復(fù) 【python】學(xué)微獲取全套0基礎(chǔ)Python知識(shí)手冊(cè)
          回復(fù) 【2019】獲取2019 .NET 開(kāi)發(fā)者峰會(huì)資料PPT
          回復(fù) 【加群】加入dotnet微信交流群

          技巧:微信可以設(shè)置雪花昵稱了!


          臥槽:微信又能免費(fèi)提現(xiàn)了!



          瀏覽 84
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  影音先锋一区二区三区 | 丁香五月色 | 亚洲AV成人无码www在线观看 | 中国老年人A毛片视频 | 小泽玛利亚无码视频 |