程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#基本之泛型

C#基本之泛型

編輯:C#入門知識

C#基本之泛型。本站提示廣大學習愛好者:(C#基本之泛型)文章只能為提供參考,不一定能成為您想要的結果。以下是C#基本之泛型正文


1.泛型的實質

  泛型的利益不消多說,在.NET中我看到有許多技巧都是以泛型為基本的,不外由於不懂泛型而只能對那些技巧一臉茫然。泛型重要用於聚集類,最重要的緣由是它不須要裝箱拆箱且類型平安,好比很經常使用的List<T>。關於List<T>我今後還想停止深究,如今我寫了一個超簡版的MyList<T>聚集,以下面第一段代碼所示。代碼很簡略,但在寫的進程中有一個細節,假如我為listInt賦值string類型的變量時編譯器會提醒報錯。編譯器很智能,然則從這個景象中你會不會獵奇泛型中的T是在甚麼情形下指定的呢,是生成IL時照樣JIT靜態編譯時?老辦法我將exe放入Reflector對象中,發明IL代碼中滿是T,這解釋在編譯時T僅僅只是一個占位符,真真的調換是在運轉時靜態調換的。可是在寫泛型類時期碼只要一份,那我為MyList創立int、string類型的對象時這個代碼是若何公用的呢?關於值類型聚集好比listInt,因為終究須要調換T,那末確定是有一份完全的代碼外面T被調換為int。關於援用類型,由於變量只是一個指向堆中的指針,是以代碼只要一份。總結起來就是值類型代碼有多份而援用類型代碼只要一份,別的編寫自界說泛型代碼時最好應用成心義的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.泛型標准

  這個很主要,重要包含束縛和default。.NET是推舉我們開辟者盡量的多應用束縛,由於束縛越多越可以包管法式不會失足。泛型束縛由where指定,六種束縛以下所示。這些束縛可以零丁應用也能夠一路應用,但也有不克不及一路應用的好比值類型與援用類型束縛。關於default的感化我們可以思慮如許一個成績,假如在泛型類中我們須要初始化一個T變量。由於T既有能夠是值類型也有能夠是援用類型,所以不克不及直接用new或等於0。那若何斷定T是值類型照樣援用類型呢?這裡就要用到default,關於援用類型default(T)將前往null,關於數值類型default(T)將前往0。這裡之所以寫數值類型是由於值類型還能夠是構造體,default會將構造體中的成員初始化為0或null。還有一種特別情形就是可空值類型,此時將前往Nullable<T>,如許初始變量直接應用T t=default(T)便可以了。固然泛型類給人帶來了奧秘感,不外運轉時它的實質就是一個通俗的類,是以照舊具有類的特征好比繼續。這為我們開辟者帶來了許多利益,好比我想要有一個int聚集類,它除有List<int>的功效外還有自界說的某些功效,這時候候只需MyList : List<int>便可以獲得想要的後果了,異常便利。

where T : struct 值類型束縛,T必需為值類型。

where T:class 援用類型束縛,T必需為援用類型。

where T:new() 結構器束縛,T必需具有公共無參結構函數且new()束縛放在最初。

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

where T:BaseClass 基類束縛,T必需為BaseClass類或其子類。

where T:Interface 接口束縛,T必需為指定的接口或其完成接口。

3.反射創立泛型

  和非泛型類一樣,應用反射可以在運轉時獲得關於泛型類的成員信息。在進修進程我沒想到居然還可使用反射創立泛型類,更奇異的是還可以在代碼裡直接寫IL指令,代碼以下所示。流程上照樣誰人規矩,創立法式集-模塊-類-字段和辦法,個中最重要的就是Builder開頭的一系列辦法。有一個欠好懂得的處所就是為辦法添加辦法體,正常的邏輯是直接挪用ReturnType的有參結構函數創立List<TName1>對象,可是在.NET裡並沒有如許的辦法,留意這裡ReturnType曾經是綁定了TName1的List對象而不是通俗的List<T>。所以我們須要拿到List<T>這個類型的有參結構函數,它被封裝在cInfo對象裡,然後再將我們的ReturnType和cInfo聯系關系起來獲得List<TName1>的結構函數。除結構函數中的T須要調換為TName1外,參數IEnumerable<T>中的T也要被調換為TName1,表現在代碼裡是這一句ienumOf.MakeGenericType(TFromListOf),最初它將隨結構函數一路與TName1停止聯系關系。在創立Hello辦法我將它設置為靜態的,本來我是想設置為實例辦法然後調試時去看看能否真的添加了這個辦法,不外很奇異我創立的實例o作為Invoke的參數老是報錯提醒沒法找到辦法進口,監督o發明外面基本沒有Hello辦法,在靜態辦法下調試也沒有從o裡看到有關辦法的信息,假如讀者你對此有本身的設法主意迎接留言,若有毛病還請指出。

