Presentation is loading. Please wait.

Presentation is loading. Please wait.

第7章 泛型和反射 7.1 泛 型 7.2 反 射.

Similar presentations


Presentation on theme: "第7章 泛型和反射 7.1 泛 型 7.2 反 射."— Presentation transcript:

1 第7章 泛型和反射 7.1 泛 型 7.2 反 射

2 7.1 泛 型 7.1.1 什么是泛型   所谓泛型,是指通过参数化类型来实现在同一份代码上操作多种数据类型,泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用。 泛型类型和普通类型的区别在于泛型类型与一组类型参数或类型变量关联。   C#泛型能力是由CLR在运行时支持,区别于C++的编译时模板机制和Java的编译时的“搽拭法”。这使得泛型能力可以在各个支持CLR的语言之间进行无缝的互操作。

3 double MAX1(double a,double b) int MAX(int a,int b)
int、double数据 数据的抽象:   int MAX(int a,int b) { return a>b?a:b; }   double MAX1(double a,double b) int MAX(int a,int b) { return a>b?a:b; } double MAX(double a,double b) 重载 泛型 数据类型的抽象: T MAX<T>(T a,T b) { return a>b?a:b; }

4 7.1.2 泛型的声明和使用 通常先声明泛型,然后通过类型实例化来使用泛型。定义泛型的语法格式如下:
  通常先声明泛型,然后通过类型实例化来使用泛型。定义泛型的语法格式如下:   [访问修饰符][返回类型] 泛型名称<类型参数列表>   其中,“泛型名称”要符合标识符的定义。尖括号表示类型参数列表,可以包含一个或多个类型参数,如<T,U,>。

5 C#中常用的泛型有泛型类和泛型方法,例如:
class Stack<T> //声明泛型类 {  T data[MaxSize];   int top; } void swap<T>(ref T a,ref T b) //定义泛型方法 {  T tmp = a;   a = b;   b = tmp;

