这篇文章延续《C# 基础知识系列- 5 反射和泛型》,继续介绍C#在反射所开发的功能和作的努力。上一篇文章大概介绍了一下泛型和反射的一些基本内容,主要是经过获取对象的类型,而后经过这个类型对象操做对象。这一篇介绍一个在反射中很重要的内容:特性,以及上一篇未完成的内容——泛型在反射中的引用。编程
特性是一种类加强技术,配合解析对应的解析方法能够完成不少类本来没有的功能。特性本质是一种标签,能够标注在类、方法、属性等。它是类自己的一种信息扩展,就像生活中一我的只有一个身份证号,可是能够有多个身份同样,而这些多出来的身份对于类来讲就是特性。特性虽然是对类的加强,但不局限于在类上作标记,属性、方法上均可以。c#
在C#中特性分为三种,位映射特性、自定义特性和伪自定义特性。编程语言
位映射特性,举个例子,在C#中一个类会有public、private、abstract(抽象类)、saled(不能继承)等修饰符,而这些修饰符在C#编译的过程当中会生成一串二进制码,里面存放就是 是不是public、是不是private 等。这些就是位映射特性的一部分,位映射特性对咱们来讲是没法进行扩展和修改的,因此就不作更多的介绍。测试
咱们一般说的特性通常指的是自定义特性,这部分特性也是咱们可以扩展的,也是咱们实际开发中用的特性。spa
如何正确的定义一个特性呢?在C#中,特性也是类的一种。因此声明一个特性,就如同声明一个类同样,不一样的是,这个类指定一个根父类是System.Attribute
。全部自定义特性都是这个类的子类或者后代类,无一例外。同时,C#提倡在定义一个特性类的时候,类名应当以Attribute
结尾,在使用的时候能够自动忽略。设计
示例:code
public class DemoAttribute : Attribute { }
以上实例就是定义了一个很普通的特性类,用了也没有任何用的特性。由于特性只是一种标签。这个特性类能够用在任何支持特性的地方,当这个特性标记一个类的时候,目标类的子类也将自动获取这个特性。对象
以上是一个特性的默认行为,若是咱们想要对此作必定限制的话,那么就须要用到特性System.AttributeUsageAttribute
。这个特性类用来控制特性的使用方式。继承
public bool Inherited { get; set; }// 该特性是否能够被子类继承,默认是 True public bool AllowMultiple { get; set; }// 一个类是否能够屡次使用该特性作标记,默认是 False public AttributeTargets ValidOn { get; }//获取一组值,这组值标识指示的属性可应用到的程序元素,该参数使用构造方法赋值
咱们再来看看AttributeTargets
里有些什么吧。接口
[System.Flags] public enum AttributeTargets { Assembly = 1,// 表示特性是用在 Assembly上的,不经常使用 Module = 2, //特性是用在 Module上的,不经常使用 Class = 4, // 表示特性是用来类上的 Struct = 8, //表示用在结构体上 Enum = 16, // 0x00000010 表示用在枚举上 Constructor = 32, // 0x00000020 构造方法 Method = 64, // 0x00000040 普通方法 Property = 128, // 0x00000080 属性 Field = 256, // 0x00000100 字段 Event = 512, // 0x00000200 事件 Interface = 1024, // 0x00000400 接口 Parameter = 2048, // 0x00000800 方法的参数 Delegate = 4096, // 0x00001000 委托 ReturnValue = 8192, // 0x00002000 返回值 GenericParameter = 16384, // 0x00004000 泛型参数 All = GenericParameter | ReturnValue | Delegate | Parameter | Interface | Event | Field | Property | Method | Constructor | Enum | Struct | Class | Module | Assembly, // 0x00007FFF ,全部 }
咱们经常使用的限制是ALL或者类等,限制也能够是多个,写法以下:限制A|限制B|限制C
,表示A、B、C三种限制共存。具体原理是由于 AttributeTargets
是支持位运算的枚举,经过必定的位运算能够在一个值中间存放多个枚举。
说了这么多,咱们本身从新写一个特性类吧:
1.限定只能给类使用的特性
[AttributeUsage(AttributeTargets.Class)] public class DemoAttribute : Attribute { }
2.限定只能给方法使用的特性
[AttributeUsage(AttributeTargets.Method)] public class DemoAttribute : Attribute { }
3.限定不能继承的特性
[AttributeUsage(AttributeTargets.All, Inherited = false)] public class DemoAttribute : Attribute { }
4.限定类和枚举可使用,但不能继承的 特性
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Enum, Inherited = false)] public class DemoAttribute : Attribute { }
须要注意的一点就是,若是要指定是否能够被继承或者是否容许屡次使用 这两个属性则须要先指定特性的做用范围,即限定是类能使用仍是全部均可以。
咱们自定义了一个特性,就必须使用它才能会有意义,不然它只是一个普通的类。那么咱们该如何使用呢?其实在上一节中咱们隐晦的介绍了特性的使用方式。就是用中括号包裹起来,给类、属性、方法等标记起来。
首先咱们定义一个贼普通的特性:
public class DemoAttribute : Attribute { }//没有任何限制,能够用在任何支持特性的地方
而后使用它:
[Demo]//[DemoAttribute] public class TestDemo { }
如示例所示,在类上面添加[Demo]
标记,表示这个类应用了特性DemoAttribute,也可使用类名,可是C#会自动忽略类名中结尾的Attribute。固然有的人会把特性写在类或者方法等声明的同一行开头位置,不过我通常会写在不一样行,毕竟阅读上简单明了。
咱们以前说过,抛开它集成自Attribute类不提,它也是一个类。既然是类,那么就会有属性。那么如今定义一个带属性的特性类:
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Enum, Inherited = false)] public class DemoAttribute : Attribute { public string Name { get; set; } }
该特性声明了一个变量,使用方式以下:
[Demo(Name = "测试")] public class Student{ }
DemoAttribute是一个只能用在 类、枚举 上的特性,有一个属性是Name。在使用的时候能够用(属性名="属性值")
的方式为属性赋值。
更多的使用方式:
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Enum, Inherited = false)] public class DemoAttribute : Attribute { public string Name { get; set; } public int Age { get; set; } public DemoAttribute(int age) { Age = age; } }
使用:
[Demo(10,Name = "测试")] public Student(){ }
若是特性类声明了构造方法,那么在使用的时候,优先按照构造方法的顺序进行赋值,而后使用属性名=属性值
的方式为其余属性进行赋值。
在第一节中介绍了如何声明一个特性和使用特性,可是没有反射或者类加载技术,那么特性的做用就并无想象中的那么大。就像人有多个身份,可是也得有对应的公司或者对应的环境。好比说,王XX有个身份是某XX公司老总,那么XX公司得须要在工商局注册登记,他这个身份才会有效。若是没有登记,那么这个身份也就是个虚名。当特性离开了反射,离开了类加载技术,特性就是摆设。固然这部分只限于自定义特性,由于C#内置的一些特性涉及到另外的技术:动态编译,或者须要编译器的配合。咱们自定义的特性显然没有这些特权,因此必须咱们手动开发对应的行为和规范。
首先,声明一个类和特性:
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Enum, Inherited = false)] public class DemoAttribute : Attribute { public string Name { get; set; } public int Age { get; set; } public DemoAttribute(int age) { Age = age; } } [Demo(10,Name = "测试")] public class Student { }
var stuType = typeof(Student);
上述代码先获取到一个类的类型对象,而后调用:
IEnumerable<CustomAttributeData> attrs = stuType.CustomAttributes;
将获取到这个类上声明的全部的自定义的特性,不过获取到的是一个CustomAttributeData
,这个类封装了一个特性的特征,可是在咱们使用起来会很困难,并且咱们更多的须要获得特性自己的对象,而不是这种须要咱们进一步处理的对象。那么,调用:
IEnumerable<Attribute> data = stuType.GetCustomAttributes(typeof(DemoAttribute));
经过上述方法就能够获取到一组类型是DemoAttribute
的特性对象。
那么回想一下为何是一组?在AttributeUsageAttribute
有一个AllowMultiple
属性,这个属性就是用来标记这个特性是否能够标注多个,也就是在同一目标上屡次使用,若是这个值为True,则在此处将获取不定个,不然最多一个。具体取决于对目标作了多少标记。
获取到特性以后,依据实际需求进行开发。这里就不作过多介绍了,在后续篇幅中会对这部分的使用作更多的介绍。
特性不止能够标记在类上,还能够标记在属性、方法上。那么这些元素应该如何获取对应的特性呢?
1. 属性
var stuType = typeof(Student); var property = stuType.GetProperties()[0];//假设类有一个Property var attrs = property.GetCustomAttributes(typeof(DemoAttribute));
2. 方法
var stuType = typeof(Student); var method = stuType.GetMethods()[0];// 假设类有一个方法 var attrs = method.GetCustomAttributes(typeof(DemoAttribute));
须要注意的地方是,
var
关键字;DemoAttribute只是一个代指,不是特指以前声明的DemoAttribute特性类,由于以前声明的特性类没有对属性和方法进行支持,因此在本节中直接使用会编译不经过。
特性就先简单的介绍到这里,特性能够标记给不少目标好比程序集、模块、类等一系列,但实际开发至少是Web开发中,更多的是标记类、方法、属性等。这里只是介绍了特性的声明和使用,可是没有介绍实际开发中特性的使用,这部分有机会在后续篇幅中介绍吧。由于我也用的不是不少。
以前在《C# 基础知识系列- 5 泛型和反射》介绍过,C#的泛型不会在编译过程当中抹去痕迹,意思就是咱们能够经过反射获取到对象的实际泛型类型。那么如何获取呢?
var stuType = typeof(Student); // 获取类的泛型参数 var genericTypes = stuType.GenericTypeArguments; var method = stuType.GetMethods()[0]; // 获取方法的泛型参数 var types = method.GetGenericArguments();
这个问题,在我写Java代码的时候,困扰了我好久,没有很好的办法。可是在C#中,我能够不用考虑这个问题。
反射在各大编程语言中是一个很重要的特色,泛型、特性在泛型中扮演着很重要的角色。反射在实际开发中扮演着很重要的角色,可是咱们在开发中必须慎重考虑反射的使用。
到目前为止,反射介绍告一段落,但这不是结束。由于反射是个可深可浅的内容,目前只是介绍了依稀概念和理论上的一些内容,而更多的则隐藏在实际开发中,这时候就须要结合需求进行设计和代码编写了。
更多内容烦请关注 个人博客《高先生小屋》