C# 反射/對映學習

Nincems發表於2018-11-18

轉自:https://www.cnblogs.com/kingcat/archive/2007/09/03/879873.html

最近想研究一下反射,先上網找了找資料,幾乎大部分都是照抄MSDN的內容,生澀難懂,幾乎沒說,又找了找,發現一些強人的例項解析,才稍微有了點門道,個人感覺,反射其實就是為了能夠在程式執行期間動態的載入一個外部的DLL集合,然後通過某種辦法找到這個DLL集合中的某個空間下的某個類的某個成員(通過反射可以訪問該類所包含的所有成員,不論成員是公有還是私有),看看網上N人寫的例項:

1.運用反射呼叫其它程式集中的方法:
假設另一個工程中的所有類都編譯到一個dll檔案中了,在這很多的類當中,有一個類叫StringUtil,名稱空間在HSMP.CommonBasic.Common 程式碼如下 :

namespace lab.CommonBasic.Common
{
class StringUtil
{
    public double GetSum(double x, double y)
    {
        return x + y;
    }   
}
}

編譯後dll檔案的存放路徑是:D:\Test\HSMP.CommonBasic.dll
現在的問題是,如何通過程式呼叫該dll檔案中的GetSum方法
大概有以下幾步:

//這裡要用LoadFrom,只有在本工程裡新增了該dll的引用後才可以使用Load   
Assembly objAss = Assembly.LoadFrom(@"D:\Test\HSMP.CommonBasic.dll");

//HSMP.CommonBasic.Common.StringUtil類的全路徑   
Type t = objAss.GetType("HSMP.CommonBasic.Common.StringUtil");

//動態生成類StringUtil的例項   
object obj = System.Activator.CreateInstance(t);

//引數資訊,GetSum需要兩個int引數   
System.Type[] paramTypes = new System.Type[2];
paramTypes[0] = System.Type.GetType("System.Int32");
paramTypes[1] = System.Type.GetType("System.Int32");

//找到對應的方法   
MethodInfo p = t.GetMethod("SayHello", paramTypes);

// 也可以不給型別引數,下面呼叫時給實參就可以
MethodInfo p = t.GetMethod("SayHello");

//引數值,如果所呼叫的方法沒有引數,不用寫這些   
Object[] parameters = new Object[2];
parameters[0] = 3;
parameters[1] = 4;

//如果沒有引數,寫null即可。 
object objRetval = p.Invoke(obj, parameters);

通過程式碼可以看出反射其實很簡單。就是動態型別載入。 以上我們用反射對靜態型別做載入,但如果類是個範型類該怎麼辦呢?其實方法大同小異,只是獲取到型別時需要設定一下實際要使用的型別即可,先看範型類定義:

namespace lab.CommonBasic.Common
{
class StringUtil<T,U>
{
    public string GetSum(T x, U y)
    {
        return x + " and " + y;
    }   
}
}

再看反射操作:

... 前面程式碼相同

//HSMP.CommonBasic.Common.StringUtil類的全路徑(範型類最後用 `2 標示需要2個範型型別引數)   
Type t = objAss.GetType("HSMP.CommonBasic.Common.StringUtil`2");

// 這一步很重要,設定實際的範型型別
t = t.MakeGenericType(new Type[2] { typeof(string), typeof(string) });

//動態生成類StringUtil的例項   
object obj = System.Activator.CreateInstance(t);

... 後面程式碼相同

還有一種情況,如果類是常類,但方法是範型方法改怎麼辦?非常簡單:

... 前面程式碼相同

//HSMP.CommonBasic.Common.StringUtil類的全路徑(範型類最後用 `2 標示需要2個範型型別引數)   
Type t = objAss.GetType("HSMP.CommonBasic.Common.StringUtil`2");

// 這一步要去掉,因為類不是泛型類所以不能給類設定範型型別
//t = t.MakeGenericType(new Type[2] { typeof(string), typeof(string) }); 
... 中間程式碼相同     // 區別在這裡,把類級別的範型型別設定放到方法級別就可以了。    MethodInfo p = t.GetMethod("SayHello");
MethodInfo p = p.MakeGenericMethod(new Type[2] { typeof(string), typeof(string) });
... 後面程式碼相同

2.動態載入, 更改, 增加...某個程式集 下面例子中, ChangeValue類的myValue本是私有欄位, 一般情況下在類外部是不能改它的值的, 但利用反射就能直接訪問私有欄位而不需要通過包裝器

class ChangeValue
{
// 這是私有欄位
private string myValue;

// 一般只有通過這樣的公共屬性外面才可能訪問私有欄位
public ChangeValue(string str)
{
    myValue = str;
}
public void WriteLine()
{
    Console.WriteLine("MyValue is: " + myValue);
}
}

class Test
{
  public static void Main(string[] args)
  {
    // 正常訪問
    ChangeValue cv = new ChangeValue("old value");
    cv.WriteLine();

    //反射的方法直接訪問私有欄位
    Type t = cv.GetType();

    //得到私有欄位物件並賦值
    FieldInfo field = t.GetField("myValue", BindingFlags.NonPublic | BindingFlags.Instance);
    field.SetValue(cv, "new value");

    //輸出的是新值 "new value"
    cv.WriteLine();
 }
 }

打個比方, 你要寫一個播放器, 要支援如mp3, wmv, avi...等格式, 你還希望使用者能自己安裝一個新的格式, 也就是我們常說的外掛.

在實現這些, 可能你要將每種格式都寫成單個的解碼程式集, 如 mp3.dll, wmv.dll, avi.dll.... 這樣當播放時, 根據副檔名去動態呼叫相應的解碼程式集, 那麼這時你就得用反射去動態載入這些dll了.如: Assembly.LoadFile ("...avi.dll"), 然後通過反射可以用avi.dll裡面定義的類了.

原文請見:https://www.cnblogs.com/kingcat/archive/2007/09/03/879873.html

相關文章