1.什么是元數據(MetaData)和反射(reflection)
一般情況下我們的程序都在處理數據的讀、寫、操作和展示。但是有些程序操作的數據不是數字、文本、圖片,而是程序和程序類型本身的信息。
①元數據是包含程序以及類型信息的數據,它保存在程序的程序集當中。
②程序在運行的時候,可以查看其他程序集或者其本身的元數據。這個行為就是反射。
2.Type類
BCL聲明了一個Type類型(它是抽象類),用來包含類型的特性。使用這個類的對象能讓我們獲取程序使用的類型的信息。
由於Type是抽象類,所以它不能被實例化。而是在運行時,CLR創建從Type(RuntimeType)派生的類型的實例。當我們要訪問這些實例的時候,CLR不會返回派生類的引用而是返回Type基類的引用。
關於Type有如下重要的點:
①對於程序每一個需要用到的類型,CLR會穿件一個包含這個類型信息的Type類型的對象(真實的是上面說的派生的類型的實例)。
②程序中用到的每一個類型都會關聯到獨立的Type類的對兩個象。
③無論創建的類型有多少個實例,只有一個Type對象會關聯到所有這些實例。就像下面的圖表示的一樣。創建了一個OtherClass的實例oc、以及兩個MyClass的實例mc1和mc2,但是在堆上都只會有一個Type對象來的對應他們,如下面的圖示:
簡單看一下Type這個類型,里面可以看到如下的一些方法和屬性。
官方文檔更全面哦: https://docs.microsoft.com/zh-cn/dotnet/api/system.type?view=netframework-4.8
3.學習如何獲取一個Type類對象
方法一:通過GetType方法
object類型包含了一個GetType方法,它可以用來返回事例的Type對象引用。由於所有的類都是繼承自object類型,所以所有的類都可以調用GetType來獲得Type類型對象的引用。下面的圖很好的說明了,基類、派生類和object之間的關系
所以下面的代碼,在遍歷派生類的Field的時候才能,把基類的也輸出出來。
//基類 class BaseClass { public int BaseField = 0; } //派生類 class DerivedClass : BaseClass { public int DerivedField = 0; } class Program { static void Main(string[] args) { var bc = new BaseClass(); var dc = new DerivedClass(); BaseClass[] bca = new BaseClass[] { bc, dc }; foreach(var v in bca) { //獲取類型 Type t = v.GetType(); Console.WriteLine("Object Type: {0}", t.Name); //獲取類中的字段 FieldInfo[] fi = t.GetFields(); foreach (var f in fi) Console.WriteLine(" Field:{0}", f.Name); Console.WriteLine(); } Console.WriteLine("End!"); Console.ReadKey(); } }
結果:
Object Type: BaseClass
Field:BaseField
Object Type: DerivedClass
Field:DerivedField
Field:BaseField
End!
方法二:還可以通過typeof()方法來獲取一個類型的Type對象引用。例如下面的代碼:
Type t = typeof(DerivedClass);
此外我們可以根據程序集來獲取程序集內的類型
//通過程序集獲取類型 var baseType = Assembly.GetExecutingAssembly().GetType("TestDemo.BaseClass"); var derivedType = Assembly.GetExecutingAssembly().GetType("TestDemo.DerivedClass");
4.常用的操作
結合GetType和typeof操作,可以做很多事情。
①獲取數組類型
static void Main(string[] args) { var intArray = typeof(int).MakeArrayType(); var int3Array = typeof(int).MakeArrayType(3); Console.WriteLine($"是否是int 數組 intArray == typeof(int[]) :{intArray == typeof(int[]) }"); Console.WriteLine($"是否是int 3維數組 intArray3 == typeof(int[]) :{int3Array == typeof(int[]) }"); Console.WriteLine($"是否是int 3維數組 intArray3 == typeof(int[,,]):{int3Array == typeof(int[,,]) }"); //數組元素的類型 Type elementType = intArray.GetElementType(); Type elementType2 = int3Array.GetElementType(); Console.WriteLine($"{intArray}類型元素類型:{elementType }"); Console.WriteLine($"{int3Array}類型元素類型:{elementType2 }"); //獲取數組的維數 var rank = int3Array.GetArrayRank(); Console.WriteLine($"{int3Array}類型維數:{rank }"); Console.ReadKey(); }
如上面的例子,
MakeArrayType() 可以用來獲取數組類型,有一個參數是數組的維數
GetElementType() 可以用來獲取數組元素的類型
GetArrayRank() 可以獲取數組的維數
②獲取嵌套類型
public class Class { public class Student { public string Name { get; set; } } } class Program { static void Main(string[] args) { #region 嵌套類型 var classType = typeof(Class); foreach (var t in classType.GetNestedTypes()) { Console.WriteLine($"NestedType ={t}"); //獲取一個值,該值指示 System.Type 是否聲明為公共類型。 Console.WriteLine($"{t}訪問 {t.IsPublic}"); //獲取一個值,通過該值指示類是否是嵌套的並且聲明為公共的。 Console.WriteLine($"{t}訪問 {t.IsNestedPublic}"); } Console.ReadKey(); #endregion } }
輸出:
NestedType =TestDemo.Class+Student TestDemo.Class+Student訪問 False TestDemo.Class+Student訪問 True
③獲取類型名稱
Type里面具有NameSpace、Name和FullName屬性。一般FullName是兩者的組合。但是對於嵌套類型和封閉式泛型不成立。可以參考下面的demo
static void Main(string[] args) { #region 獲取名稱 var type = typeof(Class); Console.WriteLine($"\n------------一般類型-------------"); PrintTypeName(type); //嵌套類型 Console.WriteLine($"\n------------嵌套類型-------------"); foreach (var t in type.GetNestedTypes()) { PrintTypeName(t); } var type2 = typeof(Dictionary<,>); //非封閉式泛型 var type3 = typeof(Dictionary<string, int>); //封閉式泛型 Console.WriteLine($"\n------------非封閉式泛型-------------"); PrintTypeName(type2); Console.WriteLine($"\n------------封閉式泛型-------------"); PrintTypeName(type3); Console.ReadKey(); #endregion } private static void PrintTypeName(Type t) { Console.WriteLine($"NameSpace: {t.Namespace}"); Console.WriteLine($"Name :{t.Name}"); Console.WriteLine($"FullName: {t.FullName}"); }
結果:
------------一般類型------------- NameSpace: TestDemo Name :Class FullName: TestDemo.Class ------------嵌套類型------------- NameSpace: TestDemo Name :Student FullName: TestDemo.Class+Student NameSpace: TestDemo Name :Teacher FullName: TestDemo.Class+Teacher ------------非封閉式泛型------------- NameSpace: System.Collections.Generic Name :Dictionary`2 FullName: System.Collections.Generic.Dictionary`2 ------------封閉式泛型------------- NameSpace: System.Collections.Generic Name :Dictionary`2 FullName: System.Collections.Generic.Dictionary`2[ [System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089], [System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
④獲取基類類型和接口類型
var base1 = typeof(System.String).BaseType; var base2 = typeof(System.IO.FileStream).BaseType; var base3 = typeof(DerivedClass).BaseType; Console.WriteLine($"base1 :{base1.Name}"); Console.WriteLine($"base2 :{base2.Name}"); Console.WriteLine($"base3 :{base3.Name}"); foreach (var iType in typeof(Guid).GetInterfaces()) { Console.WriteLine($"iType :{iType.Name}"); }
輸出:
base1 :Object base2 :Stream base3 :BaseClass iType :IFormattable iType :IComparable iType :IComparable`1 iType :IEquatable`1
此外Type還有兩個方法:
我們在判斷某個實例對象是否是某個類型的時候,經常使用 is語句。
Type中的方法 IsInstanceOfType 其實和is是等價的。
var baseClassObject = new BaseClass(); var check1 = baseClassObject is BaseClass; var check2 = base3.IsInstanceOfType(baseClassObject); Console.WriteLine($"使用is判斷類型是否相同 :{check1}"); //結果True Console.WriteLine($"使用IsInstanceOfType類型是否相同 :{check2 }"); //結果True
返回結果都是True的。
還有一個是 IsAssignableFrom ,它的作用是確定指定類型的實例是否可以分配給當前類型的實例。
var base4 = typeof(BaseClass); //baseClass的實例 var baseClassObject = new BaseClass(); var derivedClassObject = new DerivedClass(); var classObject = new Class(); var checkResult1 = base4.IsAssignableFrom(baseClassObject.GetType()); //判斷BaseClass類型是否可以分配給BassClass類型 var checkResult2 = base4.IsAssignableFrom(derivedClassObject.GetType()); //判斷DerivedClass類型是否可以分配給BassClass類型 var checkResult3 = base4.IsAssignableFrom(classObject.GetType()); //判斷Class類型是否可以分配給BassClass類型 Console.WriteLine($"使用IsAssignableFrom類型是否和接受的類型一致 :{checkResult1}"); //True Console.WriteLine($"使用IsAssignableFrom類型是否和接受的類型一致 :{checkResult2}"); //True Console.WriteLine($"使用IsAssignableFrom類型是否和接受的類型一致 :{checkResult3}"); //False
⑤實例化類型
I. 有兩種方法可以動態的實例化類型。
方法一 通過靜態的 Activator.CreateInstance()方法創建,它有多個重載函數。
var dateTime1 = (DateTime)Activator.CreateInstance(typeof(DateTime),2019,6,19); var dateTime2 = (DateTime)Activator.CreateInstance(typeof(DateTime), 2019,6,19,10,10,10); Console.WriteLine($"DateTime1: {dateTime1}"); //DateTime1: 2019/6/19 0:00:00 Console.WriteLine($"DateTime2: {dateTime2}"); //DateTime2: 2019/6/19 10:10:10
一般我們像上面一樣都是傳一個Type和構造函數的參數。當不存在這個構造函數的時候,就會拋出錯誤。
方法二 調用ConstructInfo對象上面的Invoke方法,ConstructInfo對象是通過調用類型(高級環境)上的GetConstructor方法獲取的。
先分析一下場景,例如我有下面這樣的一個類型:
public class InvokeClass { private string _testString; private long _testInt; public InvokeClass(string abc) { _testString = abc; } public InvokeClass(StringBuilder abc) { _testString = abc.ToString(); } public InvokeClass(string abc,long def) { _testString = abc; _testInt = def; } }
存在兩個構造函數,一個傳入的是string類型,一個傳入的是StringBuilder類型,此時如果我通過new 的方式去創建一個對象,並傳入構造函數為null,那么就是報出下面的錯誤:說明存在二義性,也就是說找不到對應使用哪個來構造。
同樣的,如果我使用方法一 Activator.CreateInstance 去創建對象,會出現下面的問題:找不到對應的構造函數。
但是采用ConstructInfo的方式就可以指定對應的構造函數了。類似如下代碼
//找到一個參數為string的構造函數 var constructorInfo = typeof(InvokeClass).GetConstructor(new[] { typeof(string)}); //使用該構造函數傳入一個null參數 var obj4 = (InvokeClass)constructorInfo.Invoke(new object[] { null });
還可以結合查詢來找到對應的構造函數
//獲取所有的構造函數 var constructorInfoArray = typeof(InvokeClass).GetConstructors(); //過濾一次,獲取所有兩個參數的構造函數 var constructorInfoArray2 = Array.FindAll(constructorInfoArray, x => x.GetParameters().Length == 2); //最后找的第二個參數是long類型的構造函數 var constructorInfo2 = Array.Find(constructorInfoArray2, x => x.GetParameters()[1].ParameterType == typeof(long)); //如果存在,就創建對象 if (constructorInfo2 != null) { var obj5 = (InvokeClass)constructorInfo2.Invoke(new object[] { "abc", 123 }); }
動態構造對象的缺點就是慢,簡單對比一下,采用反射和new創建100萬個對象,耗時對比還是比較明顯的。
var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100000; i++) { var obj3 = (InvokeClass)Activator.CreateInstance(typeof(InvokeClass), "abc", 123); } sw.Stop(); Console.WriteLine($"時間:{sw.ElapsedMilliseconds}ms"); var sw2 = new Stopwatch(); sw2.Start(); for (int i = 0; i < 100000; i++) { var obj = new InvokeClass("abc", 123); } sw2.Stop(); Console.WriteLine($"時間:{sw2.ElapsedMilliseconds}ms");
輸出:
時間:280ms
時間:1ms
II. 實例化委托
動態創建靜態方法和實例方法的委托傳入的參數不太一樣,使用的是CreateDelegate的重載,可以參考下面的例子
/// <summary> /// 創建指定類型的委托,該委托表示要對指定的類實例調用的指定實例方法。 /// </summary> /// <param name="type">要創建的委托的 System.Type</param> /// <param name="target"> 類實例,對其調用 method</param> /// <param name="method">委托要表示的實例方法的名稱</param> /// <returns></returns> public static Delegate CreateDelegate(Type type, object target, string method); /// <summary> /// 創建指定類型的委托,該委托表示指定類的指定靜態方法。 /// </summary> /// <param name="type">要創建的委托的 System.Type</param> /// <param name="target"> 表示實現 method 的類的 System.Type</param> /// <param name="method"> 委托要表示的靜態方法的名稱。</param> /// <returns></returns> public static Delegate CreateDelegate(Type type, Type target, string method);
例如:
class Program { public static int StaticSum(int a, int b) { return a + b; } public int InstanceSum(int a, int b) { return a + b; } //創建一個委托 delegate int delegateOperate(int a, int b); static void Main(string[] args) { #region 實例化委托 //靜態方法的委托 Delegate staticD = Delegate.CreateDelegate(typeof(delegateOperate), typeof(Program), "StaticSum"); //實例方法的委托 Delegate instanceD = Delegate.CreateDelegate(typeof(delegateOperate), new Program(), "InstanceSum"); Console.WriteLine($"staticD:{staticD.DynamicInvoke(1,2)}"); Console.WriteLine($"instanceD:{instanceD.DynamicInvoke(10,20)}"); #endregion Console.ReadKey(); } }
III.范型的實例化
泛型分為封閉型和未封閉型,對於封閉類型的泛型是可以通過反射進行實例化的,而未封閉的泛型不能實例化。如下圖所示:
封閉式的泛型和未綁定的泛型是可以相互轉換的。
①未綁定的泛型可以通過 MakeGenericType 變成封閉的
②封閉的可以通過GetGenericTypeDefinition 獲取未綁定的類型。
class Program { static void Main(string[] args) { Type closed = typeof(List<int>); Type unBound = typeof(List<>); //轉換 var newClosed = unBound.MakeGenericType(typeof(int)); var newUnBound = closed.GetGenericTypeDefinition(); Console.WriteLine($"List<int> 類型{closed}"); Console.WriteLine($"List<> 類型{unBound}"); Console.WriteLine($"List<> MakeGenericType執行后 類型{newClosed}"); Console.WriteLine($"List<int> GetGenericTypeDefinition執行后 類型{newUnBound}"); } }
參考: 《C#圖解教程》、《果殼中的C#》