在瞭解組件的相關資訊後,本章節接著要探討的是,一個組件在沒有事先被參考的前題下,如何被動態載入與執行。
載入組件與叫用成員
動態載入組件,並執行方法的步驟:
- 載入組件,取得型別 (Type)。
- 取得建構函式:透過型別的 GetConstructor 方法取得建構函式資訊 ( ConstructorInfo )。
- 建立執行個體:透過建構函式叫用 ConstructorInfo.Invoke 建立物件的執行個體。
- 取得方法:透過型別的 GetMethod 方法取得要執行的方法資訊 ( MethodInfo )。
- 叫用方法資訊 MethodInfo.Invoke 以執行程式碼。
叫用建構函式 (ConstructorInfo.Invoke) 的時候,必須傳入相對應的建構函式的[參數列]。
public object Invoke(object[] parameters); public abstract object Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture);
叫用方法 (MethodInfo.Invoke) 的時候,必須傳入2個參數。其中,第一個參數是[執行個體],第二個參數是要傳給該方法的[參數列]。
public object Invoke(object obj, object[] parameters); public abstract object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture);
也可以使用 (Type.InvokeMember) 叫用方法。
public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args); public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, CultureInfo culture); ...
例一:使用沒有參數的建構子建立物件,並叫用方法
//=======================================================
// 1) 載入組件,取得型別
//=======================================================
string dll_file = @"D:\MCTS\MCTS2011\Practice\ClassLibrary1\bin\Debug\ClassLibrary1.dll";
Assembly asm = Assembly.LoadFile(dll_file);
Type employee = asm.GetType("ClassLibrary1.Employee");
//=======================================================
// 2) 取得建構函式資訊
//=======================================================
// 傳入空的陣列參數列,建立一個 Constructor
Type[] args = new Type[] { };
ConstructorInfo ContInfo = employee.GetConstructor(args);
//=======================================================
// 3) 建立執行個體
//=======================================================
// 產生 ctor 的 instance
object obj = ContInfo.Invoke(null);
//=======================================================
// 4) 取得方法資訊
//=======================================================
MethodInfo miShowData = employee.GetMethod("ShowData");
//=======================================================
// 5) Invoke 方法
//=======================================================
string sEmpData = (string) miShowData.Invoke(obj, null);
Console.WriteLine(sEmpData); //I am Unknow, 0 years old.
例二:叫用靜態成員。
若呼叫的是一個靜態方法,則不用建立執行個體,所以第一個參數可以用 null 取代。 若不用傳參數給方法,第二個參數也可以用 null 取代。
Assembly asm = Assembly.LoadFile(@"D:\MCTS\MCTS2011\Practice\ClassLibrary1\bin\Debug\ClassLibrary1.dll");
Type type = asm.GetType("ClassLibrary1.Employee");
//不傳參數呼叫靜態方法
string return1 = (string)type.GetMethod("SayHello").Invoke(null, null);
Console.WriteLine(return1); //hello.
//傳參數呼叫靜態方法
string return2 = (string)type.GetMethod("MyName").Invoke(null, new object[] { "vito" });
Console.WriteLine(return2); //I am vito
例三:使用具參數的建構子建立物件,並叫用方法
//get the instance of controler Type[] args2 = Type.EmptyTypes; ConstructorInfo ctor2 = emp.GetConstructor(args2); object obj2 = ctor2.Invoke(null); //invoke method sEmpData = (string)miShowData.Invoke(obj2, null); Console.WriteLine(sEmpData); //I am Unknow, 0 years old.
例四:精簡一下寫法
建立執行個體:GetConstructor.Invoke(建構子的參數陣列);
執行方法:GetMethod.Invoke(物件執行個體, 方法的參數陣列);
// 載入組件,取得型別
string dll_file = @"D:\MCTS\MCTS2011\Practice\ClassLibrary1\bin\Debug\ClassLibrary1.dll";
Type emp = Assembly.LoadFile(dll_file).GetType("ClassLibrary1.Employee");
// 建立型別的執行個體
// invoke 的參數是建構子的參數列
Type[] args = new Type[] { typeof(string), typeof(string), typeof(int) }; ;
object obj = emp.GetConstructor(args).Invoke(new object[] { "1234", "vito", 20 }); //指明以哪一個 Constructor 建立執行個體
// 叫用型別的方法
// invoke 的第一個參數是執行個體,第二個參數是要傳給方法的參數列
string sEmpData = (string)emp.GetMethod("ShowData").Invoke(obj, null);
Console.WriteLine(sEmpData); //I am vito, 20 years old.
除了上面例子中提到的 MethodInfo 類別,其他 Info 類別 (FieldInfo,EventInfo...) ,也都是使用類似的方法來呼叫程式碼。
Binder 類別
在處理動態程式碼時,可以建立自已的 Binder 類別,以決定如何處理型別的轉換。
Activator 類別
使用 Activator 類別的 CreateInstance 方法,可以用來建立指定型別的執行個體。 這樣就不必再透過 ConstructorInfo , 它會叫用最符合指定引數的建構函式,建立型別的執行個體。
// 載入組件,取得型別
string dll_file = @"D:\MCTS\MCTS2011\Practice\ClassLibrary1\bin\Debug\ClassLibrary1.dll";
Type emp = Assembly.LoadFile(dll_file).GetType("ClassLibrary1.Employee");
// 建立型別(無參數)的執行個體
object instance1 = Activator.CreateInstance(emp);
string sEmpData = (string)emp.GetMethod("ShowData").Invoke(instance1, null);
Console.WriteLine(sEmpData);
// 建立型別(有參數)的執行個體
object instance2 = Activator.CreateInstance(emp, new object[] { "1234", "vito", 20 });
string sEmpData2 = (string)emp.GetMethod("ShowData").Invoke(instance2, null);
Console.WriteLine(sEmpData);
範例
例六:下面範例示範,如何載入 System.Web 組件,並使用 HttpUtility 類別進行文字的編碼。
string path = @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Web.dll";
// 根據指定的檔案建立組件
Assembly webAssembly = Assembly.LoadFile(path);
// 取得型別 HttpUtility
Type utilType = webAssembly.GetType("System.Web.HttpUtility");
// 取得靜態方法 HtmlEncode 和 HtmlDecode
MethodInfo miEncode = utilType.GetMethod("HtmlEncode", new Type[] { typeof(string) });
MethodInfo miDecode = utilType.GetMethod("HtmlDecode", new Type[] { typeof(string) });
// Create a string to be encoded
string strOriginal = "This is Sally & Jack's Anniversary";
Console.WriteLine(strOriginal);
// 叫用 HtmlEncode 方法
string strEncoded = (string)miEncode.Invoke(null, new object[] { strOriginal });
Console.WriteLine(strEncoded);
// 叫用 HtmlDecode 方法
string strDecoded = (string)miDecode.Invoke(null, new object[] { strEncoded });
Console.WriteLine(strDecoded);
例七:下面是一個完整例子,示範反射的相關用法。
組件原始內容
namespace ClassLibrary1
{
public class Employee
{
private string EmpNum;
public string EmpName { get; set; }
public int Age { get; set; }
public Employee()
{
EmpNum = "F123456789";
EmpName = "Unknow";
Age = 0;
}
public Employee(string pEmpNum, string pEmpName, int pAge)
{
EmpNum = pEmpNum;
EmpName = pEmpName;
Age = pAge;
}
public string ShowData()
{
string sAge = Age.ToString();
string msg = "I am " + EmpName + ", " + sAge + " years old.";
return msg;
}
public void SetAge(int pAge)
{
Age = pAge;
}
public static string MyName(string name)
{
return "I am " + name;
}
private string MyNum()
{
return EmpNum;
}
}
}
反射上面組件的內容
//載入組件,
string dll_file = @"D:\MCTS\MCTS2011\Practice\ClassLibrary1\bin\Debug\ClassLibrary1.dll";
Assembly asm = Assembly.LoadFile(dll_file);
//反射型別
Type empType = asm.GetType("ClassLibrary1.Employee");
//建立實體
object employee = Activator.CreateInstance(empType, new object[] { "F222222222", "vito", 20 });
//=========================================================================
// 使過 MethodInfo.Invoke 叫用方法
//=========================================================================
//叫用 ShowData 方法:(1)使用 MethodInfo.Invoke
MethodInfo miShowData = empType.GetMethod("ShowData");
string sShowData = (string) miShowData.Invoke(employee, null);
Console.WriteLine(sShowData);
//取得屬性
PropertyInfo piEmpName = empType.GetProperty("EmpName");
string EmpName = (string) piEmpName.GetValue(employee, null);
//設定屬性
PropertyInfo piAge = empType.GetProperty("Age");
piAge.SetValue(employee, 30, null);
//=========================================================================
// 使過 Type.InvokeMember 叫用方法
//=========================================================================
//【叫用 Public 方法】 ShowData :(2)使用 Type.InvokeMember
sShowData = (string)empType.InvokeMember(
"ShowData"
, BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance
, null, employee, null);
Console.WriteLine(sShowData);
//【叫用 Private 方法】 MyNum
string sEmpNum = (string)empType.InvokeMember(
"MyNum"
, BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance
, null, employee, null);
//【叫用帶參數的公開方法】
empType.InvokeMember(
"SetAge"
, BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance
, null, employee, new object[]{25});
//【叫用 Static 方法】 (不須使用到 instance)
string sMyName = (string)empType.InvokeMember(
"MyName"
, BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.Public
, null, null, new object[] { "test" });
//【取得 Private 欄位】
sEmpNum = (string)empType.InvokeMember(
"EmpNum"
, BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance
, null, employee, null);
例八:動態載入組件與執行方法的完整例子。
// 1)取得組件
Assembly asm = Assembly.LoadFrom(@"D:\MCTS\MCTS2011\Practice\ClassLibrary1\bin\Debug\ClassLibrary1.dll");
// 2)取得型別
Type clsEmploye = asm.GetType("ClassLibrary1.Employee");
// 3)取得方法
MethodInfo method = clsEmploye.GetMethod("ShowData");
// 4)叫用 ShowData 方法
// 4.1)建立constructor
ConstructorInfo ctor = clsEmploye.GetConstructor(new Type[] { typeof(string), typeof(string), typeof(int) });
object obj = ctor.Invoke(new object[] { "1234", "vito", 20 });
// 4.2)invoke method
string sEmpData = (string)method.Invoke(obj, new object[] { });
Console.WriteLine(sEmpData);
// MethodInfo 類別底下的幾個重要屬性
Console.WriteLine("IsAbstract:{0}", method.IsAbstract); //判斷 method 是否為 abstract :
Console.WriteLine("IsPrivate:{0}", method.IsPrivate); //判斷 method 是否為 private :
Console.WriteLine("IsPublic:{0}", method.IsPublic); //判斷 method 是否為 public :
Console.WriteLine("IsFamily:{0}", method.IsFamily); //判斷 method 是否為 protected :只能在其類別和衍生類別內看見該方法或建構函式。
Console.WriteLine("IsAssembly:{0}", method.IsAssembly); //判斷 method 是否為 internal :只有相同組件中的其他型別可以看見該方法或建構函式,組件外部的衍生型別則看不見它們。
Module module = method.Module; //反向取得Module
Type type = method.ReflectedType; //反向取得Type,回傳結果等同上面的clsEmploye
.NET DLL 組件一旦載入,就會一直留在記憶體中,直到載入它的主程式結束為止。 若要更靈活運用記憶體,就必須使用把 DLL 組件載入到不同的 app domain。
沒有留言:
張貼留言