• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号
您的位置:首页 > 程序设计 >C#教程 > C#基础之泛型

C#基础之泛型

作者:方小白 字体:[增加 减小] 来源:互联网 时间:2017-05-28

方小白 通过本文主要向大家介绍了c#泛型,c#什么是泛型,c#泛型编程,c#泛型详解,c#泛型集合等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

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;
   }
  }
 }
</div>

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);
   MethodInf



 
分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

  • C#的泛型方法解析
  • C#给Excel添加水印实例详解
  • C#枚举类型和结构体详解
  • C#基础之泛型
  • C#调用Java方法实例详解
  • C#获取机器码的方法详解(机器名,CPU编号,硬盘编号,网卡mac等)
  • C#中数组Array,ArrayList,泛型List详细对比
  • 详解.NET 4.0中的泛型协变(covariant)和反变(contravariant)
  • C#泛型委托的用法实例分析
  • C#同步网络时间的方法实例详解

相关文章

  • 2017-05-28C#书写规范
  • 2017-05-28基于C#实现的屏幕指定区域截屏代码
  • 2017-05-28解析C#中的分部类和分部方法
  • 2017-05-28基于C#实现的多生产者多消费者同步问题实例
  • 2017-05-28详解C#对XML、JSON等格式的解析
  • 2017-05-28C#编写ActiveX网页截图控件
  • 2017-05-28C#多线程传递参数及任务用法示例
  • 2017-05-28C# 面向对象三大特性:封装、继承、多态
  • 2017-05-28C#异步下载文件
  • 2017-05-28字符串和十六进制之间的转换方法实例

文章分类

  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号

最近更新的内容

    • c#数学表示法(后缀表示法)详解
    • C#操作FTP出现500错误解决办法
    • C#使用默认浏览器打开网页的方法
    • sqlserver备份还原数据库功能封装分享
    • c# 给button添加不规则的图片以及用pictureBox替代button响应点击事件的方法
    • c#生成excel示例sql数据库导出excel
    • C# yield在WCF中的错误使用(二)
    • C#使用xsd文件验证XML格式是否正确的实现方法
    • C#动态生成PictureBox并指定图片的方法
    • C#数组的常用操作方法小结

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有