欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#基礎(chǔ)之泛型

 更新時(shí)間:2016年08月09日 10:51:14   作者:方小白  
泛型是 2.0 版 C# 語(yǔ)言和公共語(yǔ)言運(yùn)行庫(kù) (CLR) 中的一個(gè)新功能。接下來(lái)通過(guò)本文給大家介紹c#基礎(chǔ)之泛型,感興趣的朋友一起學(xué)習(xí)吧

1.泛型的本質(zhì)

  泛型的好處不用多說(shuō),在.NET中我看到有很多技術(shù)都是以泛型為基礎(chǔ)的,不過(guò)因?yàn)椴欢盒投荒軐?duì)那些技術(shù)一臉茫然。泛型主要用于集合類,最主要的原因是它不需要裝箱拆箱且類型安全,比如很常用的List<T>。對(duì)于List<T>我以后還想進(jìn)行深究,現(xiàn)在我寫了一個(gè)超簡(jiǎn)版的MyList<T>集合,如下面第一段代碼所示。代碼很簡(jiǎn)單,但在寫的過(guò)程中有一個(gè)細(xì)節(jié),如果我為listInt賦值string類型的變量時(shí)編譯器會(huì)提示報(bào)錯(cuò)。編譯器很智能,但是從這個(gè)現(xiàn)象中你會(huì)不會(huì)好奇泛型中的T是在什么情況下指定的呢,是生成IL時(shí)還是JIT動(dòng)態(tài)編譯時(shí)?老方法我將exe放入Reflector工具中,發(fā)現(xiàn)IL代碼中全是T,這說(shuō)明在編譯時(shí)T僅僅只是一個(gè)占位符,真真的替換是在運(yùn)行時(shí)動(dòng)態(tài)替換的。可是在寫泛型類時(shí)代碼只有一份,那我為MyList創(chuàng)建int、string類型的對(duì)象時(shí)這個(gè)代碼是如何公用的呢?對(duì)于值類型集合比如listInt,由于最終需要替換T,那么肯定是有一份完整的代碼里面T被替換為int。對(duì)于引用類型,因?yàn)樽兞恐皇且粋€(gè)指向堆中的指針,因此代碼只有一份??偨Y(jié)起來(lái)就是值類型代碼有多份而引用類型代碼只有一份,另外編寫自定義泛型代碼時(shí)最好使用有意義的T,比如.net中常見的TResult表示返回值,這樣可讀性較好。

class Program
 {
  static void Main(string[] args)
  {
   MyList<int> listInt = new MyList<int>();
   MyList<string> listString = new MyList<string>();
   listInt.Add(24);
   listInt[1] = 5;
   listString[2] = "ha ha";
  }
 }
 public class MyList<T>
 {
  T[] array;
  int current = -1;
  public MyList()
  {
   array = new T[10];
  }
  public void Add(T t)
  {
   current++;
   if (current < 10)
    array[current] = t;
  }
  public T this[int index]
  {
   get
   {
    return array[index];
   }
   set
   {
    array[index] = value;
   }
  }
 }

2.泛型規(guī)范

  這個(gè)很重要,主要包括約束和default。.NET是推薦我們開發(fā)者盡可能的多使用約束,因?yàn)榧s束越多越可以保證程序不會(huì)出錯(cuò)。泛型約束由where指定,六種約束如下所示。這些約束可以單獨(dú)使用也可以一起使用,但也有不能一起使用的比如值類型與引用類型約束。關(guān)于default的作用我們可以思考這樣一個(gè)問(wèn)題,如果在泛型類中我們需要初始化一個(gè)T變量。因?yàn)門既有可能是值類型也有可能是引用類型,所以不能直接用new或等于0。那如何判斷T是值類型還是引用類型呢?這里就要用到default,對(duì)于引用類型default(T)將返回null,對(duì)于數(shù)值類型default(T)將返回0。這里之所以寫數(shù)值類型是因?yàn)橹殿愋瓦€可能是結(jié)構(gòu)體,default會(huì)將結(jié)構(gòu)體中的成員初始化為0或null。還有一種特殊情況就是可空值類型,此時(shí)將返回Nullable<T>,這樣初始變量直接使用T t=default(T)就可以了。雖然泛型類給人帶來(lái)了神秘感,不過(guò)運(yùn)行時(shí)它的本質(zhì)就是一個(gè)普通的類,因此依舊具有類的特性比如繼承。這為我們開發(fā)者帶來(lái)了很多好處,比如我想要有一個(gè)int集合類,它除了有List<int>的功能外還有自定義的某些功能,這時(shí)候只需MyList : List<int>就可以得到想要的效果了,非常方便。

