詳解C#編程中的反射機制與方法
編輯推薦《C#實用基礎教程》
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]")] |
當C#編譯器發現這個屬性有一個特性Table時,首先會把字符串Attribute添加到這個名稱的后面,形成一個組合名稱TableAttribute,然后在其搜索路徑的所有命名空間中搜索有相同類名的類。但要注意,如果該特性名結尾是Attribute,編譯器就不會把該字符串加到組合名稱中。所有的特性都是從System.Attribute類型上面派生的。
接著我們來看一下Table特性的定制格式
[AttributeUsageAttribute(AttributeTargets.Class,Inherited=true,AllowMultiple=false)] |
在定義類型時使用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")] |
表特性
[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();
//定義一個字典來存放表中字段和值的對應序列
DictionarycolumValue = 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) |
應用
例子這個東西其實挺難弄得,弄個簡單的,雖然能說明問題但卻容易讓人覺得沒實用價值,弄個有實用價值卻又往往牽扯很多別的技術甚至牽扯很多業務邏輯,看起來很復雜很難懂。在這里我盡量追求幾個有實用價值又不復雜的例子。
#p#
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 = "
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配置
<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() |
如果在不同的程序集下,那主函數和配置會略有不同
不同程序集主函數
public static void Main() |
這部分源碼下載
2、插件編程技術
插件是指遵循一定的接口規范、可以動態加載和運行的程序模塊。從上面的例子可以看出,通過反射可以非常方便的動態加載程序集。因此,利用反射的動態加載代碼能力,可以很容易的實現插件。插件編程的要點是使用接口來定義插件的功能特征。插件的宿主程序通過接口來確認、裝載和執行插件的功能,實現插件功能的所有類都必須實現定義插件的接口。
這里只是選貼一部分代碼,詳細分析請看源碼
結構圖
接口部分
接口
public interface IHost
{
ListPlugins { 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 Listplugins = 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; }
}
}
【編輯推薦】