反射就是动态加载某一个程序集,获取程序集中类型然后调用其中的方法或者属性等。我们通常不太注意经常使用反射的地方,如vs编译器,我们在打一个类的时候,里面的方法、属性、字段等信息都会出现智能提示这个时候就是使用反射实现的。还有我们使用的reflactor等反编译器等也是这样的;xml、js、二进制序列化等也是使用反射,否则如何知道需要序列化的字段有哪些呢;访问一个程序集中的私有方法;各种框架中使用反射,即修改配置文件就会得到相应的改变使用的都是反射。反射的另一个作用就是我们可以在做一个可拓展的程序,用插件来实现程序功能拓展,我们可以使用反射、接口,约定一个方法只有实现了特定接口的类,才可以作为插件来使用。如记事本,我们可以给它写插件来更改字体颜色、字体大小、大小写转换等。反射不仅仅可以调用某一个程序集中的public方法或者属性,private也是可以进行调用的,但是这种情况下最好别调用其他程序中的private方法,因为private方法以后有可能会更改,不过某一些特殊情况下为了达到某一些特殊目的,不得不调用的时候也得调用。反射效率并不高,所以一切视具体情况而定。反射中有两个比较重要的东西一个就是Assembly一个是Type。其中Assembly就是动态来加载程序集的,Type是取得其中程序集中的类型。如果一个类型我们已经知道类名,我们可以使用typeof(类名)来获取type,如果只有对象我们可以使用对象.GetType()来获取type。
常用代码如下,eg:
//1>动态加载程序集 Assembly asm= Assembly.LoadFile(@"c:\aa.dll"); //2>获取类型 Type[] types=asm.GetTypes();//获取所有类型名 Type type= asm.GetType("完全限定名称即命名空间+类名");//获取特定类型名 Type[] types= asm.GetExportedTypes();//获取所有Public类型
获取某一个类型方法之后可以使用invoke来进行调用。
下面讲解一个有插件的记事本,代码如下:
主窗体(有一个MenuStrip、一个TextBox)界面参照Windows自带的记事本
private void Form1_Load(object sender, EventArgs e) { string[] directoryInfos = Directory.GetFiles(Path.Combine(Application.StartupPath.ToString(), "Plugins"), "*.dll"); foreach (var directoryInfo in directoryInfos) { Assembly assembly = Assembly.LoadFile(directoryInfo); Type[] types = assembly.GetTypes();//获取所有类型,包括public、internal修饰符的 //Type[] types = assembly.GetExportedTypes();//只能获取public类型的,internal修饰符修饰的类型不可获取。 Type myInterfaceType = typeof(IMyInterface); foreach (var type in types) { if (!type.IsAbstract && myInterfaceType.IsAssignableFrom(type))//判断必须不是抽象类,并且是实现了IMyInterface接口的类。 { ToolStripItem tsi = new ToolStripButton(); IMyInterface obj = (IMyInterface)Activator.CreateInstance(type); tsi = toolStripMenuItem1.DropDownItems.Add(obj.Name); tsi.Tag = obj; tsi.Click += tsi_Click; ; } } } } void tsi_Click(object sender, EventArgs e) { ToolStripItem obj = sender as ToolStripItem; if (obj != null) { IMyInterface myInterface = obj.Tag as IMyInterface; myInterface.Run(textBox1);//约定必须是Run方法 } }
接口
public interface IMyInterface { string Name { get; } void Run(TextBox str); }
插件1:实现了大写转变
public class Class1:IMyInterface { public string Name { get { return "大写转化"; } } public void Run(System.Windows.Forms.TextBox str) { str.Text = str.Text.ToUpper(); } }
插件2:实现了字体的转变和字体大小的转变
public class Class1 : IMyInterface { public string Name { get { return "字体"; } } public void Run(TextBox str) { str.Font=new Font("华文行楷",70); } }
最后实现的效果就是我们最后把两个插件类库生成的dll文件拷贝到主程序的Plugins文件夹下,然后重启主程序就可以实现靠插件实现拓展功能。
利用反射调用类中的私有方法,代码如下,eg:
主方法
1 class Test 2 { 3 private static Type type = typeof(Class1); 4 5 static void Main() 6 { 7 //object obj = Activator.CreateInstance(type);//默认调用的是无参构造函数 8 object obj = Activator.CreateInstance(type,100,160);//调用有两个int类型参数的构造函数 9 //MethodInfo[] methodInfos = type.GetMethods();//只可以获取public方法 10 MethodInfo[] methodInfos = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);//可以获取private方法 11 foreach (var methodInfo in methodInfos) 12 { 13 if (methodInfo.Name == "ReturnValue") 14 { 15 Console.WriteLine(methodInfo.Invoke(obj, null)); 16 } 17 } 18 Console.ReadLine(); 19 } 20 }
包含私有方法的类
public class Class1 { private readonly int _i; private readonly int _j; public Class1(int i, int j) { this._i = i; this._j = j; } public void GetValue() { Console.WriteLine("getValue method"); } private string ReturnValue() { return (_i + _j).ToString(); } }
结果为:260
总结:我们在调用构造函数的时候可以使用Activator来创建对象,同时还可以使用GetConstructor来创建对象。
上面的 object obj = Activator.CreateInstance(type,100,160); 可以换成如下代码,eg:
//ConstructorInfo [] constructorInfo = type.GetConstructors();//可以获取所有的构造函数 ConstructorInfo constructorInfo = type.GetConstructor(new Type[]{typeof(int),typeof(int)});//可以获取特定某一个的构造函数,其中的new Type[]数组是一个参数列表中的参数类型,要跟参数列表中的参数数量及类型相对应。 object obj = constructorInfo.Invoke(new object[] { 100, 160 });
需要区分的有一个地方就是获取所有类行还有获取所有方法,获取所有类型,代码如下,eg:
Type[] types = assembly.GetTypes();//获取所有类型,public和internal都可以获取。
//Type[] types = assembly.GetExportedTypes();//获取所有可以导出的类型即public类型的,不包括internal类型的
下面说一下获取方法,代码如下:
MethodInfo[] methodInfos = type.GetMethods();//只可以获取public方法 MethodInfo[] methodInfos = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);//可以获取private方法
当我们需要判断当前类型是否是抽象的,我们可以使用type类型的IsAbstract,判断当前类型是否是抽象类型。(编译过后接口其实也是一个抽象方法)
当我们需要判断一个类型是否是一个类的子类的时候,我们可以使用type类型的IsSubClassOf,这个方法只是用来判断类的不能用来判断接口。
当我们需要判断一个对象是不是一个类型的对象的时候,我们可以使用IsTypeOfInstanceOfType(类、接口都适用)
当我们需要判断一个类型是否可以接受另一个类型的值的时候,我们需要使用IsAssignFrom