where T : struct 值類型約束,T必須為值類型。

where T:class 引用類型約束,T必須為引用類型。

where T:new() 構(gòu)造器約束,T必須擁有公共無(wú)參構(gòu)造函數(shù)且new()約束放在最后。

where T:U 裸類型約束,T必須是U或派生自U。

where T:BaseClass 基類約束,T必須為BaseClass類或其子類。

where T:Interface 接口約束,T必須為指定的接口或其實(shí)現(xiàn)接口。

3.反射創(chuàng)建泛型

  和非泛型類一樣,利用反射可以在運(yùn)行時(shí)獲取關(guān)于泛型類的成員信息。在學(xué)習(xí)過(guò)程我沒想到竟然還可以使用反射創(chuàng)建泛型類,更神奇的是還可以在代碼里直接寫IL指令,代碼如下所示。流程上還是那個(gè)規(guī)則,創(chuàng)建程序集-模塊-類-字段和方法,其中最主要的就是Builder結(jié)尾的一系列方法。有一個(gè)不好理解的地方就是為方法添加方法體,正常的邏輯是直接調(diào)用ReturnType的有參構(gòu)造函數(shù)創(chuàng)建List<TName1>對(duì)象,可是在.NET里并沒有這樣的方法,注意這里ReturnType已經(jīng)是綁定了TName1的List對(duì)象而不是普通的List<T>。所以我們需要拿到List<T>這個(gè)類型的有參構(gòu)造函數(shù),它被封裝在cInfo對(duì)象里,然后再將我們的ReturnType和cInfo關(guān)聯(lián)起來(lái)得到List<TName1>的構(gòu)造函數(shù)。除了構(gòu)造函數(shù)中的T需要替換為TName1外,參數(shù)IEnumerable<T>中的T也要被替換為TName1,體現(xiàn)在代碼里是這一句ienumOf.MakeGenericType(TFromListOf),最后它將隨構(gòu)造函數(shù)一起與TName1進(jìn)行關(guān)聯(lián)。在創(chuàng)建Hello方法我將它設(shè)置為靜態(tài)的,原本我是想設(shè)置為實(shí)例方法然后調(diào)試時(shí)去看看是否真的添加了這個(gè)方法,不過(guò)很奇怪我創(chuàng)建的實(shí)例o作為Invoke的參數(shù)總是報(bào)錯(cuò)提示無(wú)法找到方法入口,監(jiān)視o發(fā)現(xiàn)里面根本沒有Hello方法,在靜態(tài)方法下調(diào)試也沒有從o里看到有關(guān)方法的信息,如果讀者你對(duì)此有自己的想法歡迎留言,如有錯(cuò)誤還請(qǐng)指出。

public class BaseClass { }
 public interface IInterfaceA { }
 public interface IInterfaceB { }
 //作為TName1的類型參數(shù)
 public class ClassT1 { }
 //作為TName2的類型參數(shù)
 public class ClassT2 :BaseClass,IInterfaceA, IInterfaceB { }
 public class ReflectionT
 {
  public void CreateGeneric()
  {
   //創(chuàng)建一個(gè)名為”ReflectionT“的動(dòng)態(tài)程序集,這個(gè)程序集可以執(zhí)行和保存。
   AppDomain myDomain = AppDomain.CurrentDomain;
   AssemblyName assemblyName = new AssemblyName("ReflectionT");
   AssemblyBuilder assemblyBuilder = myDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
   //在這個(gè)程序集中創(chuàng)建一個(gè)與程序集名相同的模塊,接著創(chuàng)建一個(gè)類MyClass。
   ModuleBuilder moudleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll");
   TypeBuilder myType = moudleBuilder.DefineType("MyClass", TypeAttributes.Public);
   //創(chuàng)建類型參數(shù)名,將達(dá)到這樣的效果:public MyClass<TParam1,TParam2>
   string[] tNames = { "TName1", "TName2" };
   GenericTypeParameterBuilder[] gtps = myType.DefineGenericParameters(tNames);
   GenericTypeParameterBuilder tName1 = gtps[0];
   GenericTypeParameterBuilder tName2 = gtps[1];
   //為泛型添加約束,TName1將會(huì)被添加構(gòu)造器約束和引用類型約束
   tName1.SetGenericParameterAttributes(GenericParameterAttributes.DefaultConstructorConstraint | GenericParameterAttributes.ReferenceTypeConstraint);
   //TName2達(dá)到的效果將是:where TName2:ValueType,IComparable,IEnumerable
   Type baseType = typeof(BaseClass);
   Type interfaceA = typeof(IInterfaceA);
   Type interfaceB = typeof(IInterfaceA);
   Type[] interfaceTypes = { interfaceA, interfaceB };
   tName2.SetBaseTypeConstraint(baseType);
   tName2.SetInterfaceConstraints(interfaceTypes);
   /*為泛型類MyClass添加字段:
   private string name;
   public TName1 tField1;
    */
   FieldBuilder fieldBuilder = myType.DefineField("name", typeof(string), FieldAttributes.Public);
   FieldBuilder fieldBuilder2 = myType.DefineField("tField1", tName1, FieldAttributes.Public);
   //為泛型類添加方法Hello
   Type listType = typeof(List<>);
   Type ReturnType = listType.MakeGenericType(tName1);
   Type[] parameter = { tName1.MakeArrayType() };
   MethodBuilder methodBuilder = myType.DefineMethod(
    "Hello",        //方法名
    MethodAttributes.Public | MethodAttributes.Static, //指定方法的屬性
    ReturnType,      //方法的放回類型
    parameter);       //方法的參數(shù)
   //為方法添加方法體
   Type ienumOf = typeof(IEnumerable<>);
   Type TFromListOf = listType.GetGenericArguments()[0];
   Type ienumOfT = ienumOf.MakeGenericType(TFromListOf);
   Type[] ctorArgs = { ienumOfT };
   ConstructorInfo cInfo = listType.GetConstructor(ctorArgs);
   //最終的目的是要調(diào)用List<TName1>的構(gòu)造函數(shù) : new List<TName1>(IEnumerable<TName1>);
   ConstructorInfo ctor = TypeBuilder.GetConstructor(ReturnType, cInfo);
   //設(shè)置IL指令
   ILGenerator msil = methodBuilder.GetILGenerator();
   msil.Emit(OpCodes.Ldarg_0);
   msil.Emit(OpCodes.Newobj, ctor);
   msil.Emit(OpCodes.Ret);
   //創(chuàng)建并保存程序集
   Type finished = myType.CreateType();
   assemblyBuilder.Save(assemblyName.Name + ".dll");
   //創(chuàng)建這個(gè)MyClass這個(gè)類
   Type[] typeArgs = { typeof(ClassT1), typeof(ClassT2) };
   Type constructed = finished.MakeGenericType(typeArgs);
   object o = Activator.CreateInstance(constructed);
   MethodInfo mi = constructed.GetMethod("Hello");
   ClassT1[] inputParameter = { new ClassT1(), new ClassT1() };
   object[] arguments = { inputParameter };
   List<ClassT1> listResult = (List<ClassT1>)mi.Invoke(null, arguments);
   //查看返回結(jié)果中的數(shù)量和完全限定名
   Console.WriteLine(listResult.Count);
   Console.WriteLine(listResult[0].GetType().FullName);
   //查看類型參數(shù)以及約束
   foreach (Type t in finished.GetGenericArguments())
   {
    Console.WriteLine(t.ToString());
    foreach (Type c in t.GetGenericParameterConstraints())
    {
     Console.WriteLine(" "+c.ToString());
    }
   }
  }
 }

4.泛型中的out和in

  在VS查看IEnumerable<T>的定義時(shí)會(huì)看到在T前面有一個(gè)out,與其對(duì)應(yīng)的還有一個(gè)in。這就是.NET中的協(xié)變與逆變,剛開始筆者對(duì)于這2個(gè)概念很暈,主要以下4個(gè)疑惑,我想如果你解決了的話應(yīng)該也會(huì)有更進(jìn)一步的認(rèn)識(shí)。

1.為什么需要協(xié)變和逆變,協(xié)變與逆變有什么效果?

2.為什么有了協(xié)變與逆變就可以類型安全的進(jìn)行轉(zhuǎn)換,不加out和in就不可以轉(zhuǎn)換?

3.使用協(xié)變和逆變需要注意什么?

4.協(xié)變與逆變?yōu)槭裁粗荒苡糜诮涌诤臀校?br />

下面第一段代碼解決了第一個(gè)問(wèn)題。對(duì)于第二個(gè)問(wèn)題請(qǐng)看第二段代碼,里面對(duì)無(wú)out、in的泛型為什么不安全講得很清楚,從中我們要注意到如果要當(dāng)進(jìn)行協(xié)變時(shí)Function2是完全ok的,當(dāng)進(jìn)行逆變時(shí)Function1又是完全ok的。所以加out只是讓開發(fā)者在代碼里無(wú)法使用in的功能,加in則是讓開發(fā)者無(wú)法使用out的功能。讀者可以自己動(dòng)手試試,在out T的情況下作為輸入?yún)?shù)將會(huì)報(bào)錯(cuò),同樣將in T作為返回參數(shù)也會(huì)報(bào)錯(cuò),且VS報(bào)錯(cuò)時(shí)會(huì)直接告訴你這樣只能在協(xié)變或逆變情況下使用。也就是說(shuō)加了out后,只有Function2能夠編譯通過(guò),這樣o=str將不會(huì)受Function1的影響而不安全;加了in后,只有Function1能夠編譯通過(guò),這樣str=o將不會(huì)受Function2的影響而不安全。使用out和in要注意它們只能用于接口和委托,且不能作用于值類型。out用于屬性時(shí)只能用于只讀屬性,in則是只寫屬性,進(jìn)行協(xié)變和逆變時(shí)這2個(gè)類型參數(shù)必須要有繼承關(guān)系,現(xiàn)在為什么不能用于值類型你應(yīng)該懂了吧。對(duì)于第四個(gè)疑惑我沒有找到一個(gè)完全正確的答案,只是發(fā)現(xiàn)了我認(rèn)同的想法。接口和委托,有什么共同點(diǎn)?顯然就是方法,在接口或委托中聲明的T都將用于方法且只能用于方法,由上面的討論可知協(xié)變和逆變這種情況正是適用于方法這樣的成員。對(duì)于在抽象類中不可以使用的原因,或許微軟是覺得在抽象類中再搞一個(gè)僅限于方法的限制太麻煩了吧。

public interface INone<T> { }
  public interface IOut<out T> { }
  public interface IIn<in T> { }
  public class MyClass<T> : INone<T>, IOut<T>, IIn<T> { }
  void hh()
  {
   INone<object> oBase1 = new MyClass<object>();
   INone<string> o1 = new MyClass<string>();
   //下面兩句都無(wú)法編譯通過(guò)
   //o1 = oBase1;
   //oBase1 = o1;
   //為了能夠進(jìn)行轉(zhuǎn)換,于是出現(xiàn)了協(xié)變與逆變
   IOut<object> oBase2 = new MyClass<object>();
   IOut<string> o2 = new MyClass<string>();
   //o2 = oBase2; 編譯不通過(guò)
   //有了out關(guān)鍵字的話,就可以實(shí)現(xiàn)從IOut<string>到IOut<object>的轉(zhuǎn)換-往父類轉(zhuǎn)換
   oBase2 = o2;
   IIn<object> oBase3 = new MyClass<object>();
   IIn<string> o3 = new MyClass<string>();
   //oBase3 = o3; 編譯不通過(guò)
   //有了in關(guān)鍵字的話,就可以實(shí)現(xiàn)從IIn<object>到IOut<string>的轉(zhuǎn)換-往子類轉(zhuǎn)換
   o3 = oBase3;
  }
