一:在运行期间检查代码和动态编程
介绍自定义特性、反射和动态编程。自定义特性允许把自定义元数据与程序元素关联起来。
这些元素局是在编译过程中创建的,并嵌入到程序集中。
反射是一个普通术语,它描述了运行过程中检查和处理程序元素的功能。
反射允许完成一下任务
1):枚举类型的成员
2):实例化新对象 --CreateInstance()
3):执行对象的成员
4):查找类型的信息
5):查找程序集的信息
6):检查应用于某种类型的自定义特性
7):创建和编译新程序集。
二:自定义特性
对于特殊的特性,编译器可以以特俗的方法定制编译过程。例如:可以根据StructLayout特性中的信息在内存中布置结构。
自定义特性不会影响编译过程,因为编译器不能识别他们,但这些特性在应用于程序元素时,可以在编译好的程序集中用作元数据。
这些元数据在文档说明中非常有用。但是,使自定义特性非常强大的因素是使用反射,代码可以读取这些元数据,使用它们在运行期间做出决策。自定义特性可以直接影响代码运行的方式。
用处:自定义特性可以用于支持对自定义许可类进行声明性的代码访问安全检查,把信息与程序元素关联起来,程序元素由测试工具使用,或者在开发可扩展的架构时,允许加载插件或模块。
2.1:编写自定义特性
编译器遇到代码中某个应用了自定义特性的元素时,该如何处理。
[FieldName("SocialSecurityNumber")] public string SocialSecurityNumber { get;set; }
说明:当c#编译器发现这个属性(property)应用了一个FieldName特性时,首先会把字符串Attribute追加到这个名称后面,形成一个组合名称FieldNameAttribute,然后在其搜索路径的所有名称空间(即在using语句中提及的名称空间)中搜索有指定名称的类。
如果该特性的名称以Attribute结尾。编译器就不会把该字符串加到组合名称中,而是不修改该特性名。上面代码等价于
[FieldNameAttribute("SocialSecurityNumber")] public string SocialSecurityNumber { get;set; }
编译器会找到含有该名称的类,且这个类直接或间接派生自System.Attribute。编译器还认为这个类包含控制特性用法的信息。特别是属性类需要指定:
1:特性可以应用到哪些类型的程序元素上(类、结构、属性和方法)
2:它是否可以多次应用到同一个程序元素上
3:特性在应用到类或接口上时,是否由派生类和接口继承
4:这些特性由那些必选和可选参数
如果编译器找不到对应的特性类,或者找到一个特性类,但使用特性的方式与特性类中的信息不匹配,编译器就会产生一个编译错误。例如:特性类指定只能用于类,但我们把它引用到结构上,这样编译器就会产生一个编译错误。
[AttributeUsage(AttributeTargets.Property,AllowMultiple=false,Inherited=false)] public class FieldNameAttribute:Attribute { private string _name; public FieldNameAttribute(string name) { _name=name; } }
2.2:指定AttributeUsage特性
AttributeUsage主要用于标识自定义特性可以应用到哪些类型的程序元素上。这些信息由它第一个参数给出,该参数是必选的。该类型是枚举类型AttributeTargets。指定特性可以应用于哪些类型上。
AttributeTargets枚举成员如下:
微软文档路径:https://docs.microsoft.com/zh-cn/dotnet/api/system.attributetargets?f1url=https%3A%2F%2Fmsdn.microsoft.com%2Fquery%2Fdev15.query%3FappId%3DDev15IDEF1%26l%3DZH-CN%26k%3Dk(System.AttributeTargets.GenericParameter);k(DevLang-csharp)%26rd%3Dtrue&view=netframework-4.8
注意:Assembly和Module。特性可以应用到真个程序集或模块中 ,而不是引用到代码中的一个元素上,在这种情况下,这个特性可以放在源代码任何地方,但需要用关键字Assembly或Module作为前缀
[assembly:SomeAssemblyAttribute(Parameters)]
[moudule:SomeAttribute(parameters)]
也可以使用Or运算符把这些值组合起来。
[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field,AllowMultiple=false,Inherited=false)] public class FileNameAttribute:Attribute
AttributeUsage特性还包含另外两个参数:AllowMultiple和Inherited。他们用不同的语法来指定:
AllowMultiple参数表示一个特性是否可以多次应用到同一项上,这里把它设置为false,表示如果编译器遇到下述代码,就会产生一个错误:
[FieldName("SocialSecutiryNumber")] [FieldName("NationInsuranceNumer")] public string SocialSecutiryNumber { }
把Inhertited参数设置为true,就表示应用到类或接口上的特性也可以自动应用到所有派生类或接口上。如果特性应用到方法或属性上,它就可以自动应用到该方法或属性等重写版本上。
2.3:指定特性参数
如何指定自定义特性接受的参数。
反射会从程序集中读取元数据(特性),并实例化他们表示的特性类。因此编译器需要确保存在自定义特性指定的要求特性,才能在运行期间实例化指定特性。
三:反射
通过System.Type类,可以访问关于 任何数据类型的信息。
System.Reflection.Assembly类,它可以用于访问给定程序集的相关信息,或者把这个程序集加载到程序中。
3.1:System.Type类
Type t=typeof(double);//这个做的目的只为了存储类型的引用
Type类实际上时一个抽象的基类,只要实例化一个Type对象,实际上就实例化了Type一个派生类。尽管派生类只提供各种Type方法和属性的不同重载,这些方法和属性返回对应数据类型的正确数据。
Type有与每种数据类型对应的派生类。他们一般不添加新的方法或属性。
通常,获取指向任何给定类型的Type引用有3种常用方式。
1:使用typeof运算符
Type t=type0f(double)
2:使用GetType()方法,所有类都会从System.Object继承这个方法。
double d=10; Type t=d.GetType();
调用GetType()方法,而不是把类型的名称作为其参数。但是,返回的Type对象仍只与该数据类型相关:它不包含与该类型的实例相关的任何信息。如果引用了一个对象,但不能确保该对象上是哪个类的实例,GetType()方法就很有用。
3:调用Type类的静态方法GetType()
Type是许多反射功能的入口。它实现许多方法和属性,注意:可用的属性都是只读的:可以使用Type去顶数据类型,但不能使用它修改该类型。
3.1.1Type的属性
由Type实现的属性可以分为下述三类。许多属性都可以获取包含与类相关的各种名称的字符串。
属性还可以进一步获取Type对象的引用,这些引用表示相关的类
许多布尔属性表示这种类型时一个类,还是一个枚举等。
这些特性包括 IsAbstract、IsArray、IsClass、IsEnum、IsInterface、IsPointer、IsPrimitive(一种预定义的基本元数据类型)、IsPublic、IsSealed、以及IsValueType
参考官方文档:https://docs.microsoft.com/zh-cn/dotnet/api/system.type?view=netframework-4.8
IsAbstract:获取一个值,通过该值指示 Type 是否为抽象或者是接口。当类型定义为abstract和Interface是,返回true,其他都返回false。
如果当前Type表示泛型类型或泛型方法的定义中的类型参数,则此属性始终返回false。
IsArray:是否数组、IsClass 否是一个类或委托,即。不是值类型或接口
IsInterface:是否是接口,即:不是类或值类型
IsByRef:是否是引用传递
IsPrimitive:是否是基元类型 就是clr自带的基本类型如int,double 等。
IsPointer:是否为在指针
IsPublic:是否声明公共类型
IsSealed:是否声明为封装类
IsValueType:是否为值类型
IsVisible:是否可由程序集之外的代码访问的值,true
是公共类型或公共嵌套类型从而使所有封闭类型都是公共类型 ,否则就是false
IsSerializable:是否可序列化 ,要在相应的元素设置 [Serializable] 特性 ,如果有这个就是true。
还有一些其他的,可以去上面给出的连接地址里查找。
也可以获取在其中定义该类型的程序集的引用
Type t=typeof(Vector); Assamebly containingAssembly=new Assembly(t);
3.1.2 方法
System.Type的大多数方法都用于获取对应数据类型的成员信息“构造函数、属性、方法和事件。
有很多方法,但是模式都差不多。
如:GetMethod()和GetMethods()。GetMethod()方法返回System.Reflection.MethodInfo对象的一个引用,其中包含一个方法的细节信息。GetMethods()返回这种引用的一个数组,区别是这个返回所有方法的细节信息。该方法包含特定的参数列表。这两个方法都有重载方法,重载方法有一个附加的参数,即BindingFlags枚举值,该值表示应返回那些值成员,例如:返回公有成员、实例成员和静态成员。
如果不带参数就是返回数据类型的所有公共方法。
图表中,从上到下分别为:构造函数、事件、字段、成员、方法、属性。
GetMember()和GetMembers()方法返回数据类型的任何成员或所有成员的详细信息,不管这些成员是构造函数、属性和方法。