6 【例7.1】 分析以下程序的执行结果。 using System; namespace proj7_1
{ class Student //学生类 { int sno; //学号 string sname; //姓名 public Student() { } public Student(int no, string name) { sno = no; sname = name; } public void Dispstudent() //输出学生对象 { Console.Write("[{0}:{1}] ",sno,sname); }

7 class Teacher //教师类 { int tno; //编号 string tname; //姓名 public Teacher() { } public Teacher(int no, string name) { tno = no; tname = name; } public void Dispteacher() //输出教师对象 { Console.Write("[{0}:{1}] ", tno, tname); }

8 class Stack<T> //声明栈泛型类
{ int maxsize; //栈中元素最多个数 T[] data; //存放栈中T类型的元素 int top; //栈顶指针 public Stack() //构造函数 { maxsize = 10; data = new T[maxsize]; top = -1; } public bool StackEmpty() //判断栈空方法 { return top == -1; } public bool Push(T e) //元素e进栈方法 { if (top == maxsize - 1) //栈满返回false return false; top++; data[top] = e; return true;

9 public bool Pop(ref T e) //元素出栈方法
{ if (top == -1) //栈空返回false return false; e = data[top]; top--; return true; }

10 class Program { static void Main(string[] args) { //-----整数栈操作----- int e = 0; Stack<int> s1 = new Stack<int>(); //定义整数栈 s1.Push(1); //进栈3个整数 s1.Push(2); s1.Push(3); Console.Write("整数栈出栈次序:"); while (!s1.StackEmpty()) //栈不空时出栈元素 { s1.Pop(ref e); Console.Write("{0} ", e); } Console.WriteLine();

11 //-----实数栈操作----- double d= 0; Stack<double> s2 = new Stack<double>(); //定义实数栈 s2.Push(2.5); //进栈3个实数 s2.Push(3.8); s2.Push(5.9); Console.Write("实数栈出栈次序:"); while (!s2.StackEmpty()) //栈不空时出栈元素 { s2.Pop(ref d); Console.Write("{0} ", d); } Console.WriteLine();

12 //-----学生对象栈操作----- Student st =new Student(); Stack<Student> s3 = new Stack<Student>(); //定义学生栈 s3.Push(new Student(1,"Student1")); //进栈3个学生对象 s3.Push(new Student(2,"Student2")); s3.Push(new Student(3,"Student3")); Console.Write("学生对象栈出栈次序:"); while (!s3.StackEmpty()) //栈不空时出栈元素 { s3.Pop(ref st); st.Dispstudent(); } Console.WriteLine();

13 //-----教师对象栈操作----- Teacher te = new Teacher(); Stack<Teacher> s4 = new Stack<Teacher>();//定义教师栈 s4.Push(new Teacher(1, "Teacher1")); //进栈3个教师对象 s4.Push(new Teacher(2, "Teacher2")); s4.Push(new Teacher(3, "Teacher3")); Console.Write("教师对象栈出栈次序:"); while (!s4.StackEmpty()) //栈不空时出栈元素 { s4.Pop(ref te); te.Dispteacher(); } Console.WriteLine();

14 本程序先声明Student和Teacher两个类,再声明一个泛型栈Stack<T>,然后实例化为整数栈s1、实数栈s2、学生对象栈s3和教师对象栈s4,各自进栈3个元素后并出栈,程序执行结果如图7.2所示。

15 泛型的MSIL代码分析 与.NET Framework同时发布的中间语言反汇编工具(ildasm.exe)可以加载任意的.NET程序集并分析它的内容,包括关联的清单、MSIL代码和类型元数据。

16 在命令行方式下进入ildasm.exe所在的文件夹,键入ildasm命令,运行该程序,出现一个“IL DASM”对话框,选择“文件|打开”命令。
选择例7.1的程序proj7-1.exe(位于“D:\C#程序\ch7\proj7-1\proj7-1\bin\Debug”文件夹中),便以树状图展示该程序集的结构,展开所有项的结果如图7.3所示。

17

18 从例7.1程序的中间语言代码中可以看到如下要点。
1. 编译方式 第一轮编译时,编译器只为Stack<T>产生“泛型版”的IL代码与元数据,并不进行泛型的实例化,T在中间只充当占位符。例如,在Stack<T>的构造函数中占位符显示为<!T>。 在JIT编译时,当JIT编译器第一次遇到Stack<int>时,将用int替换“范型版”IL代码与元数据中的T,即进行泛型类型的实例化。例如,Main函数中显示的<int32>。

19 2. 引用类型作为参数和值类型作为参数 CLR为所有类型参数为“引用类型”的泛型类型产生同一份代码;但是如果类型参数为“值类型”,对每一个不同的“值类型”,CLR将为其产生一份独立的代码,这里的s3和s4的idloc.s的地址相同,都是CS$4$0000。 因为实例化一个引用类型的泛型,它在内存中分配的大小是一样的,但是当实例化一个值类型的时候,在内存中分配的大小是不一样的。这样做的目的是尽可能减小代码量。

20 7.1.4 类型参数的约束 在泛型类型或方法定义中,类型参数是在实例化泛型类型的变量时指定的特定类型的占位符。
类型参数的约束 在泛型类型或方法定义中,类型参数是在实例化泛型类型的变量时指定的特定类型的占位符。 那么,类型参数是不是任何类型呢?结论是否定的。

21 例如,声明以下泛型类型: class MyGType<T1, T2> { static public bool LessThan(T1 obj1, T2 obj2) { return obj1 < obj2; } } 在编译时,系统指出“运算符<无法应用于T1和T2类型的操作数”的错误。如果它是正确的,在实例化为MyGType<int,string>时,一个整数和一个字符串怎么比较呢?甚至在实例化为MyGType<int,MyClass>时,一个整数和一个对象怎么比较呢?

22 为了解决这个问题,C#提出了类型参数的约束的概念。通过约束检查泛型列表中的某个项以确定它是否有效。
例如,约束告诉编译器:仅此类型的对象或从此类型派生的对象才可用作类型参数。一旦编译器有了这个保证,它就能够允许在泛型类中调用该类型的方法。  约束是使用上下文关键字 where 应用的。where子句的一般格式如下: where 类型参数:约束1,约束2,…

23 这样在对类型参数的类型种类施加限制后,如果使用该泛型的代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。
例如,以下泛型有3个类型参数,T1是未绑定的(没有约束),T2只有MyClass1类型或从它派生的类或MyClass2类型或从它派生的类才能用作类型实参,T3只有MyClass3类型或从它派生的类才能用作类型实参: class MyClass<T1,T2,T3> where T2:MyClass1,MyClass2 T3:MyClass3 { … } 这样在对类型参数的类型种类施加限制后,如果使用该泛型的代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。

24 7.1.5 泛型的继承 C#除了可以单独声明泛型类型外,也可以在基类中包含泛型类型的声明。
泛型的继承 C#除了可以单独声明泛型类型外,也可以在基类中包含泛型类型的声明。 但基类如果是泛型类,它的类型要么已实例化,要么来源于子类(同样是泛型类型)声明的类型参数。

25 例如,若声明了如下泛型: 则以下声明是正确的: class C<U,V> { … }
class D:C<string,int> //继承的类型已实例化 class E<U,V>:C<U,V> //E类型为C类型提供了U、V,即来源于子类 class F<U,V>:C<string,int> //F类型继承于C<string,int>,可看成F继承一个非泛型的类

26 而以下声明是错误的: class G:C<U,V> //因为G类型不是泛型,C是泛型,G无法给C提供泛型的实例化 { }

27   泛型接口和委托 1. 泛型接口 与泛型继承类似,泛型接口的类型参数要么已实例化,要么来源于实现类声明的类型参数。

28  2. 泛型委托 泛型委托支持在委托返回值和参数上应用参数类型,这些参数类型同样可以附带合法的约束。

29 例如: delegate bool MyDelegate<T>(T value); class MyClass
{ static bool method1(int i){ … }     static bool method2(string s){ … }      static void Main()      { MyDelegate<string> p2 = method2;          MyDelegate<int> p1 = new MyDelegate<int>( method1);      }

30 7.2 反 射 7.2.1 反射概述 反射是一种机制,通过这种机制可以知道一个未知类型的类型信息。
反 射 7.2.1 反射概述   反射是一种机制,通过这种机制可以知道一个未知类型的类型信息。   例如有一个对象,它不是我们定义的,既可能是通过网络捕捉到的,也可能是使用泛型定义的,但我们想知道这个对象的类型信息,想知道这个对象有哪些方法或者属性什么的,甚至想进一步调用这个对象的方法。关键是现在只知道它是一个对象,不知道它的类型,自然不会知道它有哪些方法等信息,这时该怎么办呢? 

31   反射机制就是解决这么一个问题的,通过反射机制就可以知道未知类型对象的类型信息。

32 归纳起来,反射在下列情况下很有用: 需要访问程序元数据的属性。 检查和实例化程序集中的类型。 在运行时构建新类型。 执行后期绑定,访问在运行时创建的类型的方法。

33 7.2.2 反射中常用的类 1. Type类   System.Reflection是反射的命名空间,而Type类为System.Reflection功能的根,也是访问元数据的主要方式。   Type类表示类型声明,包括类类型、接口类型、数组类型、值类型、枚举类型、类型参数、泛型类型定义,以及开放或封闭构造的泛型类型。

34 Type类的属性 公共属性 说明 IsAbstract 获取一个值,通过该值指示Type是否为抽象的并且必须被重写 IsArray
IsByRef 获取一个值,通过该值指示Type是否由引用传递 IsClass 获取一个值,通过该值指示Type是否是一个类,即不是值类型或接口 IsInterface 获取一个值,通过该值指示Type是否为接口,即不是类或值类型 IsSubclassOf 确定当前Type表示的类是否是从指定的Type表示的类派生的 MakeArrayType 返回一个表示当前类型的一维数组(下限为零)的Type对象 Module 获取在其中定义当前Type的模块 Name 获取当前成员的名称 Namespace 获取Type的命名空间 ReflectedType 获取用于获取该成员的类对象

35 Type类的方法 方法 说明 GetElementType 当在派生类中重写时,返回当前数组、指针或引用类型包含的或引用的对象的Type
GetEvent 获取由当前Type声明或继承的特定事件 GetEvents 获取由当前Type声明或继承的事件 GetField 获取当前Type的特定字段 GetFields 获取当前Type的字段 GetInterface 获取由当前Type实现或继承的特定接口 GetInterfaces 当在派生类中重写时,获取由当前Type实现或继承的所有接口 GetMember 获取当前Type的指定成员 GetMembers 获取当前Type的成员(包括属性、方法、字段、事件等) GetMethod 获取当前Type的特定方法 GetMethods 获取当前Type的方法 GetProperties 获取当前Type的属性 GetProperty 获取当前Type的特定属性 InvokeMember 使用指定的绑定约束并匹配指定的参数列表,调用指定成员

36 归纳起来,得到一个Type实例的三种方法如下: (1)使用System.Object.GetType(),例如:
  Person pe=new Person(); //定义pe为person类的一个对象   Type t=pe.GetType();   这样t为pe的Type对象。   (2)使用System.Type.GetType()静态方法,参数为类型的完全限定名。例如:   Type t=Type.GetType("MyNs.Person");   其中,MyNs.Person为MyNs命名空间中的Person类,这样t为该类的Type对象。

37 (3)使用typeof运算符,例如:   Type t=typeof(Person);   其中Person为一个类,这样t为该类的Type对象。

38 2. System.Reflection反射命名空间
Assembly类:通过它可以加载、了解和操作一个程序集。 AssemblyName类:通过它可以找到大量隐藏在程序集的身份中的信息,如版本信息、区域信息等。 ConstructorInfo类:用于发现构造函数及调用构造函数。通过对ConstructorInfo调用Invoke来创建对象,其中ConstructorInfo是由Type对象的GetConstructors或GetConstructor方法返回的。 EventInfo类:通过它可以找到事件的信息。

39 FieldInfo类:通过它可以找到字段的信息。
MethodInfo类:通过它可以找到方法的信息。 ParameterInfo类:通过它可以找到参数的信息。 PropertyInfo类:通过它可以找到属性的信息。 MemberInfo类:它是一个抽象基类,为EventInfo、FieldInfo、MethodInfo、PropertyInfo等类型定义了公共的行为。 Module类:用来访问带有多文件程序集的给定模块。 DefaultMemberAttribute类:定义某类型的成员,该成员是InvokeMember使用的默认成员。

40 其中重要的Assembly类,它的常用属性和常用方法如下。
公共属性 说明 EntryPoint 获取此程序集的入口点 FullName 获取程序集的显示名称 Location 获取包含清单的已加载文件的路径或位置 ManifestModule 获取包含当前程序集清单的模块

41 Assembly类的常用方法 方法 说明 GetFiles 获取程序集清单文件表中的文件 GetModule 获取此程序集中的指定模块
GetModules 获取作为此程序集的一部分的所有模块 GetType 获取表示指定类型的Type对象 GetTypes 获取此程序集中定义的类型 LoadFile 加载程序集文件的内容 LoadFrom 在已知程序集的文件名或路径等信息时加载程序集 LoadModule 加载此程序集的内部模块

42 7.2.3 反射的应用示例 1. 通过反射查看类型的成员信息 查看类型信息的过程如下:
  查看类型信息的过程如下:   (1)获取指定类型的一个Type对象或Type对象数组。   (2)通过Type类的许多方法来发现与该类型的成员有关的信息。

43   【例7.3】 编写一个程序,通过反射输出System.Object类的方法、字段和构造函数的信息。
  解:先通过Type的GetTypes()方法获取System.Object类的Type对象t,然后用Type类的GetMethods()、GetFields()、GetConstructors()分别获取t对象的方法、字段和构造函数信息并输出。程序如下:

44 using System; using System.Reflection; namespace proj7_3 {   class Program   { static void Main(string[] args) {  string classname = "System.Object";   Console.WriteLine("{0}类",classname);   Type t = Type.GetType(classname);   MethodInfo[] m = t.GetMethods();   Console.WriteLine(" {0}的方法个数:{1}", t.FullName, m.Length);

45 foreach(MethodInfo item in m)
Console.WriteLine("\t{0} ",item.Name); FieldInfo[] f = t.GetFields(); Console.WriteLine(" {0}的字段个数:{1}", t.FullName, f.Length); foreach (FieldInfo item in f) Console.WriteLine("\t{0} ", item.Name); ConstructorInfo[] c = t.GetConstructors(); Console.WriteLine(" {0}的构造函数个数:{1}", t.FullName, c.Length); foreach (ConstructorInfo item in c) } 程序执行结果

46 2. 通过反射调用未知类的某方法   调用未知类的某方法的过程如下:   (1)假设一个未知类c属于某个DLL文件xyz.dll,采用Assembly.LoadFrom("xyz.dll")加载该程序集。   (2)调用assembly.GetTypes()方法得到一个Type对象数组t。   (3)通过Type.GetConstructor()方法得到某个对象的构造函数。   (4)通过ConstructorInfo.Invoke()方法调用构造函数创建未知类的对象s。   (5)通过对象s调用某方法。

47 【例7.4】 有一个项目proj7_4,通过添加代码文件模板向其中添加一个Sport.cs文件,该文件的内容如下:
  using System;   public abstract class Sport    //体育运动类   { protected string name;     //项目名 public abstract string GetDuration();  //获取比赛时间 public abstract string GetName(); //获取项目名 }

48 在命令行方式下使用以下命令生成Sport.dll文件:
csc/target:library Sport.cs

49 同样通过添加代码文件模板向其中添加一个SomeSports.cs文件,该文件的内容如下:
using System; public class Basketball : Sport { public Basketball() //篮球类 { name = "篮球"; } public override string GetDuration() { return "共4节,每节15分钟"; } public override string GetName() { return name; } }

50 public class Hockey : Sport //曲棍球类
{   public Hockey()   { name = "曲棍球"; }   public override string GetDuration()   {  return "两个半场,各35分钟"; }   public override string GetName()   {  return name; } } public class Football : Sport //足球类   public Football()   { name = "足球"; }   { return "两个半场,各45分钟"; }   { return name; }

51   在命令行方式下使用以下命令生成SomeSports.dll文件:
  csc/target:library /reference:Sport.dll SomeSports.cs   这样就生成两个动态链接库文件Sport.dll和SomeSports.dll。现要在Program类Main中设计相应代码,根据用户选择的体育项目输出相应的比赛时间。

52 解:由于在设计Program. cs程序时,提供的Sport. dll和SomeSports
  解:由于在设计Program.cs程序时,提供的Sport.dll和SomeSports.dll都是动态链接库,不知道其中每个类是如何声明的,只知道GetName()和GetDuration()两个方法的功能,为此采用反射技术。

53 设计Program.cs程序如下: using System; using System.Reflection;
namespace proj4_3 {  class Program   { static void Main(string[] args) { int i, j;   if (args.GetLength(0) < 1) //命令行输入的参数不正确 Console.WriteLine("用法:Program dll库名"); else { Assembly assembly=Assembly.LoadFrom(args[0]); //获取程序集对象 Type[] types = assembly.GetTypes(); Console.WriteLine(assembly.GetName().Name + "包含的项目名如下:"); for (i = 0; i < types.GetLength(0); ++i) Console.WriteLine("\r" + i + ": " + types[i].Name); i = types.Length - 1;

54 Console.Write("请选择(0-" + i + "):");
j = Convert.ToInt32(Console.ReadLine()); Console.WriteLine(); if (types[j].IsSubclassOf(typeof(Sport))) //若types[j]是Sport的子类 { ConstructorInfo ci = types[j].GetConstructor(new Type[0]); Sport sport = (Sport)ci.Invoke(new Object[0]); //创建sport对象 Console.WriteLine(sport.GetName()+"比赛时间:"+ sport.GetDuration()); } else Console.WriteLine(types[j].Name + "不属于指定的体育项目");

55   在命令行中输入以下命令Program.exe文件:
  csc/reference:Sport.dll Program.cs   在命令行中输入以下命令执行Program.exe:   Program SomeSports.dll   程序的一次执行结果如图7.3所示,在出现各种提示后输入2,输出足球的比赛时间。

56 以前我们必须知道一个类的完整定义,才可以使用这个类。 采用反射技术:
  以前我们必须知道一个类的完整定义,才可以使用这个类。   采用反射技术: 只需知道类和方法,可以通过反射技术使用它。 .dll

57 ━━本章完━━


Download ppt "第7章 泛型和反射 7.1 泛 型 7.2 反 射."

Similar presentations


Ads by Google