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();
listInt[] = ;
listString[] = "ha ha";
}
} public class MyList<T>
{
T[] array;
int current = -;
public MyList()
{
array = new T[];
} public void Add(T t)
{
current++;
if (current < )
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[];
GenericTypeParameterBuilder tName2 = gtps[]; //为泛型添加约束,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()[];
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[].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. [.net 面向对象编程基础] (18) 泛型

    [.net 面向对象编程基础] (18) 泛型 上一节我们说到了两种数据类型数组和集合,数组是指包含同一类型的多个元素,集合是指.net中提供数据存储和检索的专用类. 数组使用前需要先指定大小,并且检 ...

  2. 黑马程序员:Java基础总结----泛型(高级)

    黑马程序员:Java基础总结 泛型(高级)   ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 泛型(高级) 泛型是提供给javac编译器使用的,可以限定集合中的输入类型 ...

  3. Java基础:泛型

    Java的泛型是什么呢, 就是类型的參数化,这得类型包含方法參数和返回值.也就是原本该是确定类型的地方换成了变量,把类型的确定时间向后延迟了. 在之前,学过"重载"的概念,重载是什 ...

  4. java基础(9) - 泛型解析

    泛型 定义简单的泛型类 泛型方法 /** * 1.定义一个泛型类 * 在类名后添加类的泛型参数 <T> * 泛型类里面的所有T会根据创建泛型类时传入的参数确定类型 * 2.定义泛型方法 * ...

  5. Java基础之泛型

    泛型: (1)为什么会出现泛型? 因为集合存放的数据类型不固定,故往集合里面存放元素时,存在安全隐患, 如果在定义集合时,可以想定义数组一样指定数据类型,那么就可以解决该类安全问题. JDK1.5后出 ...

  6. 【Java基础】泛型

    Num1:请不要在新代码中使用原生类型 泛型类和接口统称为泛型.每种泛型定义一组参数化的类型,构成格式是:类或接口名称,接着用<>把对应于泛型形式类型的参数的实际参数列表括起来.比如:Li ...

  7. 黑马程序员——【Java基础】——泛型、Utilities工具类、其他对象API

    ---------- android培训.java培训.期待与您交流! ---------- 一.泛型 (一)泛型概述 1.泛型:JDK1.5版本以后出现的新特性,用于解决安全问题,是一个类型安全机制 ...

  8. Java基础之泛型——使用二叉树进行排序(TryBinaryTree)

    控制台程序. 1.实现针对容器类的基于集合的循环 为了让容器类类型的对象能够在基于集合的for循环中可用,类必须并且只需要满足一个要求——必须实现泛型接口java.lang.Iterable<& ...

  9. C#基础:泛型委托

    泛型委托是委托的一种特殊形式,感觉看上去比较怪异,其实在使用的时候跟委托差不多,不过泛型委托更具有类型通用性. 就拿C#里最常见的委托EventHandler打比方.在.NET 2.0以前,也就是泛型 ...

随机推荐

  1. Asp.net MVC的Model Binder工作流程以及扩展方法(1) - Custom Model Binder

    在Asp.net MVC中, Model Binder是生命周期中的一个非常重要的部分.搞清楚Model Binder的流程,能够帮助理解Model Binder的背后发生了什么.同时该系列文章会列举 ...

  2. Sql Server之旅——第九站 看公司这些DBA们设计的这些复合索引

    这一篇再说下索引的最后一个主题,索引覆盖,当然学习比较好的捷径是看看那些大师们设计的索引,看从中能提取些什么营养的东西,下面我们看 看数据库中一个核心的Orders表. 一:查看表的架构 <1& ...

  3. (1)编写一个接口ShapePara,要求: 接口中的方法: int getArea():获得图形的面积。int getCircumference():获得图形的周长 (2)编写一个圆类Circle,要求:圆类Circle实现接口ShapePara。 该类包含有成员变量: radius:public 修饰的double类型radius,表示圆的半径。 x:private修饰的double型变量x,

    package com.hanqi.test; //创建接口 public interface ShapePara { //获取面积的方法 double getArea(); //获取周长的方法 do ...

  4. mysql优化之索引篇

    对mysql优化是一个综合性的技术,主要包括 a: 表的设计合理化(符合3NF) b: 添加适当索引(index) [四种: 普通索引.主键索引.唯一索引unique.全文索引] c: 分表技术(水平 ...

  5. Android UI编程(1)——九宫格(GridView)

    (转自:http://blog.csdn.net/Thanksgining/article/details/42968847) 参考博客:http://blog.csdn.net/xyz_lmn/ar ...

  6. C语言中怎么将文件里的数据创建到(读到)链表中?

    定义的结构体: struct student { ]; //学生学号 ]; //学生姓名 struct student *next; //next 指针 指向 struct student 类型的变量 ...

  7. 理解 QEMU/KVM 和 Ceph(3):存储卷挂接和设备名称

    本系列文章会总结 QEMU/KVM 和 Ceph 之间的整合: (1)QEMU-KVM 和 Ceph RBD 的 缓存机制总结 (2)QEMU 的 RBD 块驱动(block driver) (3)存 ...

  8. 学习OpenStack之 (1):安装devstack

    1. 系统准备 ubuntu 12.04 server 虚拟机. 2G内存. 依次运行以下命令来安装git: sudo apt-get update sudo apt-get upgrade sudo ...

  9. No resource found that matches the given name 'Theme.AppCompat.Light 的完美解决方案

    No resource found that matches the given name 'Theme.AppCompat.Light 的完美解决方案 首先这个问题的产生是由于缺少Theme.App ...

  10. 初步了解Entity Framework

    来源:http://www.cnblogs.com/Wayou/archive/2012/09/20/EF_CodeFirst.html Entity Framework的全称是ADO.NET Ent ...