public interface INone<T>
 {
  void Function1(T tParam);
  T Function2();
 }
 class MyClass<T> : INone<T>
 {
  public void Function1(T tParam)
  {
   Console.WriteLine(tParam.ToString());
  }
  public T Function2()
  {
   T t = default(T);
   return t;
  }
 }
 class hhh
 {
  void fangyz()
  {
   INone<object> o = new MyClass<object>();
   INone<string> str = new MyClass<string>();
   //假設(shè)str能夠轉(zhuǎn)換為o
   //o = str;
   object o1=new object();
   //這樣的話就是object類型向string類型轉(zhuǎn)換了,類型不安全
   o.Function1(o1);
   //這樣則是string類型向object類型轉(zhuǎn)換了,注意這樣是ok的,沒什么問(wèn)題
   object o2=o.Function2();
   //假設(shè)str能夠轉(zhuǎn)換為o
   //str=o;
   //string對(duì)象將轉(zhuǎn)變?yōu)閛bject,這樣沒問(wèn)題
   str.Function1("haha");
   //這樣將是object向string類型的轉(zhuǎn)換,類型不安全。
   string o3=str.Function2();
  }
 }

以上所述是小編給大家介紹的C#基礎(chǔ)之泛型,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

  • 學(xué)會(huì)使用C#異常

    學(xué)會(huì)使用C#異常

    在C#中,程序中在運(yùn)行時(shí)出現(xiàn)的錯(cuò)誤,會(huì)不斷在程序中進(jìn)行傳播,這種機(jī)制稱為“異?!薄?異常通常由錯(cuò)誤的代碼引發(fā),并由能夠更正錯(cuò)誤的代碼進(jìn)行catch。本文將對(duì)C#異常簡(jiǎn)要分析說(shuō)明,下面就跟著小編一起來(lái)看下吧
    2016-12-12
  • C#實(shí)現(xiàn)貪吃蛇小游戲

    C#實(shí)現(xiàn)貪吃蛇小游戲

    這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)貪吃蛇小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • C#執(zhí)行Javascript代碼的幾種方法總結(jié)

    C#執(zhí)行Javascript代碼的幾種方法總結(jié)

    本篇文章主要是對(duì)C#執(zhí)行Javascript代碼的幾種方法進(jìn)行了詳細(xì)的總結(jié)介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助
    2014-01-01
  • C# 動(dòng)態(tài)加載程序集信息

    C# 動(dòng)態(tài)加載程序集信息

    在設(shè)計(jì)模式的策略模式中,需要?jiǎng)討B(tài)加載程序集信息,本文通過(guò)一個(gè)簡(jiǎn)單的實(shí)例,來(lái)講解動(dòng)態(tài)加載Dll需要的知識(shí)點(diǎn)。下面跟著小編一起來(lái)看下吧
    2017-03-03
  • c#編寫webservice服務(wù)引用實(shí)例分享

    c#編寫webservice服務(wù)引用實(shí)例分享

    c#編寫webservice服務(wù)引用實(shí)例分享,大家參考使用吧
    2013-12-12
  • C#判斷字符串是否是數(shù)字(實(shí)例)

    C#判斷字符串是否是數(shù)字(實(shí)例)

    本文主要分享了C#實(shí)現(xiàn)判斷字符串是否是數(shù)字的具體實(shí)例,具有一定的參考價(jià)值,需要的朋友一起來(lái)看下吧
    2016-12-12
  • C# DataTable中查詢指定字段名稱的數(shù)據(jù)

    C# DataTable中查詢指定字段名稱的數(shù)據(jù)

    這篇文章主要介紹了C# DataTable中查詢指定字段名稱的數(shù)據(jù),本文直接給出實(shí)例代碼,簡(jiǎn)單易懂,需要的朋友可以參考下
    2015-06-06
  • 基于XSLT調(diào)試的相關(guān)問(wèn)題

    基于XSLT調(diào)試的相關(guān)問(wèn)題

    本篇文章是對(duì)XSLT調(diào)試的相關(guān)問(wèn)題進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • Unity 使用TexturePacker打包圖集的操作方法

    Unity 使用TexturePacker打包圖集的操作方法

    這篇文章主要介紹了Unity 使用TexturePacker打包圖集的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-08-08
  • C#中的out關(guān)鍵字用法詳解

    C#中的out關(guān)鍵字用法詳解

    在 C# 中,out 關(guān)鍵字是一個(gè)修飾符,它允許你在方法內(nèi)部創(chuàng)建一個(gè)臨時(shí)的變量,用于接收傳遞進(jìn)來(lái)的參數(shù)值,并在方法執(zhí)行完畢后,將該變量的值返回給調(diào)用方法的對(duì)象,本文將給大家詳細(xì)介紹一下C#中的out關(guān)鍵字用法,需要的朋友可以參考下
    2024-02-02

最新評(píng)論