反射是.NET中的重要机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类、结构、委托、接口和枚举等)的成员和成员的信息。有了反射,即可对每一个类型了如指掌。另外我还可以直接创建对象,即使这个对象的类型在编译时还不知道。
.Net中获取运行时类型信息的方式,.Net的应用程序由几个部分:‘程序集(Assembly)’、‘模块(Module)’、‘类型(class)’组成,而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息
Assembly类可以获得正在运行的装配件信息,也可以动态的加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。
Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等等,通过Type类可以得到这些要素的信息,并且调用之。
MethodInfo包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。
命名空间只是说明一个类型是那个族的,比如有人是汉族、有人是回族;
而装配件表明一个类型住在哪里,比如有人住在北京、有人住在上海;
反射的用途:
(1)使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
(2)使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
(3)使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。
(4)使用MethodInfo了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。
(5)使用FiedInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。
(6)使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。
(7)使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
(8)使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。
反射用到的命名空间:
System.Reflection
System.Type
System.Reflection.Assembly
反射用到的主要类:
System.Type 类--通过这个类可以访问任何给定数据类型的信息。
System.Reflection.Assembly类--它可以用于访问给定程序集的信息,或者把这个程序集加载到程序中。
System.Type类:
System.Type 类对于反射起着核心的作用。但它是一个抽象的基类,Type有与每种数据类型对应的派生类,我们使用这个派生类的对象的方法、字段、属性来查找有关该类型的所有信息。
获取给定类型的Type引用有3种常用方式:
●使用 C# typeof 运算符。
Type t = typeof(string);
●使用对象GetType()方法。
string s = "grayworm";
Type t = s.GetType();
●还可以调用Type类的静态方法GetType()。
Type t = Type.GetType("System.String");
上面这三类代码都是获取string类型的Type,在取出string类型的Type引用t后,我们就可以通过t来探测string类型的结构了。
string n = "libbybyron"; Type t = n.GetType(); foreach (MemberInfo mi in t.GetMembers()) { Console.WriteLine("{0}/t{1}",mi.MemberType,mi.Name); }
Type类的属性:
Name 数据类型名
FullName 数据类型的完全限定名(包括命名空间名)
Namespace 定义数据类型的命名空间名
IsAbstract 指示该类型是否是抽象类型
IsArray 指示该类型是否是数组
IsClass 指示该类型是否是类
IsEnum 指示该类型是否是枚举
IsInterface 指示该类型是否是接口
IsPublic 指示该类型是否是公有的
IsSealed 指示该类型是否是密封类
IsValueType 指示该类型是否是值类型
Type类的方法:
GetConstructor(), GetConstructors():返回ConstructorInfo类型,用于取得该类的构造函数的信息
GetEvent(), GetEvents():返回EventInfo类型,用于取得该类的事件的信息
GetField(), GetFields():返回FieldInfo类型,用于取得该类的字段(成员变量)的信息
GetInterface(), GetInterfaces():返回InterfaceInfo类型,用于取得该类实现的接口的信息
GetMember(), GetMembers():返回MemberInfo类型,用于取得该类的所有成员的信息
GetMethod(), GetMethods():返回MethodInfo类型,用于取得该类的方法的信息
GetProperty(), GetProperties():返回PropertyInfo类型,用于取得该类的属性的信息
可以调用这些成员,其方式是调用Type的InvokeMember()方法,或者调用MethodInfo, PropertyInfo和其他类的Invoke()方法。
查看类中的构造方法:
NewClassw nc = new NewClassw(); Type t = nc.GetType(); ConstructorInfo[] ci = t.GetConstructors(); //获取类的所有构造函数 foreach (ConstructorInfo c in ci) //遍历每一个构造函数 { ParameterInfo[] ps = c.GetParameters(); //取出每个构造函数的所有参数 foreach (ParameterInfo pi in ps) //遍历并打印所该构造函数的所有参数 { Console.Write(pi.ParameterType.ToString()+" "+pi.Name+","); } Console.WriteLine(); }
获取类型信息
namespace ConsoleApplication2 { class Program { static void Main(string[] args) { MyClass m = new MyClass(); Type type = m.GetType(); Console.WriteLine("类型名:" + type.Name); Console.WriteLine("类全名:"+type.FullName); Console.WriteLine("命名空间名:"+type.Namespace); Console.WriteLine("程序集名:"+type.Assembly); Console.WriteLine("模块名:"+type.Module); Console.WriteLine("基类名:"+type.BaseType); Console.WriteLine("是否类:"+type.IsClass); Console.WriteLine("类的公共成员:"); MemberInfo[] memberInfos = type.GetMembers();//得到所有公共成员 foreach (var item in memberInfos) { Console.WriteLine("{0}:{1}",item.MemberType,item); } } } class MyClass { public string m; public void test() { } public int MyProperty { get; set; } } }
获取程序集元数据
Assembly类定义了一个程序集,它是一个可重用、无版本冲突并且可自我描述的公共语言 运行库应用程序构造块。因为程序集中是使用元数据进行自我描述的,所以我们就能通过其元数据得到程序集内部的构成。结合Assembly和反射能够获取程 序集的元数据,但是首先要将程序集装入内存中。可以使用Assembly类的多种静态Load方法加载程序集。
public static void Main() { //获取当前执行代码的程序集 Assembly assem = Assembly.GetExecutingAssembly(); Console.WriteLine("程序集全名:"+assem.FullName); Console.WriteLine("程序集的版本:"+assem.GetName().Version); Console.WriteLine("程序集初始位置:"+assem.CodeBase); Console.WriteLine("程序集位置:"+assem.Location); Console.WriteLine("程序集入口:"+assem.EntryPoint); Type[] types = assem.GetTypes(); Console.WriteLine("程序集下包含的类型:"); foreach (var item in types) { Console.WriteLine("类:"+item.Name); } }
用构造函数动态生成对象:
Type t = typeof(NewClassw); Type[] pt = new Type[2]; pt[0] = typeof(string); pt[1] = typeof(string); //根据参数类型获取构造函数 ConstructorInfo ci = t.GetConstructor(pt); //构造Object数组,作为构造函数的输入参数 object[] obj = new object[2]{"libbybyron","hi.baidu.com"}; //调用构造函数生成对象 object o = ci.Invoke(obj); //调用生成的对象的方法测试是否对象生成成功 //((NewClassw)o).show();
用Activator生成对象:
Type t = typeof(NewClassw); //构造函数的参数 object[] obj = new object[2] { "libbybyron", "hi.baidu.com" }; //用Activator的CreateInstance静态方法,生成新对象 object o = Activator.CreateInstance(t,"libbybyron","hi.baidu.com"); //((NewClassw)o).show();
查看类中的属性:
NewClassw nc = new NewClassw(); Type t = nc.GetType(); PropertyInfo[] pis = t.GetProperties(); foreach(PropertyInfo pi in pis) { Console.WriteLine(pi.Name); }
查看类中的public方法:
NewClassw nc = new NewClassw(); Type t = nc.GetType(); MethodInfo[] mis = t.GetMethods(); foreach (MethodInfo mi in mis) { Console.WriteLine(mi.ReturnType+" "+mi.Name); }
查看类中的public字段
NewClassw nc = new NewClassw(); Type t = nc.GetType(); FieldInfo[] fis = t.GetFields(); foreach (FieldInfo fi in fis) { Console.WriteLine(fi.Name); }
用反射生成对象,并调用属性、方法和字段进行操作
NewClassw nc = new NewClassw(); Type t = nc.GetType(); object obj = Activator.CreateInstance(t); //取得ID字段 FieldInfo fi = t.GetField("ID"); //给ID字段赋值 fi.SetValue(obj, "k001"); //取得MyName属性 PropertyInfo pi1 = t.GetProperty("MyName"); //给MyName属性赋值 pi1.SetValue(obj, "libbybyron", null); PropertyInfo pi2 = t.GetProperty("MyInfo"); pi2.SetValue(obj, "hi.baidu.com", null); //取得show方法 MethodInfo mi = t.GetMethod("show"); //调用show方法 mi.Invoke(obj, null);
System.Reflection.Assembly类
Assembly类可以获得程序集的信息,也可以动态的加载程序集,以及在程序集中查找类型信息,并创建该类型的实例。
使用Assembly类可以降低程序集之间的耦合,有利于软件结构的合理化。
通过程序集名称返回Assembly对象
Assembly ass = Assembly.Load("ClassLibrary8");
通过DLL文件名称返回Assembly对象
Assembly ass = Assembly.LoadFrom("ClassLibrary8.dll");
通过Assembly获取程序集中类
Type t = ass.GetType("ClassLibrary8.NewClass"); //参数必须是类的全名
通过Assembly获取程序集中所有的类
Type[] t = ass.GetTypes();
//通过程序集的名称反射 Assembly ass = Assembly.Load("ClassLibrary8"); Type t = ass.GetType("ClassLibrary831.NewClass"); object o = Activator.CreateInstance(t, "libbybyron", "http://hi.baidu.com/"); MethodInfo mi = t.GetMethod("show"); mi.Invoke(o, null); //通过DLL文件全名反射其中的所有类型 Assembly assembly = Assembly.LoadFrom("xxx.dll的路径"); Type[] aa = a.GetTypes(); foreach(Type t in aa) { if(t.FullName == "a.b.c") { object o = Activator.CreateInstance(t); } }
晚绑定能够带来很多设计上的便利,合适的使用能够大大提高程序的复用性和灵活性,但是任何东西都有两面性,使用的时侯,需要再三衡量
早绑定是在编译时绑定对象类型,而晚绑定是在运行时才绑定对象的类型。利用反射可以实现晚绑定,即动态加载类型,并调用他们的方法,下边是MSDN中的一个例子,详细的解释信息见注释
动态加载类型
namespace ConsoleApplication2 { public class Example { private int factor; public Example(int f) { factor = f; } public int SampleMethod(int x) { Console.WriteLine("\nExample.SampleMethod({0}) executes.", x); return x * factor; } public static void Main() { //获取当前执行代码的程序集 Assembly assem = Assembly.GetExecutingAssembly(); Console.WriteLine("Assembly Full Name:"); Console.WriteLine(assem.FullName); // The AssemblyName type can be used to parse the full name. AssemblyName assemName = assem.GetName(); Console.WriteLine("\nName: {0}", assemName.Name); Console.WriteLine("Version: {0}.{1}", assemName.Version.Major, assemName.Version.Minor); Console.WriteLine("\nAssembly CodeBase:"); Console.WriteLine(assem.CodeBase); // 从程序集众创建一个Example实例并且用object类型的引用o指向它,同时调用一个输入参数的构造函数 Object o = assem.CreateInstance("ConsoleApplication2.Example", false, BindingFlags.ExactBinding, null, new Object[] { 2 }, null, null); //构造Example类的一个晚绑定的方法SampleMethod MethodInfo m = assem.GetType("ConsoleApplication2.Example").GetMethod("SampleMethod"); //调用刚才实例化好的Example对象o中的SampleMethod方法,传入的参数为42 Object ret = m.Invoke(o, new Object[] { 42 }); Console.WriteLine("SampleMethod returned {0}.", ret); Console.WriteLine("\nAssembly entry point:"); Console.WriteLine(assem.EntryPoint); } }
反射特性:
[Table(Name="dbo.[User]")] public partial class User {
当C#编译器发现这个属性有一个特性Table时,首先会把字符串Attribute添加到这个名称的后面,形成一个组合名称 TableAttribute,然后在其搜索路径的所有命名空间中搜索有相同类名的类。但要注意,如果该特性名结尾是Attribute,编译器就不会把 该字符串加到组合名称中。所有的特性都是从System.Attribute类型上面派生的。
接着我们来看一下Table特性的定制格式
[AttributeUsageAttribute(AttributeTargets.Class,Inherited=true,AllowMultiple=false)] public class TalbeAttribute:Attribute {
在定义类型时使用System.AttributeUsage特性来表明这个自定义特性的使用范围,这里使用了Class样式,表示 TableAttribute特性只能用在其它的Class类型前面,若放置在Interface或Struct类型前面,或者放在对象成员的前面则会出 现编译错误。这里还是用语句 AllowMultiple=false 语句来表明对于一个类型,该特性只能用一次,若一个Class类型前面出现多个TableAttribute,则会出现编译错误。若设置 AllowMultiple=true,则该特性可以多次定义,也就是一个Class类型前面可以出现多个相同类型的特性。不过这里我们假设一个对象只能 映射到一个数据表上,没有多重映射,因此就指明对同一个类型该特性不能多次使用。Inherited参数设定为true,就表示应用到类或接口上的特性也 可以自动应用到所派生的类或接口上。
我们再看一下定制TalbeAttribute特性的完整例子:
[AttributeUsageAttribute(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public class TableAttribute : Attribute { //保存表名的字段 private string _tableName; public TableAttribute() { } public TableAttribute(string tableName) { this._tableName = tableName; } /// /// 映射的表名(表的全名:模式名.表名) /// public string TableName { set { this._tableName = value; } get { return this._tableName; } } }
特性也是一个Class类型,可以有多个构造函数,就像C#的new语句一样,我们向类型附加特性时可以使用不同的初始化参数来指明使用特性的那个构造函 数。我们附加特性时还可以使用“属性名=属性值”的方法来直接指明特性的属性值。该特性中定义了一个TableName属性,该属性就是被修饰的对象所映 射的数据库表的名称。
下面我们举一个使用特性来进行O/RMapping的例子,也就是将对象转化成Sql语句
用户类:
User类
[Table("User")] public class User { [Colum("userID", DbType = DbType.Int32)] public int UserID { get; set; } [Colum("UserName", DbType = DbType.String)] public string UserName { get; set; } }
表特性
[AttributeUsageAttribute(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public class TableAttribute : Attribute { //保存表名的字段 private string _tableName; public TableAttribute() { } public TableAttribute(string tableName) { this._tableName = tableName; } /// /// 映射的表名(表的全名:模式名.表名) /// public string TableName { set { this._tableName = value; } get { return this._tableName; } } }
列特性:
[AttributeUsageAttribute(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] public class ColumAttribute : Attribute { private string _columName; private DbType _dbType ; public ColumAttribute() { } public ColumAttribute(string columName) : this() { this._columName = columName; } public ColumAttribute(string columName, DbType dbType) : this(columName) { this._dbType = dbType; } //列名 public virtual string ColumName { set { this._columName = value; } get { return this._columName; } } //描述一些特殊的数据库类型 public DbType DbType { get { return _dbType; } set { _dbType = value; } } ORMHelp public class ORMHelp { public void Insert(object table) { Type type = table.GetType(); //定义一个字典来存放表中字段和值的对应序列 Dictionary columValue = new Dictionary(); StringBuilder SqlStr=new StringBuilder(); SqlStr.Append("insert into "); //得到表名子 TableAttribute temp = (TalbeAttribute)type.GetCustomAttributes(typeof(TalbeAttribute), false).First(); SqlStr.Append(temp.TableName); SqlStr.Append("("); PropertyInfo[] Propertys=type.GetProperties(); foreach (var item in Propertys) { object[] attributes = item.GetCustomAttributes(false); foreach (var item1 in attributes) { //获得相应属性的值 string value= table.GetType().InvokeMember(item.Name, System.Reflection.BindingFlags.GetProperty, null, table, null).ToString(); ColumAttribute colum = item1 as ColumAttribute; if (colum != null) { columValue.Add(colum.ColumName,value); } } } //拼插入操作字符串 foreach (var item in columValue) { SqlStr.Append(item.Key); SqlStr.Append(","); } SqlStr.Remove(SqlStr.Length-1, 1); SqlStr.Append(") values('"); foreach (var item in columValue) { SqlStr.Append(item.Value); SqlStr.Append("','"); } SqlStr.Remove(SqlStr.Length - 2, 2); SqlStr.Append(")"); Console.WriteLine(SqlStr.ToString()); } } SqlStr中的内容为insert into User(userID,UserName) values('1','lfm')
前端使用代码:
static void Main(string[] args) { ORMHelp o = new ORMHelp(); User u = new User() { UserID=1,UserName="lfm"}; o.Insert(u); }
如何使用反射获取类型
首先我们来看如何获得类型信息。
获得类型信息有两种方法,一种是得到实例对象
这个时侯我仅仅是得到这个实例对象,得到的方式也许是一个object的引用,也许是一个接口的引用,但是我并不知道它的确切类型,我需要了解,那么就可
以通过调用System.Object上声明的方法GetType来获取实例对象的类型对象,比如在某个方法内,我需要判断传递进来的参数是否实现了某个
接口,如果实现了,则调用该接口的一个方法:
public void Process( object processObj ) { Type t = processsObj.GetType(); if( t.GetInterface(“ITest”) !=null ) .... }
另外一种获取类型的方法是通过Type.GetType以及Assembly.GetType方法,如:
Type t = Type.GetType(“System.String”);
需要注意的是,前面我们讲到了命名空间和装配件的关系,要查找一个类,必须指定它所在的装配件,或者在已经获得的Assembly实例上面调用GetType。
本装配件中类型可以只写类型名称,另一个例外是mscorlib.dll,这个装配件中声明的类型也可以省略装配件名称(.Net装配件编译的时候,默认都引用了mscorlib.dll,除非在编译的时候明确指定不引用它),比如:
System.String是在mscorlib.dll中声明的,上面的Type t = Type.GetType(“System.String”)是正确的
System.Data.DataTable是在System.Data.dll中声明的,那么:
Type.GetType(“System.Data.DataTable”)就只能得到空引用。
必须:
Type
t =
Type.GetType("System.Data.DataTable,System.Data,Version=1.0.3300.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089");
如何根据类型来动态创建对象
System.Activator提供了方法来根据类型动态创建对象,比如创建一个DataTable:
Type t = Type.GetType("System.Data.DataTable,System.Data,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); DataTable table = (DataTable)Activator.CreateInstance(t);
根据有参数的构造器创建对象
namespace TestSpace { public class TestClass { private string _value; public TestClass(string value) { _value=value; } } } … Type t = Type.GetType(“TestSpace.TestClass”); Object[] constructParms = new object[] {“hello”}; //构造器参数 TestClass obj = (TestClass)Activator.CreateInstance(t,constructParms);
如何获取方法以及动态调用方法
namespace TestSpace { public class TestClass { private string _value; public TestClass() { } public TestClass(string value) { _value = value; } public string GetValue( string prefix ) { if( _value==null ) return "NULL"; else return prefix+" : "+_value; } public string Value { set { _value=value; } get { if( _value==null ) return "NULL"; else return _value; } } } }
包含一个有参数的构造器,一个GetValue的方法,一个Value属性,我们可以通过方法的名称来得到方法并且调用之
//获取类型信息 Type t = Type.GetType("TestSpace.TestClass"); //构造器的参数 object[] constuctParms = new object[]{"timmy"}; //根据类型创建对象 object dObj = Activator.CreateInstance(t,constuctParms); //获取方法的信息 MethodInfo method = t.GetMethod("GetValue"); //调用方法的一些标志位,这里的含义是Public并且是实例方法,这也是默认的值 BindingFlags flag = BindingFlags.Public | BindingFlags.Instance; //GetValue方法的参数 object[] parameters = new object[]{"Hello"}; //调用方法,用一个object接收返回值 object returnValue = method.Invoke(dObj,flag,Type.DefaultBinder,parameters,null);
动态创建委托
委托是C#中实现事件的基础,有时候不可避免的要动态的创建委托,实际上委托也是一种类型:System.Delegate,所有的委托都是从这个类派生的
System.Delegate提供了一些静态方法来动态创建一个委托,比如一个委托:
namespace TestSpace { delegate string TestDelegate(string value); public class TestClass { public TestClass() { } public void GetValue(string value) { return value; } } }
示例:
TestClass obj = new TestClass(); //获取类型,实际上这里也可以直接用typeof来获取类型 Type t = Type.GetType(“TestSpace.TestClass”); //创建代理,传入类型、创建代理的对象以及方法名称 TestDelegate method = (TestDelegate)Delegate.CreateDelegate(t,obj,”GetValue”); String returnValue = method(“hello”);
1、使用反射通过读取配置文件来动态的创建相关类的对象
接口
interface ILog { bool Write(string message); bool Write(Exception ex); } TextFileLog class TextFileLog : ILog { public bool Write(string message) { string fileDir = ConfigurationManager.AppSettings["LogTarget"].ToString(); using (StreamWriter w = File.AppendText(fileDir)) { // w.Write(" Log Entry : "); w.WriteLine("发生时间{0}", DateTime.Now.ToLocalTime().ToString()); w.WriteLine("日志内容为:{0}", message); w.WriteLine("-------------------------------"); // Update the underlying file. w.Flush(); w.Close(); } return true; } public bool Write(Exception ex) { Write(ex.Message); return true; } } XmlFileLog class XmlFileLog : ILog { public bool Write(string message) { string xmlFilePath = ConfigurationManager.AppSettings["LogTarget"].ToString(); if (File.Exists(xmlFilePath)) { XmlDocument doc = new XmlDocument(); doc.Load(xmlFilePath); XmlDocumentFragment docFrag = doc.CreateDocumentFragment(); XmlNode nod = doc.SelectSingleNode("Logs"); docFrag.InnerXml = "" + DateTime.Now.ToLocalTime().ToString() + "" + message + ""; nod.AppendChild(docFrag); doc.Save(xmlFilePath); return true; } else { XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true; //设置缩进 settings.ConformanceLevel = ConformanceLevel.Auto; settings.IndentChars = " "; settings.OmitXmlDeclaration = false; using (XmlWriter writer = XmlWriter.Create(xmlFilePath, settings)) { //Start writing the XML document writer.WriteStartDocument(false); //Start with the root element writer.WriteStartElement("Logs"); writer.WriteStartElement("Log"); writer.WriteStartElement("Time"); writer.WriteString(DateTime.Now.ToLocalTime().ToString()); writer.WriteEndElement(); writer.WriteStartElement("Message"); writer.WriteString(message); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndDocument(); //Flush the object and write the XML data to the file writer.Flush(); return true; } } } public bool Write(Exception ex) { Write(ex.Message); return true; } }
App.config配置
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="LogType" value="LogClassLibrary.TextFileLog"/> <!-- 本程序集配置 <add key="LogType" value="ConsoleApplication2.Log例子.TextFileLog"/> --> <!-- XmlFileLog TextFileLog--> <add key="LogTarget" value="c:\log.txt"/> </appSettings> </configuration>
主程序
public static void Main() { #region 同程序集下 System.Type type=System.Type.GetType(ConfigurationManager.AppSettings["LogType"].ToString()); ILog log = (ILog)Activator.CreateInstance(type); log.Write(new Exception("异常测试")); #endregion }
如果在不同的程序集下,那主函数和配置会略有不同
不同程序集主函数
public static void Main() { #region 不同程序集 string assemblyPath = Path.Combine(Environment.CurrentDirectory, "LogClassLibrary.dll"); Assembly a = Assembly.LoadFrom(assemblyPath); Type type = a.GetType(ConfigurationManager.AppSettings["LogType"].ToString()); LogClassLibrary.ILog log = (LogClassLibrary.ILog)type.InvokeMember(null, BindingFlags.CreateInstance,null,null,null); log.Write(new Exception("异常测试")); #endregion }
插件编程技术
插件是指遵循一定的接口规范、可以动态加载和运行的程序模块。从上面的例子可以看出,通过反射可以非常方便的动态加载程序集。因此,利用反射的动态加载代 码能力,可以很容易的实现插件。插件编程的要点是使用接口来定义插件的功能特征。插件的宿主程序通过接口来确认、装载和执行插件的功能,实现插件功能的所 有类都必须实现定义插件的接口。
这里只是选贴一部分代码,详细分析请看源码
结构图
接口
public interface IHost { List Plugins { get; } int LoadPlugins(string path); ILog GetLog(string name); } public interface ILog { bool Write(string message); bool Write(Exception ex); }
宿主实现
Reflection,中文翻译为反射。这是.Net中获取运行时类型信息的方式,.Net的应用程序由几个部分:‘程序集(Assembly)’、‘模块(Module)’、‘类型(class)’组成,而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息,例如:Assembly类可以获得正在运行的装配件信息,也可以动态的加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等等,通过Type类可以得到这些要素的信息,并且调用之。MethodInfo包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。诸如此类,还有FieldInfo、EventInfo等等,这些类都包含在System.Reflection命名空间下。 一、Type类于获取类型信息 System.Type 类对于反射起着核心的作用。当反射请求加载的类型时,公共语言运行库将为它创建一个 Type。您可以使用 Type 对象的方法、字段、属性和嵌套类来查找有关该类型的所有信息。 大家运行一下下面的代码根据结果分析一下就能比较清楚的理解Type了 获取类型信息 namespace ConsoleApplication2 { class Program { static void Main(string[] args) { MyClass m = new MyClass(); Type type = m.GetType(); Console.WriteLine("类型名:" + type.Name); Console.WriteLine("类全名:"+type.FullName); Console.WriteLine("命名空间名:"+type.Namespace); Console.WriteLine("程序集名:"+type.Assembly); Console.WriteLine("模块名:"+type.Module); Console.WriteLine("基类名:"+type.BaseType); Console.WriteLine("是否类:"+type.IsClass); Console.WriteLine("类的公共成员:"); MemberInfo[] memberInfos = type.GetMembers();//得到所有公共成员 foreach (var item in memberInfos) { Console.WriteLine("{0}:{1}",item.MemberType,item); } } } class MyClass { public string m; public void test() { } public int MyProperty { get; set; } } } 二、获取程序集元数据 Assembly类定义了一个程序集,它是一个可重用、无版本冲突并且可自我描述的公共语言运行库应用程序构造块。因为程序集中是使用元数据进行自我描述的,所以我们就能通过其元数据得到程序集内部的构成。结合Assembly和反射能够获取程序集的元数据,但是首先要将程序集装入内存中。可以使用Assembly类的多种静态Load方法加载程序集。 下面的程序显示程序集的信息 public static void Main() { //获取当前执行代码的程序集 Assembly assem = Assembly.GetExecutingAssembly(); Console.WriteLine("程序集全名:"+assem.FullName); Console.WriteLine("程序集的版本:"+assem.GetName().Version); Console.WriteLine("程序集初始位置:"+assem.CodeBase); Console.WriteLine("程序集位置:"+assem.Location); Console.WriteLine("程序集入口:"+assem.EntryPoint); Type[] types = assem.GetTypes(); Console.WriteLine("程序集下包含的类型:"); foreach (var item in types) { Console.WriteLine("类:"+item.Name); } } 三、动态加载类型 早绑定是在编译时绑定对象类型,而晚绑定是在运行时才绑定对象的类型。利用反射可以实现晚绑定,即动态加载类型,并调用他们的方法,下边是MSDN中的一个例子,详细的解释信息见注释 动态加载类型 namespace ConsoleApplication2 { public class Example { private int factor; public Example(int f) { factor = f; } public int SampleMethod(int x) { Console.WriteLine("\nExample.SampleMethod({0}) executes.", x); return x * factor; } public static void Main() { //获取当前执行代码的程序集 Assembly assem = Assembly.GetExecutingAssembly(); Console.WriteLine("Assembly Full Name:"); Console.WriteLine(assem.FullName); // The AssemblyName type can be used to parse the full name. AssemblyName assemName = assem.GetName(); Console.WriteLine("\nName: {0}", assemName.Name); Console.WriteLine("Version: {0}.{1}", assemName.Version.Major, assemName.Version.Minor); Console.WriteLine("\nAssembly CodeBase:"); Console.WriteLine(assem.CodeBase); // 从程序集众创建一个Example实例并且用object类型的引用o指向它,同时调用一个输入参数的构造函数 Object o = assem.CreateInstance("ConsoleApplication2.Example", false, BindingFlags.ExactBinding, null, new Object[] { 2 }, null, null); //构造Example类的一个晚绑定的方法SampleMethod MethodInfo m = assem.GetType("ConsoleApplication2.Example").GetMethod("SampleMethod"); //调用刚才实例化好的Example对象o中的SampleMethod方法,传入的参数为42 Object ret = m.Invoke(o, new Object[] { 42 }); Console.WriteLine("SampleMethod returned {0}.", ret); Console.WriteLine("\nAssembly entry point:"); Console.WriteLine(assem.EntryPoint); } } 反射特性: [Table(Name="dbo.[User]")] public partial class User { 当C#编译器发现这个属性有一个特性Table时,首先会把字符串Attribute添加到这个名称的后面,形成一个组合名称TableAttribute,然后在其搜索路径的所有命名空间中搜索有相同类名的类。但要注意,如果该特性名结尾是Attribute,编译器就不会把该字符串加到组合名称中。所有的特性都是从System.Attribute类型上面派生的。 接着我们来看一下Table特性的定制格式 [AttributeUsageAttribute(AttributeTargets.Class,Inherited=true,AllowMultiple=false)] public class TalbeAttribute:Attribute { 在定义类型时使用System.AttributeUsage特性来表明这个自定义特性的使用范围,这里使用了Class样式,表示TableAttribute特性只能用在其它的Class类型前面,若放置在Interface或Struct类型前面,或者放在对象成员的前面则会出现编译错误。这里还是用语句 AllowMultiple=false 语句来表明对于一个类型,该特性只能用一次,若一个Class类型前面出现多个TableAttribute,则会出现编译错误。若设置AllowMultiple=true,则该特性可以多次定义,也就是一个Class类型前面可以出现多个相同类型的特性。不过这里我们假设一个对象只能映射到一个数据表上,没有多重映射,因此就指明对同一个类型该特性不能多次使用。Inherited参数设定为true,就表示应用到类或接口上的特性也可以自动应用到所派生的类或接口上。 我们再看一下定制TalbeAttribute特性的完整例子: [AttributeUsageAttribute(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public class TableAttribute : Attribute { //保存表名的字段 private string _tableName; public TableAttribute() { } public TableAttribute(string tableName) { this._tableName = tableName; } /// /// 映射的表名(表的全名:模式名.表名) /// public string TableName { set { this._tableName = value; } get { return this._tableName; } } } 特性也是一个Class类型,可以有多个构造函数,就像C#的new语句一样,我们向类型附加特性时可以使用不同的初始化参数来指明使用特性的那个构造函数。我们附加特性时还可以使用“属性名=属性值”的方法来直接指明特性的属性值。该特性中定义了一个TableName属性,该属性就是被修饰的对象所映射的数据库表的名称。 下面我们举一个使用特性来进行O/RMapping的例子,也就是将对象转化成Sql语句 用户类: User类 [Table("User")] public class User { [Colum("userID", DbType = DbType.Int32)] public int UserID { get; set; } [Colum("UserName", DbType = DbType.String)] public string UserName { get; set; } } 表特性 [AttributeUsageAttribute(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public class TableAttribute : Attribute { //保存表名的字段 private string _tableName; public TableAttribute() { } public TableAttribute(string tableName) { this._tableName = tableName; } /// /// 映射的表名(表的全名:模式名.表名) /// public string TableName { set { this._tableName = value; } get { return this._tableName; } } } 列特性: [AttributeUsageAttribute(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] public class ColumAttribute : Attribute { private string _columName; private DbType _dbType ; public ColumAttribute() { } public ColumAttribute(string columName) : this() { this._columName = columName; } public ColumAttribute(string columName, DbType dbType) : this(columName) { this._dbType = dbType; } //列名 public virtual string ColumName { set { this._columName = value; } get { return this._columName; } } //描述一些特殊的数据库类型 public DbType DbType { get { return _dbType; } set { _dbType = value; } } ORMHelp public class ORMHelp { public void Insert(object table) { Type type = table.GetType(); //定义一个字典来存放表中字段和值的对应序列 Dictionary columValue = new Dictionary(); StringBuilder SqlStr=new StringBuilder(); SqlStr.Append("insert into "); //得到表名子 TableAttribute temp = (TalbeAttribute)type.GetCustomAttributes(typeof(TalbeAttribute), false).First(); SqlStr.Append(temp.TableName); SqlStr.Append("("); PropertyInfo[] Propertys=type.GetProperties(); foreach (var item in Propertys) { object[] attributes = item.GetCustomAttributes(false); foreach (var item1 in attributes) { //获得相应属性的值 string value= table.GetType().InvokeMember(item.Name, System.Reflection.BindingFlags.GetProperty, null, table, null).ToString(); ColumAttribute colum = item1 as ColumAttribute; if (colum != null) { columValue.Add(colum.ColumName,value); } } } //拼插入操作字符串 foreach (var item in columValue) { SqlStr.Append(item.Key); SqlStr.Append(","); } SqlStr.Remove(SqlStr.Length-1, 1); SqlStr.Append(") values('"); foreach (var item in columValue) { SqlStr.Append(item.Value); SqlStr.Append("','"); } SqlStr.Remove(SqlStr.Length - 2, 2); SqlStr.Append(")"); Console.WriteLine(SqlStr.ToString()); } } SqlStr中的内容为insert into User(userID,UserName) values('1','lfm') 前端使用代码: 前端代码 static void Main(string[] args) { ORMHelp o = new ORMHelp(); User u = new User() { UserID=1,UserName="lfm"}; o.Insert(u); } 应用 例子这个东西其实挺难弄得,弄个简单的,虽然能说明问题但却容易让人觉得没实用价值,弄个有实用价值却又往往牵扯很多别的技术甚至牵扯很多业务逻辑,看起来很复杂很难懂。在这里我尽量追求几个有实用价值又不复杂的例子。 1、使用反射通过读取配置文件来动态的创建相关类的对象 我们先来看看Main函数和需要动态加载的对象在同一个程序集的情况 结构图: 接口 interface ILog { bool Write(string message); bool Write(Exception ex); } TextFileLog class TextFileLog : ILog { public bool Write(string message) { string fileDir = ConfigurationManager.AppSettings["LogTarget"].ToString(); using (StreamWriter w = File.AppendText(fileDir)) { // w.Write(" Log Entry : "); w.WriteLine("发生时间{0}", DateTime.Now.ToLocalTime().ToString()); w.WriteLine("日志内容为:{0}", message); w.WriteLine("-------------------------------"); // Update the underlying file. w.Flush(); w.Close(); } return true; } public bool Write(Exception ex) { Write(ex.Message); return true; } } XmlFileLog class XmlFileLog : ILog { public bool Write(string message) { string xmlFilePath = ConfigurationManager.AppSettings["LogTarget"].ToString(); if (File.Exists(xmlFilePath)) { XmlDocument doc = new XmlDocument(); doc.Load(xmlFilePath); XmlDocumentFragment docFrag = doc.CreateDocumentFragment(); XmlNode nod = doc.SelectSingleNode("Logs"); docFrag.InnerXml = "" + DateTime.Now.ToLocalTime().ToString() + "" + message + ""; nod.AppendChild(docFrag); doc.Save(xmlFilePath); return true; } else { XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true; //设置缩进 settings.ConformanceLevel = ConformanceLevel.Auto; settings.IndentChars = " "; settings.OmitXmlDeclaration = false; using (XmlWriter writer = XmlWriter.Create(xmlFilePath, settings)) { //Start writing the XML document writer.WriteStartDocument(false); //Start with the root element writer.WriteStartElement("Logs"); writer.WriteStartElement("Log"); writer.WriteStartElement("Time"); writer.WriteString(DateTime.Now.ToLocalTime().ToString()); writer.WriteEndElement(); writer.WriteStartElement("Message"); writer.WriteString(message); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndDocument(); //Flush the object and write the XML data to the file writer.Flush(); return true; } } } public bool Write(Exception ex) { Write(ex.Message); return true; } } App.config配置 <?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="LogType" value="LogClassLibrary.TextFileLog"/> <!-- 本程序集配置 <add key="LogType" value="ConsoleApplication2.Log例子.TextFileLog"/> --> <!-- XmlFileLog TextFileLog--> <add key="LogTarget" value="c:\log.txt"/> </appSettings> </configuration> 主程序 public static void Main() { #region 同程序集下 System.Type type=System.Type.GetType(ConfigurationManager.AppSettings["LogType"].ToString()); ILog log = (ILog)Activator.CreateInstance(type); log.Write(new Exception("异常测试")); #endregion } 如果在不同的程序集下,那主函数和配置会略有不同 不同程序集主函数 public static void Main() { #region 不同程序集 string assemblyPath = Path.Combine(Environment.CurrentDirectory, "LogClassLibrary.dll"); Assembly a = Assembly.LoadFrom(assemblyPath); Type type = a.GetType(ConfigurationManager.AppSettings["LogType"].ToString()); LogClassLibrary.ILog log = (LogClassLibrary.ILog)type.InvokeMember(null, BindingFlags.CreateInstance,null,null,null); log.Write(new Exception("异常测试")); #endregion } 这部分源码下载 源码下载 2、插件编程技术 插件是指遵循一定的接口规范、可以动态加载和运行的程序模块。从上面的例子可以看出,通过反射可以非常方便的动态加载程序集。因此,利用反射的动态加载代码能力,可以很容易的实现插件。插件编程的要点是使用接口来定义插件的功能特征。插件的宿主程序通过接口来确认、装载和执行插件的功能,实现插件功能的所有类都必须实现定义插件的接口。 这里只是选贴一部分代码,详细分析请看源码 结构图 接口部分 接口 public interface IHost { List Plugins { get; } int LoadPlugins(string path); ILog GetLog(string name); } public interface ILog { bool Write(string message); bool Write(Exception ex); } 宿主实现 public class Host : IHost { private List plugins = new List(); #region IHost 成员 public List Plugins { get { return plugins; } } public int LoadPlugins(string path) { string[] assemblyFiles = Directory.GetFiles(path, "*.dll"); foreach (var file in assemblyFiles) { Assembly assembly = Assembly.LoadFrom(file); foreach (var type in assembly.GetExportedTypes()) { if (type.IsClass && typeof(ILog).IsAssignableFrom(type)) { ILog plugin = Activator.CreateInstance(type) as ILog; plugins.Add(plugin); } } } return plugins.Count; } public ILog GetLog(string name) { foreach (var item in plugins) { if (item.GetType().ToString()==name) { return item; } } return null; } #endregion } ILog的实现和上例基本一样,请参考 主程序代码static void Main(string[] args) { Host.Host host = new Host.Host(); host.LoadPlugins("."); InterfaceLayer.ILog log = host.GetLog(ConfigurationManager.AppSettings["LogType"].ToString()); log.Write(new Exception("异常测试")); } 插件编程源码下载 源码下载 3、分析对象,得到对象中的属性值 大家使用应都用过asp.net中的DropdownList,在绑定其值的时候绝大多数情况下我们做的都是同样的事情,获得数据源,根据数据源中的某些列绑定控件,下边我们来说说通用情况的处理方式。我们只需要提供数据集合,以及需要绑定到控件的两个属性(text,value)名即可。 public class DDlControl { private ListControl underlyingList; public DDlControl(ListControl underlyingList) { this.underlyingList = underlyingList; } public void Add(IDDL ddl) { underlyingList.Items.Add(new ListItem(ddl.Name, ddl.Value)); } public void Add(T t, string nameStr, string valueStr) { string name = Convert.ToString(t.GetType().InvokeMember (nameStr, System.Reflection.BindingFlags.GetProperty, null, t, null)); string value = Convert.ToString(t.GetType().InvokeMember (valueStr, System.Reflection.BindingFlags.GetProperty, null, t, null)); Add(new DDLStruct(name,value)); } public void Clear() { underlyingList.Items.Clear(); } public IDDL SelectedItem { get { ListItem item = underlyingList.SelectedItem; return new DDLStruct(item.Text, item.Value); } } public void BindTo(IEnumerable list, string nameStr, string valueStr) { Clear(); foreach (var item in list) { Add(item, nameStr, valueStr); } } public string SelectValue { get { return underlyingList.SelectedValue; } set { underlyingList.SelectedValue=value; } } } public struct DDLStruct { public DDLStruct(string name, string value) { this.name = name; this.value = value; } private string name; private string value; public string Name { get { return name; } } public string Value { get { return value; } } }
分析对象,得到对象中的属性值
应都用过asp.net中的DropdownList,在绑定其值的时候绝大多数情况下我们 做的都是同样的事情,获得数据源,根据数据源中的某些列绑定控件,下边我们来说说通用情况的处理方式。我们只需要提供数据集合,以及需要绑定到控件的两个 属性(text,value)名即可。
public class DDlControl { private ListControl underlyingList; public DDlControl(ListControl underlyingList) { this.underlyingList = underlyingList; } public void Add(IDDL ddl) { underlyingList.Items.Add(new ListItem(ddl.Name, ddl.Value)); } public void Add(T t, string nameStr, string valueStr) { string name = Convert.ToString(t.GetType().InvokeMember (nameStr, System.Reflection.BindingFlags.GetProperty, null, t, null)); string value = Convert.ToString(t.GetType().InvokeMember (valueStr, System.Reflection.BindingFlags.GetProperty, null, t, null)); Add(new DDLStruct(name,value)); } public void Clear() { underlyingList.Items.Clear(); } public IDDL SelectedItem { get { ListItem item = underlyingList.SelectedItem; return new DDLStruct(item.Text, item.Value); } } public void BindTo(IEnumerable list, string nameStr, string valueStr) { Clear(); foreach (var item in list) { Add(item, nameStr, valueStr); } } public string SelectValue { get { return underlyingList.SelectedValue; } set { underlyingList.SelectedValue=value; } } } public struct DDLStruct { public DDLStruct(string name, string value) { this.name = name; this.value = value; } private string name; private string value; public string Name { get { return name; } } public string Value { get { return value; } } }