反射在项目中常常会配合特性使用, 所以把这俩个放到一起来说,对于理解他们更有帮助。 反射是通过dll读取类型的信息,通过反射创建对象,调用其中的方法,查找程序集信息等,功能非常多,我们在这里只介绍他的常用的功能。 特性其实比较简单,他可以说是类的一个补充,给一个类多添加了一部分信息。
一、自定义特性
.NET允许用户自定义特性,定义的特性并不会影响编译过程,因为编译器不能识别他们,但这些特性在应用于程序元素时,可以在编译好的程序集中用作元数据,这也就很好的配合了反射,通过反射读取特性的信息, 就可以做很多事,下面来看一下用户控制特性的信息:
- 特性可以应用到哪些类型的程序元素上(类、结构、属性、和方法等)
- 它是否可以多次应用到同一个程序元素上
- 特性在应用到类和接口上时,是否由派生类和接口继承
- 这个特性有哪些必选和可选参数
上代码看一下特性的使用,特性都需要继承Attribute类:
//参数1: 枚举,指定特性可以应用到哪些元素上 //参数2:是否可以有多个 //参数3:能否被派生类继承 [AttributeUsage( AttributeTargets.Property,AllowMultiple =false,Inherited =false)] public class FieldNameAttribute:Attribute { private string name; public FieldNameAttribute(string name) { this.name = name; } } /// <summary> /// 测试 /// </summary> [FieldName("test")] public class Test { [FieldName("test")] public int Id { get; set; } }
如果把特性放到类上,编译器就会产生一个错误。
AttributeUsage特性:
特性类本身用一个特性类(AttributeUsage)标注,这是微软定义的一个特性,它主要功能用于标识自定义特性可以应用到哪里,上面的代码中可以看出,第一个参数是指定特性的位置,大家可以看一下AttributeTargets枚举的参数, 就可以了解都可以使用在哪些元素了。也可以使用OR运算符(AttributeTargets.Property |AttributeTarget.Field ),当然也可以直接选择all。
指定特性参数:
以上的例子中使用构造函数的方式标注,如果没有对应的构造函数,编译器会报错,因为反射会从程序集中读取元数据,并实例化它们表示的特性类,所以需要提供对应构造函数,才能在运行期间实例化指定的特性。
指定可选参数:
//参数1: 枚举,指定特性可以应用到哪些元素上 //参数2:是否可以有多个 //参数3:能否被派生类继承 [AttributeUsage( AttributeTargets.Property,AllowMultiple =false,Inherited =false)] public class FieldNameAttribute:Attribute { private string name; public string Name { get; set; } //public FieldNameAttribute(string name) { // this.name = name; //} } /// <summary> /// 测试 /// </summary> // [FieldName("test")] public class Test { [FieldName(Name ="aaa")] public int Id { get; set; } }
Name属性属于可选参数,可赋值,也可忽略。
二、反射
System.Type的中type类,可以获取类型的引用,比如Type t=typeof(double),t是对double类型的引用。Type类是一个抽象的基类,当实例化Type的对象时,实际上是实例化它的一个派生类。
GetType()方法也可以获取类型的引用,比如:double d=11;Type t=d.GetType();此方法是Object继承而来, 所有对象都会有。还可以调用Type的静态方GetType()获取:Type t=Type.GetType("System.Double");Type 类型下还有很多属性、方法可以用:
Type t=typeof(double); //数据类型名 var name = t.Name; //数据类型的完全限定名 var fullName = t.FullName; //数据类型的名称空间名 var nameSpace = t.Namespace; //该类型的直接基类 var baseType = t.BaseType;
返回的对象类型 | 方法 |
ConstructorInfo | GetConstructor(),GetConstructors() |
EventInfo | GetEvent(),GetEvents() |
FiledInfo | GetField(),GetFields() |
MemberInfo | GetMember(),GetMembers(),GetDefaultMembers() |
PropertyInfo | GetProperty(),GetProperties() |
Assembly类:
Assembly类在System.Reflection名称空间下定义,它允许访问给定程序集的元数据,和Type类一样 ,它也包含很多加载和执行程序集的属性和方法,方法和属性与Type类似,下面我们来写个反射获取创建对象的实际应用:
新建一个控制台项目(myAssembly),添加其余三个类库,如下:
在IDal接口中声明如下:
public interface IQueryDal { void Query(); }
在ORAService中声明如下:
public class OracleServices : IQueryDal { public void Query() { Console.WriteLine("oracle查询"); } }
在SQLService中声明如下:
public class SqlserServices : IQueryDal { public void Query() { Console.WriteLine("sqlser数据查询"); } }
接着是配置文件:
<appSettings> <add key="Assembly" value="ORAService"/> <add key="FullName" value="ORAService.OracleServices"/> </appSettings>
最后是主程序中:
static void Main(string[] args) { IQueryDal dal = GetDal(); dal.Query(); Console.ReadKey(); } public static IQueryDal GetDal() { var dllName= ConfigurationManager.AppSettings["Assembly"]; var FullName = ConfigurationManager.AppSettings["FullName"]; Assembly assembly = Assembly.Load(dllName); Type t=assembly.GetType(FullName); IQueryDal dal=(IQueryDal)Activator.CreateInstance(t); return dal; }
以上代码需要把ORAService和SQLService的dll 放到主程序中/Bin/Debug中,通过反射创建对象,实现了通过修改配置文件,就可以修改程序运行时要查询的数据库,而不需要修改任何代码;
三、反射调用特性
通过反射获取特性也非常简单,代码虽然简单,但功能缺十分强大, MVC框架就是大量的特性加反射的模式来完成众多的功能,也可以看出特性配合反射,发挥出了更强大的力量。
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] //新建一个特性 public class MyAttrAttribute : Attribute { public string LastName { get; set; } }
//测试类 public class Test { [MyAttr(LastName ="测试特性")] public int Id { get; set; } }
//主程序调用 Test t = new Test(); foreach (var p in t.GetType().GetProperties()) { //判断t实例中是否有某个特性 //2参数:是否也查找继承的特性 if (p.IsDefined(typeof(MyAttrAttribute), false)) { MyAttrAttribute myattr=(MyAttrAttribute)p.GetCustomAttribute(typeof(MyAttrAttribute)); var name= myattr.LastName; //do someing Console.WriteLine(name); }; } Console.ReadKey();
四、总结
反射是可以通过字符串的形式获取类型信息并创建实例, 调用实例的方法,创建泛型类实例,调用泛型类方法等等,特性是可以给类型添加额外的信息, 像mvc类型的验证特性中就是通过特性来标注,后又通过反射来验证实体数据是否合法,合法则通过验证,在做下一步操作。本文还有很多没有说到,只是简单的应用,有不足的地方还请大家指正。