public class BaseClass { }
 public interface IInterfaceA { }
 public interface IInterfaceB { }
 //作為TName1的類型參數
 public class ClassT1 { }
 //作為TName2的類型參數
 public class ClassT2 :BaseClass,IInterfaceA, IInterfaceB { }
 public class ReflectionT
 {
  public void CreateGeneric()
  {
   //創立一個名為”ReflectionT“的靜態法式集,這個法式集可以履行和保留。
   AppDomain myDomain = AppDomain.CurrentDomain;
   AssemblyName assemblyName = new AssemblyName("ReflectionT");
   AssemblyBuilder assemblyBuilder = myDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
   //在這個法式集中創立一個與法式集名雷同的模塊,接著創立一個類MyClass。
   ModuleBuilder moudleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll");
   TypeBuilder myType = moudleBuilder.DefineType("MyClass", TypeAttributes.Public);
   //創立類型參數名,將到達如許的後果:public MyClass<TParam1,TParam2>
   string[] tNames = { "TName1", "TName2" };
   GenericTypeParameterBuilder[] gtps = myType.DefineGenericParameters(tNames);
   GenericTypeParameterBuilder tName1 = gtps[0];
   GenericTypeParameterBuilder tName2 = gtps[1];
   //為泛型添加束縛,TName1將會被添加結構器束縛和援用類型束縛
   tName1.SetGenericParameterAttributes(GenericParameterAttributes.DefaultConstructorConstraint | GenericParameterAttributes.ReferenceTypeConstraint);
   //TName2到達的後果將是: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);       //辦法的參數
   //為辦法添加辦法體
   Type ienumOf = typeof(IEnumerable<>);
   Type TFromListOf = listType.GetGenericArguments()[0];
   Type ienumOfT = ienumOf.MakeGenericType(TFromListOf);
   Type[] ctorArgs = { ienumOfT };
   ConstructorInfo cInfo = listType.GetConstructor(ctorArgs);
   //終究的目標是要挪用List<TName1>的結構函數 : new List<TName1>(IEnumerable<TName1>);
   ConstructorInfo ctor = TypeBuilder.GetConstructor(ReturnType, cInfo);
   //設置IL指令
   ILGenerator msil = methodBuilder.GetILGenerator();
   msil.Emit(OpCodes.Ldarg_0);
   msil.Emit(OpCodes.Newobj, ctor);
   msil.Emit(OpCodes.Ret);
   //創立並保留法式集
   Type finished = myType.CreateType();
   assemblyBuilder.Save(assemblyName.Name + ".dll");
   //創立這個MyClass這個類
   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);
   //檢查前往成果中的數目和完整限制名
   Console.WriteLine(listResult.Count);
   Console.WriteLine(listResult[0].GetType().FullName);
   //檢查類型參數和束縛
   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>的界說時會看到在T後面有一個out,與其對應的還有一個in。這就是.NET中的協變與逆變,剛開端筆者關於這2個概念很暈,重要以下4個困惑,我想假如你處理了的話應當也會有更進一步的熟悉。

1.為何須要協變和逆變,協變與逆變有甚麼後果?

2.為何有了協變與逆變便可以類型平安的停止轉換,不加out和in就弗成以轉換?

3.應用協變和逆變須要留意甚麼?

4.協變與逆變成甚麼只能用於接口和拜托?

上面第一段代碼處理了第一個成績。關於第二個成績請看第二段代碼,外面對無out、in的泛型為何不平安講得很清晰,從中我們要留意到假如要當停止協變時Function2是完整ok的,當停止逆變時Function1又是完整ok的。所以加out只是閃開發者在代碼裡沒法應用in的功效,加in則是閃開發者沒法應用out的功效。讀者可以本身著手嘗嘗,在out T的情形下作為輸出參數將會報錯,異樣將in T作為前往參數也會報錯,且VS報錯時會直接告知你如許只能在協變或逆變情形下應用。也就是說加了out後,只要Function2可以或許編譯經由過程,如許o=str將不會受Function1的影響而不平安;加了in後,只要Function1可以或許編譯經由過程,如許str=o將不會受Function2的影響而不平安。應用out和in要留意它們只能用於接口和拜托,且不克不及感化於值類型。out用於屬性時只能用於只讀屬性,in則是只寫屬性,停止協變和逆變時這2個類型參數必需要有繼續關系,如今為何不克不及用於值類型你應當懂了吧。關於第四個困惑我沒有找到一個完整准確的謎底,只是發明了我認同的設法主意。接口和拜托,有甚麼配合點?明顯就是辦法,在接口或拜托中聲明的T都將用於辦法且只能用於辦法,由下面的評論辯論可知協變和逆變這類情形恰是實用於辦法如許的成員。關於在籠統類中弗成以應用的緣由,也許微軟是認為在籠統類中再弄一個僅限於辦法的限制太費事了吧。

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>();
   //上面兩句都沒法編譯經由過程
   //o1 = oBase1;
   //oBase1 = o1;
   //為了可以或許停止轉換,因而湧現了協變與逆變
   IOut<object> oBase2 = new MyClass<object>();
   IOut<string> o2 = new MyClass<string>();
   //o2 = oBase2; 編譯欠亨過
   //有了out症結字的話,便可以完成從IOut<string>到IOut<object>的轉換-往父類轉換
   oBase2 = o2;
   IIn<object> oBase3 = new MyClass<object>();
   IIn<string> o3 = new MyClass<string>();
   //oBase3 = o3; 編譯欠亨過
   //有了in症結字的話,便可以完成從IIn<object>到IOut<string>的轉換-往子類轉換
   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>();
   //假定str可以或許轉換為o
   //o = str;
   object o1=new object();
   //如許的話就是object類型向string類型轉換了,類型不平安
   o.Function1(o1);
   //如許則是string類型向object類型轉換了,留意如許是ok的,沒甚麼成績
   object o2=o.Function2();
   //假定str可以或許轉換為o
   //str=o;
   //string對象將改變為object,如許沒成績
   str.Function1("haha");
   //如許將是object向string類型的轉換,類型不平安。
   string o3=str.Function2();
  }
 }

以上所述是小編給年夜家引見的C#基本之泛型,願望對年夜家有所贊助,假如年夜家有任何疑問請給我留言,小編會實時答復年夜家的。在此也異常感激年夜家對網站的支撐!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved