一、泛型
为什么要有泛型集合(List<T>,Dictionary<K,V>,LinkedList<T>)?
1.为了避免装箱拆箱;
2.复用集合类里的 代码(算法) List<string> List<int>
1.概念
1.1官方:是一种特殊的【算法重用】机制。允许程序员在代码中将 变量或参数的类型 先用【类型占位符】来代替,等到运行的时候再根据传入的【类】来替换。
通俗:类也可以带参数了!但是这个参数必须是 类型!用来在 类的代码中 暂时代替 类的位置,然后会在运行时,被替换。
public class MyList<T> { T[] arr; public MyList(T[] arrp) { arr = arrp; } }
2.语法
2.1泛型的运行
当 运行的时候,JIT会帮我们生成泛型类的不同版本,通过如下代码可以验证:
//aa是MyList中一个int的静态变量 MyList<string>.aa = 11; MyList<int>.aa = 22; //照理说,同为MyList类的静态变量,应该保存最后一次赋值的 22 //但是输出的时候,确实生成了两个不同的版本 Console.WriteLine(MyList<string>.aa.ToString());//11 Console.WriteLine(MyList<int>.aa.ToString());//22 //由此看出,JIT在运行代码的时候,分别为带不同的 泛型参数 的MyList类生成了不同的版本。 MyList<string>.dd = new Dog(); MyList<string>.dd.age = 11; MyList<int>.dd = new Dog(); MyList<int>.dd.age = 22; Console.WriteLine(MyList<string>.dd.age.ToString());//11 Console.WriteLine(MyList<int>.dd.age.ToString());//22
【问题】为什么JIT在执行的是有要根据泛型类 MyList<T>里的T 来产生不同的 MyList类的版本?
因为 只有当T确定为某种类型的时候,JIT才有可能计算出 当前MyList类的对象在内存中要分配多大的空间。
3.泛型约束
3.1基类约束
public class House<TPet,TPet2> where TPet:Dog //约束 TPet必须是 Dog或Dog的子类 where TPet2:Cat //约束 TPet必须是 Cat或Cat的子类 { TPet pet1; TPet2 pet2; }
测试:
//报错,因为string 不是 Dog或它的子类 House<string, Cat> myHouse = new House<string, Cat>(); //成功,因为 DogMini 继承于 Dog House<DogMini, Cat> myHouse2 = new House<DogMini, Cat>();
注意:2个禁忌
public class House<TPet,TPet2> where TPet : Nullable<int>//错:不能约束 为 Nullable 的子类,因为语法有歧义 where TPet2:string//错:不能约束为 密封类的 子类,因为不存在子类!
3.2接口约束
interface IGetReward<T> { T GetReward(); } interface IWalk { void Walk(); } interface ISing<T> { T Sing(); } class MyPetPlay<T, V> where T : IGetReward<T> where V : IWalk, ISing<V> { public MyClass(T t,V v) { t.GetReward(); //直接调用接口方法 v.Walk(); v.Sing(); } }
3.3 struct/class约束
public struct A { } public class B { } public class C<T> where T : struct //T 只能是 值类型 { } public class C2<T> where T : class //T 只能是 引用类型 { } 测试: C<int> c1 =new C<int>(); C<Dog> c2 =new C<Dog>();//异常 C2<int> c3 =new C2<int>();//异常 C2<Dog> c4=new C2<Dog>();
3.4构造器约束
约束 泛型参数 的实参必须 是一个 有无参构造函数 的类。
注意:C#现在只支持 无参构造函数 约束。
class Dog { public Dog(){ } } class Cat { public Cat(int age){ } } class Pet<T> where T : new() { T t; public Pet() { t = new T(); } } public static void Main(string[]arg) { Pet<Dog> c = new Pet<Dog>(); Pet<Cat> d = new Pet<Cat>();//异常,因为Cat没有无参构造函数 }
4.泛型方法
4.1概念:
即使所在类不是泛型类,或者方法的泛型参数不再泛型类的参数列表里,也依然使用泛型的方法。
/// <summary> /// 自定义转型方法 /// </summary> /// <typeparam name="T">被转集合的元素类型</typeparam> /// <typeparam name="R">要转成的集合的元素类型</typeparam> /// <param name="list">待转换的集合</param> /// <returns>转换后的结果集合</returns> public List<R> SelfCast<T, R>(List<T> list) where R:class //规定R必须是引用类型 { //创建一个要返回的 集合 List<R> resList = new List<R>(); //遍历 要转换的集合元素 for (int i = 0; i < list.Count; i++) { //取出元素 T t = list[i]; //转换成 指定类型 R r = t as R; //as 只能转换 引用类型对象 所以 方法定义了 where R:class //加入 到 要返回的集合中 resList.Add(r); } return resList; }
二、反射
1.概念
反射机制是一种运行时获取类(Type对象)和动态调用对象的成员的机制。
a.可以获取有关已加载的程序集和在其中定义的类型(如类、接口和值类型)的成员信息;
b.可以使用反射在运行时创建指定类的对象,以及调用和访问这些对象的成员。
这种动态获取的信息以及动态调用对象的方法的功能称为反射机制。
2.动态获取类的成员
2.1Type类
概念:每个类(class)在内存中都有一个对应的Type类对象,class中定义的成员信息都存在这个Type类对象中。
通俗:程序员写的类,在运行时也会变成一个对象,Type的对象。
Dog d1 = new Dog(); Dog d2 = new Dog(); //MessageBox.Show((d1==d2).ToString());//false //通过 对象 获取对象所属 的 【类的对象】 Type t1 = d1.GetType(); Type t2 = d2.GetType(); MessageBox.Show((t1==t2).ToString());//true 两个Dog对象的Type都是Dog类
JIT生成Type类的对象:只有在程序中第一次遇到某个类的时候,才会去加载这个类,并解析类的内容,然后生成对应的Type对象,并将类解析出来的信息装入Type对象中存入内存,方便以后复用。
2.1.1获取方式:
a. 通过对象的GetType()方法: 【Object类中有个 GetType()实例方法】
Dog dog = new Dog(); Type type= dog.GetType();//获取了Dog类的Type对象 Dog dog2 = new Dog(); Type type2= dog2.GetType();//获取了Dog类的Type对象 Console.WriteLine(type==type2);//true
特点:必须先创建对象,才能获取对象对应类的类型对象
b.通过typeof关键字+类
Type type3 = typeof(Dog); Console.WriteLine(type==type3);//true
特点:只要获取类,就能获取类的类型对象
c.通过当前运行的程序集获取 + 类全名称字符串
Type type5 = Assembly.GetExecutingAssembly().GetType("MyNameSpace.Dog");
特点:通过类的全名称字符串即可获取某个程序集里的对应类型对象。
2.1.2根据Type创建对象
//使用激活器对象Activator (相当于:Dog d6 =new Dog();) Dog dog6 = Activator.CreateInstance(type) as Dog; //注意:默认调用了 对应类的 无参构造函数
//在创建对象时根据构造函数参数来调用指定的构造函数 Dog dog7 = Activator.CreateInstance(type, "德国牧羊犬", "2") as Dog;
注意:Activator.CreateInstance(t) 虽然会按照t的类型创建一个对象的对象,但都是以object返回,如需使用,需要转成 对应的类型。
2.1.3根据Type获得类的字段
2.1.4根据Type获取类的方法和属性
Type类的常见属性:
Assembly:获取该Type对应类所在的程序集对象。
FullName:Type对应类的全名称【命名空间名+类名】
GenericTypeArguments:该类的类型参数(泛型参数)数组
IsAbstract:是否是抽象类
IsClass:是否是一个类,而不是接口或值类型
IsGenericType:是否是泛型类
IsInterface:是否是接口
Type类的常见方法:
3.Assembly 程序集类
3.1概念
就是 程序集文件 加载到 内存里后 的对象。里面包含了各种方法,可以获取程序中的各种类型。
3.2Assembly.GetTypes()获取到的类型数组信息:
3.3获取Assembly的方式
3.3.1通过 Assembly的 GetExecutingAssembly 方法,获取正在执行的 程序集对象。
Assembly ass = Assembly.GetExecutingAssembly();
补充:
添加引用的真实作用:
1.修改了项目的工程文件.csproj(配置文件),在里面添加了被引用的项目所在的位置。
2.那么编译器在生成 宿主程序的时候,会先编译生成 引用的项目,如果引用的项目的程序集生成成功,则把程序集文件 复制到 宿主程序的 bin目录下;
3.在宿主程序中可以直接访问 被引用的项目 里的命名空间和类了!(其实是在运行的时候,宿主程序主动将被引用程序加到了 宿主程序的应用程序域中了。)
3.3.2通过Type.Assembly获取Type所在的程序集对象。
MODEL.Cat cat = new MODEL.Cat(); Type catType = cat.GetType(); Assembly assModel = catType.Assembly;
3.3.3通过程序集文件路径 加载Assembly
Assembly ass3 = Assembly.LoadFrom(@"F:\DAL\bin\Debug\DAL.dll");
4.反射应用
4.1判断类是否实现某个接口
Type对象的 IsAssignableFrom方法
private void btnInterfact_Click(object sender, EventArgs e) { Type typeCat = typeof(Cat); Type typeString = typeof(String); Type typeIBark = typeof(IBark); Console.WriteLine(typeIBark.IsAssignableFrom(typeCat));//true Console.WriteLine(typeIBark.IsAssignableFrom(typeString));//false }
4.2动态调用方法
//获取类名 string strClassName = System.Configuration.ConfigurationManager.AppSettings["type"]; //根据类名创建 类型对象 Type typeCat = Assembly.GetExecutingAssembly().GetType(strClassName); //获取要调用的方法名称 string strMethod = System.Configuration.ConfigurationManager.AppSettings["methodName"]; //获取 一个方法对象 MethodInfo method = typeCat.GetMethod(strMethod); //创建 了一个 指定的对象 object objCat= Activator.CreateInstance(typeCat); //调用方法 并为方法传入一个对象(objCat)作为 方法的作用域,最终获取方法的返回值 string returnValue = method.Invoke(objCat,null).ToString(); MessageBox.Show(returnValue);
补充:为什么使用配置文件?
因为配置文件可以不编译到 程序集中,随时可以进行方便的修改,程序集在运行的时候可以读取配置文件里配置的信息,从而影响程序内部的运行。
复习序列化
using System.Runtime.Serialization.Formatters.Binary;
1.序列化一个对象,将对象中的重要信息转成二进制数据,并提供给指定的流用来操作。
Dog dog = new Dog("瑞奇", 1); using (FileStream fs = new FileStream(@"F:\广州传智.Net上课视频\DotNet03三期\Day2013-02-02-反射-Asp.net\Code\AspNet\01序列化\bin\Debug\dog.bin", FileMode.OpenOrCreate)) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(fs, dog); MessageBox.Show("序列化成功~~!"); }
序列化时:
将对象所对应的类所在程序集的名字 记录,将类的全名称记录,将对象里的 每个 字段 的名字和值记录;
并将这三个数据转成二进制数据存入 对应问流。
将对象转成byte数组
using (MemoryStream ms = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(ms, dog); MessageBox.Show("序列化成功~~!"); byte[] arr = ms.ToArray(); }
2.反序列化:将被序列化对象所产生的二进制数据获取,并根据二进制数据里的 类名 new一个新的对象,将二进制数据里保存的字段的值,设置给新对象对应的字段(真个反序列化的过程都是通过反射实现的)
using (FileStream fs = new FileStream(@"F:\广州传智.Net上课视频\DotNet03三期\Day2013-02-02-反射-Asp.net\Code\AspNet\01序列化\bin\Debug\dog.bin", FileMode.Open)) { BinaryFormatter bf = new BinaryFormatter(); Dog dog = bf.Deserialize(fs) as Dog; MessageBox.Show("反序列化成功~~!" + dog.age + "," + dog.name); }
特性
概念:Attribute用来对类、属性、方法等标注额外的信息,贴一个标签(附着物)
通俗:就是为类 贴附加的标签。
语法:
public class CNameAttribute : Attribute //一定要继承 Attribute { string strCName = string.Empty; /// <summary> /// 获取中文名称 /// </summary> public string StrCName { get { return strCName; } set { strCName = value; } } public CNameAttribute() { } public CNameAttribute(string strCName) { this.strCName = strCName; } }
为类和类的成员 添加 特性(标签),凡是"贴标签"就new 了一个 特性对象!设置给了 被贴 的对象
[特性.CName] public class Classes { [特性.CName] public void Test() { } [特性.CName] /// <summary> /// 班级表ID /// </summary> public int? CID { set { _cID = value; } get { return _cID; } } /// <summary> /// 班级名称 /// </summary> [特性.CName] public string CName { set { _cName = value; } get { return _cName; } } }
【源码】:可见 每个特性标签 都被实例化成了对象 存放在目标对象中
为特性的构造函数赋值:
其实,是省略了 小括号
使用命名参数的方式 为构造函数的参数赋值!
[特性.CName(StrCName="中文名称,哈哈哈!")] public string CName { set { _cName = value; } get { return _cName; } }
[特性.CName("中文名称,哈哈哈哈")] public string CName { set { _cName = value; } get { return _cName; } }
再次强调,我们在为类或类的成员 贴“标签”的时候,其实是为该类 的Type对象里 对应的成员对象(属性/方法...对象) 内部 添加了一个 “标签”对象。
注意:特性对象 是加在 Type对象中的,在类的实例中没有存在。
特性的使用:
1.判断成员是否加了特性 IsDefined
//获取 Classes 类型对象 Type t = typeof(MODEL.Classes); //获取所有属性对象 PropertyInfo[] pros = t.GetProperties(); //获取 CNameAttribute特性 类型对象 Type tAttr = typeof(CNameAttribute); //遍历属性对象 foreach (PropertyInfo pi in pros) { //1.判断属性是否 有 贴 CNameAttribute 特性 if (pi.IsDefined(tAttr, false)) { Console.WriteLine(pi.Name); } } //2.判断类是否贴了 CNameAttribute特性 bool isCA= t.IsDefined(tAttr, false); //3.判断 方法 是否贴了 CNameAttribute特性 MethodInfo mi = t.GetMethod("Test"); bool isMA = mi.IsDefined(tAttr, false);