一、反射是什麼
1、C#編譯執行過程
高階語言->編譯->dll/exe檔案->CLR/JIT->機器碼
2、原理解析
metadata:後設資料資料清單,記錄了dll中包含了哪些東西,是一個描述。
IL:中間語言,編譯把高階語言編譯後得到的C#中最真實的語言狀態,面嚮物件語言。
反射:來自於System.Reflection,是一個幫助類庫,可以讀取dll/exe中metadata,使用metadata建立物件。
Emit:一種反射技術,可以動態建立dll/exe。
反編譯工具:ILSpy可以反編譯dll/exe,檢視對應的C#/IL程式碼。
二、反射建立物件
1、動態讀取dll
- LoadFrom:dll全名稱,需要字尾
- LoadFile:全路徑,需要dll字尾
- Load:dll名稱不需要字尾
//1、動態讀取dll的三種方式
//(1)LoadFrom:dll全名稱,需要字尾
Assembly assembly = Assembly.LoadFrom("Business.DB.SqlServer.dll");
//(2)LoadFile:全路徑,需要dll字尾
//Assembly assembly1 = Assembly.LoadFile(@"dll檔案全路徑");
//(3)Load:dll名稱 不需要字尾
//Assembly assembly2 = Assembly.Load("Business.DB.SqlServer");
2、獲取型別
//2、獲取某一個具體的型別,引數需要是類的全名稱
Type type1 = assembly.GetType("Business.DB.SqlServer.SqlServerHelper");
3、建立物件
- 直接傳型別
- 過載方法,傳dll的全名稱
- 返回值是object型別,不能直接呼叫方法
//3、建立物件
//(1)直接傳型別
object? oInstance = Activator.CreateInstance(type1);
//(2)過載方法,傳dll的全名稱
//object? oInstanc1= Activator.CreateInstance("Business.DB.SqlServer.dll", "Business.DB.SqlServer.SqlServerHelper");
//a.oInstance.Query();//報錯了:因為oInstance當做是一個object型別,object型別是沒有Query方法;C#語言是一種強型別語言;編譯時決定你是什麼型別,以左邊為準;不能呼叫是因為編譯器不允許;實際型別一定是SqlServerHelper;
//b.如果使用dynamic 作為型別的宣告,在呼叫的時候,沒有限制;
//c.dynamic :動態型別:不是編譯時決定型別,避開編譯器的檢查;執行時決定是什麼型別
//d.dynamic dInstance = Activator.CreateInstance(type);
//e.dInstance.Query();
//f.dInstance.Get(); //報錯了--因為SqlServerHelper沒有Get方法
4、型別轉換
//4、型別轉換
// SqlServerHelper helper = (SqlServerHelper)oInstance; //不建議這樣轉換--如果真實型別不一致--會報報錯;
IDBHelper helper = oInstance as IDBHelper;//如果型別一直,就轉換,如果不一致;就返回null
三、反射建立物件詳解
using System;
namespace MyReflecttion
{
/// <summary>
/// 反射測試類
/// </summary>
public class ReflectionTest
{
#region Actor
/// <summary>
/// 無參建構函式
/// </summary>
public ReflectionTest()
{
Console.WriteLine($"這裡是{this.GetType()} 無引數建構函式");
}
/// <summary>
/// 帶引數建構函式
/// </summary>
/// <param name="name"></param>
public ReflectionTest(string name)
{
Console.WriteLine($"這裡是{this.GetType()} 有引數建構函式");
}
public ReflectionTest(int id)
{
Console.WriteLine($"這裡是{this.GetType()} 有引數建構函式");
}
public ReflectionTest(int id, string name)
{
Console.WriteLine($"這裡是{this.GetType()} 有引數建構函式");
}
public ReflectionTest(string name,int id )
{
Console.WriteLine($"這裡是{this.GetType()} 有引數建構函式");
}
#endregion
#region Method
/// <summary>
/// 無參方法
/// </summary>
public void Show1()
{
Console.WriteLine($"這裡是{this.GetType()}的Show1" );
}
/// <summary>
/// 有引數方法
/// </summary>
/// <param name="id"></param>
public void Show2(int id)
{
Console.WriteLine($"這裡是{this.GetType()}的Show2");
}
/// <summary>
/// 過載方法之一
/// </summary>
/// <param name="id"></param>
/// <param name="name"></param>
public void Show3(int id, string name)
{
Console.WriteLine($"這裡是{this.GetType()}的Show3");
}
/// <summary>
/// 過載方法之二
/// </summary>
/// <param name="name"></param>
/// <param name="id"></param>
public void Show3(string name, int id)
{
Console.WriteLine($"這裡是{this.GetType()}的Show3_2");
}
/// <summary>
/// 過載方法之三
/// </summary>
/// <param name="id"></param>
public void Show3(int id)
{
Console.WriteLine($"這裡是{this.GetType()}的Show3_3");
}
/// <summary>
/// 過載方法之四
/// </summary>
/// <param name="name"></param>
public void Show3(string name)
{
Console.WriteLine($"這裡是{this.GetType()}的Show3_4");
}
/// <summary>
/// 過載方法之五
/// </summary>
public void Show3()
{
Console.WriteLine($"這裡是{this.GetType()}的Show3_1");
}
/// <summary>
/// 私有方法
/// </summary>
/// <param name="name"></param>
private void Show4(string name) //肯定是可以的
{
Console.WriteLine($"這裡是{this.GetType()}的Show4");
}
/// <summary>
/// 靜態方法
/// </summary>
/// <param name="name"></param>
public static void Show5(string name)
{
Console.WriteLine($"這裡是{typeof(ReflectionTest)}的Show5");
}
#endregion
}
}
泛型方法泛型類
using System;
namespace MyReflecttion
{
public class GenericMethod
{
public void Show<T, W, X>(T t, W w, X x)
{
Console.WriteLine($"t.type={t.GetType().Name},w.type={ w.GetType().Name},x.type={x.GetType().Name}");
}
}
public class GenericClass<T, W, X>
{
public void Show(T t, W w, X x)
{
Console.WriteLine($"t.type={t.GetType().Name},w.type={w.GetType().Name},x.type={x.GetType().Name}");
}
}
public class GenericDouble<T>
{
public void Show<W, X>(T t, W w, X x)
{
Console.WriteLine($"t.type={t.GetType().Name},w.type={w.GetType().Name},x.type={x.GetType().Name}");
}
}
}
1、建立物件
(1)呼叫無引數建構函式的
Assembly assembly = Assembly.LoadFrom("MyReflecttion.dll");
Type type = assembly.GetType("MyReflecttion.ReflectionTest");
object noParaObject = Activator.CreateInstance(type);
(2)呼叫有引數建構函式的
需要傳遞一個object型別的陣列作為引數,引數按照從昨往右嚴格匹配,如果沒有匹配的報異常
Assembly assembly = Assembly.LoadFrom("MyReflecttion.dll");
Type type = assembly.GetType("MyReflecttion.ReflectionTest");
object paraObject = Activator.CreateInstance(type, new object[] { 123 });
object paraObject1 = Activator.CreateInstance(type, new object[] { "三三" });
object paraObject2 = Activator.CreateInstance(type, new object[] { 234, "四四" });
object paraObject3 = Activator.CreateInstance(type, new object[] { "五五", 456 });
四、反射呼叫方法詳解
獲取方法MethodInfo,執行MethodInfo 的Invoke方法,傳遞方法所在的類的例項物件+引數
1、呼叫無引數的方法
Assembly assembly = Assembly.LoadFrom("MyReflecttion.dll");
Type type = assembly.GetType("MyReflecttion.ReflectionTest");
object oInstance = Activator.CreateInstance(type);
MethodInfo show1 = type.GetMethod("Show1");
show1.Invoke(oInstance, new object[] { });
show1.Invoke(oInstance, new object[0]);
show1.Invoke(oInstance, null);
2、呼叫有引數的方法
需要透過方法引數型別類區別方法,傳遞引數,嚴格匹配引數型別
Assembly assembly = Assembly.LoadFrom("MyReflecttion.dll");
Type type = assembly.GetType("MyReflecttion.ReflectionTest");
object oInstance = Activator.CreateInstance(type);
MethodInfo show2 = type.GetMethod("Show2");
show2.Invoke(oInstance, new object[] { 123 });
MethodInfo show31 = type.GetMethod("Show3", new Type[] { typeof(string), typeof(int) });
show31.Invoke(oInstance, new object[] { "一一一", 234 });
MethodInfo show32 = type.GetMethod("Show3", new Type[] { typeof(int) });
show32.Invoke(oInstance, new object[] { 345 });
MethodInfo show33 = type.GetMethod("Show3", new Type[] { typeof(string) });
show33.Invoke(oInstance, new object[] { "二二二" });
MethodInfo show34 = type.GetMethod("Show3", new Type[0]);
show34.Invoke(oInstance, null);
3、呼叫私有方法
在獲取方法的時候,加上引數BindingFlags.NonPublic | BindingFlags.Instance
Assembly assembly = Assembly.LoadFrom("MyReflecttion.dll");
Type type = assembly.GetType("MyReflecttion.ReflectionTest");
object oInstance = Activator.CreateInstance(type);
MethodInfo show4 = type.GetMethod("Show4", BindingFlags.NonPublic | BindingFlags.Instance);
show4.Invoke(oInstance, new object[] { "String" });
4、呼叫靜態方法
不需要建立物件也可以呼叫
Assembly assembly = Assembly.LoadFrom("MyReflecttion.dll");
Type type = assembly.GetType("MyReflecttion.ReflectionTest");
MethodInfo show5 = type.GetMethod("Show5");
show5.Invoke(null, new object[] { "String" });
5、呼叫普通類的泛型方法
獲取到泛型方法後需要先確定型別
Assembly assembly = Assembly.LoadFrom("MyReflecttion.dll");
Type type = assembly.GetType("MyReflecttion.GenericMethod");
object oInstance = Activator.CreateInstance(type);
MethodInfo show = type.GetMethod("Show");
//獲取到泛型方法後需要先確定型別
MethodInfo genericshow = show.MakeGenericMethod(new Type[] { typeof(int), typeof(string), typeof(DateTime) });
genericshow.Invoke(oInstance, new object[] { 123, "三三三", DateTime.Now });
6、呼叫泛型類的普通方法
Assembly assembly = Assembly.LoadFrom("MyReflecttion.dll");
//泛型類的型別需要在類後面加佔位符
Type type = assembly.GetType("MyReflecttion.GenericClass`3");
//泛型類獲取到型別後需要先確定型別
Type generType = type.MakeGenericType(new Type[] { typeof(int), typeof(string), typeof(DateTime) });
object oInstance = Activator.CreateInstance(generType);
MethodInfo show = generType.GetMethod("Show");
show.Invoke(oInstance, new object[] { 123, "四四四", DateTime.Now });
7、呼叫泛型類的泛型方法
Assembly assembly = Assembly.LoadFrom("MyReflecttion.dll");
//泛型類的型別需要在類後面加佔位符
Type type = assembly.GetType("MyReflecttion.GenericDouble`1");
//泛型類獲取到型別後需要先確定型別
Type generType = type.MakeGenericType(new Type[] { typeof(int) });
object oInstance = Activator.CreateInstance(generType);
MethodInfo show = generType.GetMethod("Show");
//獲取到泛型方法後需要先確定型別
MethodInfo genericMethod = show.MakeGenericMethod(new Type[] { typeof(string), typeof(DateTime) });
genericMethod.Invoke(oInstance, new object[] { 123, "五五五", DateTime.Now });
五、反射操作屬性欄位
- 普通方法呼叫屬性欄位簡單快捷,反射操作麻煩點。
- 類增加一個欄位呢,普通方法呼叫需要修改程式碼,重新編譯釋出,程式碼不穩定,反射賦值沒啥優勢,反射取值不需要修改程式碼,程式碼就更加穩定。
- type.GetProperties()獲取屬性,type.GetFields()獲取欄位。
1、建立測試類
using System;
namespace Business.DB.Model
{
/// <summary>
/// 實體---屬性是不能儲存資料,只有欄位才能儲存資料
/// </summary>
public class People
{
public People()
{
Console.WriteLine("{0}被建立", this.GetType().FullName);
}
public int Id { get; set; }//帶有Get Set 方法的叫做屬性
public string Name { get; set; }
public int Age { get; set; }
public string Description;//欄位
}
}
2、傳統方式賦值取值
//傳統手藝賦值取值
Console.WriteLine("***********傳統手藝賦值取值*************");
People people = new People();
people.Id = 134;
people.Name = "WWWW";
people.Age = 25;
people.Description = "XXXXX";
Console.WriteLine($"people.Id={people.Id}");
Console.WriteLine($"people.Name={people.Name}");
Console.WriteLine($"people.Age={people.Age}");
Console.WriteLine($"people.Description={people.Description}");
3、反射方式賦值取值
//反射方式賦值取值
Console.WriteLine("***********反射方式賦值取值*************");
Type type = typeof(People);
object pObject = Activator.CreateInstance(type);
foreach (var prop in type.GetProperties())
{
if (prop.Name.Equals("Id"))
{
prop.SetValue(pObject, 134);
}
else if (prop.Name.Equals("Name"))
{
prop.SetValue(pObject, "WWWW");
}
else if (prop.Name.Equals("Age"))
{
prop.SetValue(pObject, 25);
}
}
foreach (var prop in type.GetProperties())
{
Console.WriteLine($"people.{prop.Name}={prop.GetValue(pObject)}");
}
4、執行結果
***********傳統手藝賦值取值*************
Business.DB.Model.People被建立
people.Id=134
people.Name=WWWW
people.Age=25
people.Description=XXXX
***********反射方式賦值取值*************
Business.DB.Model.People被建立
people.Id=134
people.Name=WWWW
people.Age=25
六、反射的侷限/效能問題
1、效能對比程式碼實現
using Business.DB.Interface;
using Business.DB.SqlServer;
using System;
using System.Diagnostics;
using System.Reflection;
namespace MyReflecttion
{
public class Monitor
{
public static void Show()
{
Console.WriteLine("*******************Monitor*******************");
long commonTime = 0;
long reflectionTime = 0;
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1000_000; i++) //1000000000
{
IDBHelper iDBHelper = new SqlServerHelper();
iDBHelper.Query();
}
watch.Stop();
commonTime = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
//最佳化程式碼,載入dll放到迴圈外面
Assembly assembly = Assembly.Load("Business.DB.SqlServer");//1 動態載入
Type dbHelperType = assembly.GetType("Business.DB.SqlServer.SqlServerHelper");//2 獲取型別
for (int i = 0; i < 1000_000; i++)
{
//建立物件+方法呼叫
object oDBHelper = Activator.CreateInstance(dbHelperType);//3 建立物件
IDBHelper dbHelper = (IDBHelper)oDBHelper;//4 介面強制轉換
dbHelper.Query();//5 方法呼叫
}
watch.Stop();
reflectionTime = watch.ElapsedMilliseconds;
}
Console.WriteLine($"commonTime={commonTime} reflectionTime={reflectionTime}");
}
}
}
2、執行結果
- 測試用例:普通方式迴圈100000次,建立物件+方法呼叫:16毫秒
反射方式迴圈100000次,載入dll+建立物件+方法呼叫:6300毫秒 - 載入dll放到迴圈外面,建立物件+方法呼叫放迴圈裡面,泛型方法:57毫秒
3、使用反射的建議
反射確實有效能問題,但是差別沒有那麼大,在需要的地方可以放心使用