c#中的反射
c#中的反射
前言:我们反射主要用到的类System.Type 类和 System.Reflection.Assembly类,通过System.Type这个类可以访问关于 任何数据类型的信息。而System.Reflection.Assembly类,它可以用于访问给定程序集的相关的信息。
我们先来介绍生活中的两个例子。
1:B超:大家体检的时候大概都做过B超吧,B超可以透过肚皮探测到你的内脏的生理情况。这是如何做到的呢?B超是B型超声波,它可以透过肚皮通过向你体内发射B型超声波,当超声波遇到内脏壁的时候就会产生一定的“回音”反射,然后把“回音”进行处理就可以显示出内脏的情况了。
2.地球内部结构:地球的内部结构大体可以分为三层:地壳、地幔和地核。地壳是固体,地核是液体,地幔则是半液半固的结构(中学地理的内容,大家还记得吧?)。如何在地球表面不用深入地球内部就知道其内部的构造呢?对,向地球发射“地震波”,“地震波”分两种一种是“横波”,另一种是“纵波”。“横波”只能穿透固体,而“纵波”既可穿透固体又可以穿透液体。通过在地面对纵波和横波的反回情况,我们就可以大体断定地球内部的构造了。
大家可以注意到两个例子共同的特点:就是从一个对象的外部去了解对象内部的东西。而且都是利用了波的反射功能。在.Net中的反射也可以实现从对象的外部来了解对象(或者程序集)内部结构的功能。哪怕你不知道这个对象(或程序集)是个什么东西,另外.NET中的反射还可以运态创建出对象并执行它其中的方法。
一:反射的概念与Type类
通俗的说:反射通过程序,了解以及调用“程序集”中的程序的相关的属性、方法和字段。
所以反射的作用我们总结:
1.通过反射我们可以读取程序集(.dll .exe)中的代码的内容。
2.可以根据(字符串)类名动态的创建类的对象。
3.可以动态获取对象中的信息(方法/属性/字段)。
4.可以根据(字符串)方法名称来调用执行。
我们首先先来编写一个Persons类。我们通过反射来获取类中的方法、属性和字段。
/// <summary> /// 人类 /// </summary> public class Persons { private int _id; //id号 public string Name; //名字 public int Age; //年龄 /// <summary> /// id号的属性 /// </summary> public int Id { get { return _id; } set { _id = value; } } /// <summary> /// 显示id号 /// </summary> public void DisPlayId() { Console.WriteLine(this.Id); } /// <summary> /// 显示年龄 /// </summary> public void DisplayAge() { Console.WriteLine(this.Age); } /// <summary> /// 显示名称 /// </summary> public void DisplayName() { Console.WriteLine(this.Name); } }
这时候我们就可以实例化对象。通过Type类获取类的名称、所属的命名空间、是否为公共类以及类的字段、属性和方法等信息。
/// <summary> /// 查询Type类的一些普通的信息 /// </summary> public static void Test1() { //实例化人类创建对象 Persons person = new Persons(); //Type类 可以获取名称 、所属的命名空间 、是否为公共类以及类的字段、属性、方法等信息. Type typePersonObj = person.GetType(); //显示类的名称 Console.WriteLine(typePersonObj.Name); //显示类所属的命名空间 Console.WriteLine(typePersonObj.Namespace); //显示所属的程序集 Console.WriteLine(typePersonObj.Assembly); //获取类的是否为公共 密封 私有 Console.WriteLine(typePersonObj.IsPublic);//T Console.WriteLine(typePersonObj.IsSealed);//F Console.WriteLine(typePersonObj.IsPrimitive);//F }
但这是得到类的名称、所属的命名空间、获得所属的程序集,但是我们更多的是想要得到类中的字段(默认私有的是不可以得到的)、属性和方法。我们可以使用GetFields()、GetMethods()、GetProperties()进行获取。代码如下:
/// <summary> /// 得到字段的信息 方法的信息 属性的信息 /// </summary> public static void Test2() { Persons person = new Persons(); Type typePersonObj = person.GetType(); //得到字段的信息 FieldInfo[] fieldInfos = typePersonObj.GetFields(); foreach (var item in fieldInfos) { Console.WriteLine(item.Name); } Console.WriteLine("-------------------------------------------"); //得到方法的信息 MethodInfo[] methodInfos = typePersonObj.GetMethods(); foreach (var methods in methodInfos) { Console.WriteLine(methods.Name); } Console.WriteLine("-------------------------------------------"); //得到属性的信息 PropertyInfo[] propertyInfos = typePersonObj.GetProperties(); foreach (var proInfos in propertyInfos) { Console.WriteLine(proInfos.Name); } Console.WriteLine("-------------------------------------------"); }
二:程序集Assembly与动态的调用
那么我们怎么样得到程序集呢?我们需要引用命名空间:using System.Reflection; 即可以通过程序集中的一个类的实例化,从而得到这个程序集当中的对象。当我们得到程序集之后可以通过调用FullName()方法得到程序集显示的名称。通过返回GetTypes()方法得到程序集当中的所有的类。通过foreach循环遍历得到所有类的名称。代码如下:
/// <summary> /// 通过程序集中的一个类的实例化,从而得到这个程序集当中的对象Assembly /// </summary> public static void Test1() { Person person = new Person(); //得到程序集 Assembly assembly = person.GetType().Assembly; //得到程序集的显示名称 Console.WriteLine(assembly.FullName); //通过程序集 得到程序集中的所有的类 Type[] types = assembly.GetTypes(); foreach (var type in types) { Console.WriteLine(type.Name); } }
当然我们也可以通过路径的方式得到程序集,从而得到程序集当中的所有的类。进而foreach遍历得到程序集当中的所有的类的名称。首先我们的程序集可分为两种(.dll和.exe文件)。
.exe文件点击就可以直接运行的文件:
而我们的.dll文件是我们.NET内部的文件(不可以点击运行的),但是我们可以通过反射的方式得到程序集中的内容:
这时候我们可以通过Assembly.LoadFrom()方法得到程序集的对象,从而得到程序集中的所有的方法:
/// <summary> /// 直接得到程序集 使用路径的方式 /// </summary> public static void Test2() { //通过路径的方式直接得到程序集的对象 Assembly assembly = Assembly.LoadFrom(@"E:\System.dll"); //通过程序集 得到程序集中的所有的类 Type[] types = assembly.GetTypes(); foreach (var type in types) { Console.WriteLine(type.Name); } Console.WriteLine("直接访问程序集的测试"); }
接下来就是我们非常重要的动态的调用。我们会在下面的代码中依次的使用反射的方式动态的调用一个类中的无参数的方法、有参数的方法、方法的重载、私有方法的调用和属性的调用。首先我们先声明一个Person()类,来声明字段、属性和一系列的方法。
/// <summary> /// 人类 /// </summary> public class Person { public int Id; public string Name; private int _age; public int Age { get; set; } public void DisplayId() { } public void DisplayName() { } public void DisplayAge() { } /// <summary> /// 调用的方法的测试 /// </summary> public void Test1() { Console.WriteLine("无参数的方法"); } public void Test2(string name) { Console.WriteLine("有参数的方法,参数:" + name); } public void Test3(int num) { Console.WriteLine("有参数的方法,参数:" + num); } public void Test3() { Console.WriteLine("无参数的方法"); } /// <summary> /// 私有的方法 /// </summary> private void DispalyPrivateMathod() { Console.WriteLine("我是私有的方法,一般的情况下不会被调用"); } }
首先我们先调用无参数的方法,首先我们声明类的名称和我们要调用的方法的名称。然后通过路径的方式Assembly.LoadFrom()方法得到程序集对象。然后获取程序集实例中的具有指定名称的Type对象。之后通过Activator.CreateInstance()方法来创建实例(对象)。得到方法以及调用无参数的方法。
/// <summary> /// 通过反射的技术调用方法与属性 /// </summary> public void Test1() { //调用的类的名称 string strClassName = "Person"; //调用的方法的名称 string strMathodName = "Test1"; //得到程序集对象 Assembly assembly = Assembly.LoadFrom(@"E:\03使用反射的方式进行动态的调用.exe"); //得到Type(得到程序集当中的类)得到包含命名空间的类名 Type type = assembly.GetType("_03使用反射的方式进行动态的调用." + strClassName); Console.WriteLine(type); //创建实例(对象) object obj = Activator.CreateInstance(type); //得到方法 MethodInfo methodInfos = type.GetMethod(strMathodName); //调用无参的方法 methodInfos.Invoke(obj, null);//null表示方法没有参数. }
接下来我们调用有一个参数的方法,我们其余的方法不变,在进行方法的调用的时候,我们应该传递参数,在Invoke方法中,第二个参数接收一个Object[]类型的数组。我们可以声明一个object[]类型的数组,在初始化容器中进行参数的填写。在方法中进行调用就可以了,代码如下:
/// <summary> /// 演示 调用带参数的方法 与方法的重载的调用. /// </summary> public void Test2() { //调用的类的名称 string strClassName = "Person"; //调用的方法的名称 string strMathodName = "Test2"; //得到程序集对象 Assembly assembly = Assembly.LoadFrom(@"E:\03使用反射的方式进行动态的调用.exe"); Console.WriteLine(assembly); //得到Type(得到程序集当中的类) Type type = assembly.GetType("_03使用反射的方式进行动态的调用." + strClassName); Console.WriteLine(type); //创建实例(对象) object obj = Activator.CreateInstance(type); //得到方法 MethodInfo methodInfos = type.GetMethod(strMathodName); Console.WriteLine(methodInfos); //调用无参的方法 object[] objects = new object[] { "张三" }; //objects[0] = "张三"; methodInfos.Invoke(obj, objects); }
就下来我们演示重载的方法的调用(有参数的调用),我们需要指定参数的类型。用Type类型的数组表示(如果有多个参数就在typeof(xxx)进行指定就可以了)。调用type.GetMethod把类型当作参数传递到方法中。详细代码如下:
/// <summary> /// 演示方法的重载的调用(有参数的调用) /// </summary> public void Test3() { //调用的类的名称 string strClassName = "Person"; //调用的方法的名称 string strMathodName = "Test3"; //得到程序集对象 Assembly assembly = Assembly.LoadFrom(@"E:\03使用反射的方式进行动态的调用.exe"); //得到Type(得到程序集当中的类) Type type = assembly.GetType("_03使用反射的方式进行动态的调用." + strClassName); //创建实例(对象) object obj = Activator.CreateInstance(type); //得到方法 Type[0]:表示重载的方法(无参数) Type[] types = { typeof(int) };//确定方法的重载中有参数. MethodInfo methodInfos = type.GetMethod(strMathodName, types);//types表示有参数 //调用无参数的方法 //object[] objects = new object[1] { 100 }; object[] objects = { 100 }; //objects[0] = 100; methodInfos.Invoke(obj, objects); }
演示重载方法无参数的调用,直接在type.GetMethod()方法中第二个参数为new Type[0]:
/// <summary> /// 演示重载方法无参数的调用 /// </summary> public void Test4() { //调用的类的名称 string strClassName = "Person"; //调用的方法的名称 string strMathodName = "Test3"; //得到程序集对象 Assembly assembly = Assembly.LoadFrom(@"E:\03使用反射的方式进行动态的调用.exe"); //得到Type(得到程序集当中的类) Type type = assembly.GetType("_03使用反射的方式进行动态的调用." + strClassName); //创建实例(对象) object obj = Activator.CreateInstance(type); //得到方法 MethodInfo methodInfos = type.GetMethod(strMathodName, new Type[0]); //调用无参的方法 methodInfos.Invoke(obj, null); }
接下来我们考虑一下我们可不可以调用类中的私有的方法。大家根据类的封装性和访问修饰符可以知道,我们只可以在当前的类中调用类中的私有方法,不能再类的外部调用类中的私有方法,但是我们的反射可以做到调用类中的私有方法(不推荐使用,在特殊的时候使用)。我们只需要使用:BindingFlags.NonPublic | BindingFlags.Instance,表示非公共的方法和实例方法在我们的搜索范围之内。代码如下:
/// <summary> /// 演示类中的私有的方法的调用 /// </summary> public void Test5() { //调用的类的名称 string strClassName = "Person"; //调用的方法的名称 string strMathodName = "DispalyPrivateMathod"; //得到程序集对象 Assembly assembly = Assembly.LoadFrom(@"E:\03使用反射的方式进行动态的调用.exe"); //得到Type(得到程序集当中的类) Type type = assembly.GetType("_03使用反射的方式进行动态的调用." + strClassName); //创建实例(对象) object obj = Activator.CreateInstance(type); //得到方法 MethodInfo methodInfos = type.GetMethod(strMathodName, BindingFlags.NonPublic | BindingFlags.Instance); //调用无参的方法 methodInfos.Invoke(obj, null);//null表示方法没有参数. }
接下来我们进行属性的调用,我们通过type.GetProperty()方法搜索具有指定名称的公共属性。当我们拿到属性之后使用propertyInfo.SetValue()方法进行设置指定对象的属性值。我们也可以通过propertyInfo.GetValue()得到指定对象的属性值。代码如下:
/// <summary> /// 演示属性的调用 /// </summary> public void Test6() { //调用的类的名称 string strClassName = "Person"; //调用的属性的名称 string strPropName = "Age"; //得到程序集对象 Assembly assembly = Assembly.LoadFrom(@"E:\03使用反射的方式进行动态的调用.exe"); //得到Type(得到程序集当中的类) Type type = assembly.GetType("_03使用反射的方式进行动态的调用." + strClassName); Console.WriteLine(type);//_03使用反射的方式进行动态的调用.Person //创建实例(对象) object obj = Activator.CreateInstance(type); Console.WriteLine(obj);//_03使用反射的方式进行动态的调用.Person //得到属性 PropertyInfo propertyInfo = type.GetProperty(strPropName); Console.WriteLine(propertyInfo);//Int32 Age //设置属性 propertyInfo.SetValue(obj, 100); //属性的执行 string result = propertyInfo.GetValue(obj, null).ToString(); Console.WriteLine(result); }
三:Type类的深入的方法
接下来我们学习Type类的一些深入的方法。在我们实际的运用中,有时候需要判断一个类或者接口是否为(参数)类的父类或者是接口,参数类是否为当前类(接口)的实例或者实现本接口,判断一个类是否是参数类的子类。首先我们先建立一个父类(人类):
/// <summary> /// 人类 作为父类来使用 /// </summary> public class Men { public void Test() { } }
我们建立一个接口类,表示我可以讲话(接口总是以I开头,表示我可以做什么)。
/// <summary> /// 接口的定义 /// </summary> interface ICanSpeak { }
我们新建一个子类(张三)作为人类(父类)的子类并且继承接口(我可以讲话),代码如下:
/// <summary> /// 子类的定义,继承父类和接口 /// </summary> public class ZhangSan : Men, ICanSpeak { }
在建立一个普通的类,没有父类和不继承任何的接口:
/// <summary> /// 普通的类(狗) /// </summary> public class Dog { }
在这里我们先讲一个typeof()关键字,这个方法返回从System.Type派生的类的一个实例。这个对象可以提供对象成员所属类的更多的信息。包括基本类型、方法、属性等。System.Type提供了反射技术的入口点。首先我们得到类的对象:
Type typeMen = typeof(Men); Console.WriteLine(typeMen);//_04Type类的深入的方法.Men Type typeZhangSan = typeof(ZhangSan); Type typeDog = typeof(Dog); Type typeICanSpeak = typeof(ICanSpeak);
我们先来判断一个类(或者接口)是否为一个类的父类或者接口,返回的类型是一个布尔类型,使用方法:bool Type.IsAssignableFrom(Type c)方法(官方解释:确定当前的Type的实例是否可以从指定Type的实例分配):
bool Type.IsAssignableFrom(Type c)
/// <summary> /// 判断一个类(或者接口) 是否为(参数)类的父类 或者是接口 /// </summary> public void Test1() { Type typeMen = typeof(Men); Console.WriteLine(typeMen);//_04Type类的深入的方法.Men Type typeZhangSan = typeof(ZhangSan); Type typeDog = typeof(Dog); Type typeICanSpeak = typeof(ICanSpeak); //判断人类是张三的父类吗? //当前的Type实例:张三实例 //指定的Type实例:人类(父类) Console.WriteLine(typeMen.IsAssignableFrom(typeZhangSan));//T //判断人类是狗的父类吗? Console.WriteLine(typeMen.IsAssignableFrom(typeDog));//F //接口的判断 张三继承了接口了吗? Console.WriteLine(typeICanSpeak.IsAssignableFrom(typeZhangSan));//T Console.WriteLine(typeICanSpeak.IsAssignableFrom(typeDog));//F }
我们接下来看第二个方法为:bool Type.IsInstanceOfType(object o):确定指定的对象是否是当前Type的实例。
bool Type.IsInstanceOfType(object o):
/// <summary> /// (参数)类是否为当前类(接口)的实例 或者实现本接口 /// </summary> public void Test2() { Type typeMen = typeof(Men); Type typeICanSpeak = typeof(ICanSpeak); //类的实例 ZhangSan zhangSan = new ZhangSan(); Men men = new ZhangSan(); Dog dog = new Dog(); //men类型是否是参数类的实例 Console.WriteLine(typeMen.IsInstanceOfType(men));//T Console.WriteLine(typeMen.IsInstanceOfType(zhangSan));//T Console.WriteLine(typeMen.IsInstanceOfType(dog));//F Console.WriteLine(typeICanSpeak.IsInstanceOfType(zhangSan));//T Console.WriteLine(typeICanSpeak.IsInstanceOfType(dog));//F }
判断一个类是否为参数类的子类使用(确定当前Type表示的类是否是从指定的Type表示的类派生的):
bool Type.IsSubclassOf(Type c)
注意:这个类不适用于接口
/// <summary> /// 判断一个类是否是参数类的子类 /// </summary> public void Test3() { Type typeZhangSan = typeof(ZhangSan); Type typeMen = typeof(Men); Type typeDog = typeof(Dog); //是否是参数类的子类 Console.WriteLine(typeZhangSan.IsSubclassOf(typeMen));//T Console.WriteLine(typeDog.IsSubclassOf(typeMen));//F }
四:预定义特性Attribute
特性(Attribute):是一种允许我们向程序的程序集增加元数据的语言结构。提供了一种将声明性信息与代码关联起来的途径。
Obsolete特性:将过时的方法与类标注为”过时“(过期)的,其在编译时,显示警告的信息。首先我们先来编写一个旧的方法并进行标记:
[Obsolete("该方法已经过时,请用对用的'newMathod'进行替换")] public void OldMethod() { Console.WriteLine("这是一个旧的方法"); }
这样的话当我们在别的方法中进行调用的时候,就会显示蓝色的警告线,虽然我们已经标记了过时,但是在我们的程序中依然可以使用,如图所示:
如果在我们的类中有过时的方法,我们不想让过时的方法使用,必须使用我们的新的方法,我们可以在Obsolete的第二个参数使用true关键字表示:只是是否将使用已过时的元素标记为错误的布尔值,换句话来说,我们的旧的方法的下面就报红色的错误线。编译不能通过。如图所示:
完整的代码如下:
class Program { static void Main(string[] args) { Program program = new Program(); program.Test(); Console.ReadKey(); } //[Obsolete("该方法已经过时,必须用对应的'newMathod'进行替换", true)] [Obsolete("该方法已经过时,请用对用的'newMathod'进行替换")] public void OldMethod() { Console.WriteLine("这是一个旧的方法"); } /// <summary> /// 新的方法 /// </summary> public void NewMethod() { Console.WriteLine("这个一个新的方法"); } /// <summary> /// 测试的方法 /// </summary> public void Test() { //当我们使用Obsolete方法时,为我们提示该方法已经过时,请使用新的方法替代旧的方法(此时该旧的方法报绿线,但是此方法还是可以用的) //如果我们必须让他使用新的方法,而旧的方法报红线,后加true(引发错误,必须使用新的方法,不写的话默认是flse为警告) OldMethod(); NewMethod(); } }
学习Conditional特性。一般用于程序开发过程中的“测试方法”的调用。测试阶段定义“测试的宏”,发布商业版本的时候,则取消宏即可。使用我们的预编译指令。我们首先写一个测试的方法。当我们发布的时候测试的方法不执行的。这时候我们可以使用Conditional特性,代码如下:
//特性:有条件执行,默认不执行 [Conditional("OnlyTest")] public void TestMethod() { Console.WriteLine("这是测试的方法"); }
默认是不执行的。当我们定义一个宏的时候, 允许测试方法执行:
#define OnlyTest //定义一个宏,允许测试方法执行
而我们更多的使用预编译指令来进行使用,代码如下:
/// <summary> /// 学习和演示条件编译 /// </summary> public void Test_2() { Console.WriteLine("aaaaa"); Console.WriteLine("bbbbb"); #if DEBUG //DEBUG表示在调试的阶段上执行 Console.WriteLine("ccccc"); #else //当我们取消DEBUG的时候,我们的方法就可以执行了 Console.WriteLine("ddddd"); #endif }
我们可以定义一个宏来表示我们在调试的阶段,也可以取消宏来执行另一段代码:
#define DEBUG //定义一个宏,表示调试的阶段 //#undef DEBUG //取消一个宏
完整的代码如下:
#define OnlyTest //定义一个宏,允许测试方法执行 #define DEBUG //定义一个宏,表示调试的阶段 //#undef DEBUG //取消一个宏 using System; using System.Collections.Generic; using System.Diagnostics; //特性的命名空间 using System.Linq; using System.Text; using System.Threading.Tasks; namespace _06特性的学习 { public class ConditionalClass { //特性:有条件执行,默认不执行 [Conditional("OnlyTest")] public void TestMethod() { Console.WriteLine("这是测试的方法"); } public void Method_1() { Console.WriteLine("这是一个方法1"); } public void Method_2() { Console.WriteLine("这是一个方法2"); } public void Test() { TestMethod(); Method_1(); TestMethod(); Method_2(); TestMethod(); Method_1(); Method_1(); } /// <summary> /// 学习和演示条件编译 /// </summary> public void Test_2() { Console.WriteLine("aaaaa"); Console.WriteLine("bbbbb"); #if DEBUG //DEBUG表示在调试的阶段上执行 Console.WriteLine("ccccc"); Method_1(); #else //当我们取消DEBUG的时候,我们的方法就可以执行了 Console.WriteLine("ddddd"); Method_2(); #endif } } }
好了,本篇的博客在这里就全部讲解完了,大家有什么问题的话可以积极的留言。谢谢。