重學c#系列—— 反射深入一點點[三十三]

敖毛毛發表於2022-12-23

前言

在上一章中介紹了什麼是反射:

https://www.cnblogs.com/aoximin/p/16440966.html

正文

上一節講述反射的基本原理和為什麼要用反射,還用反射的優缺點這些。

其二者的本質是一致的,都是先獲取到type(後設資料)然後在進行建立例項。

下面那個好理解看下上面那個吧。

其實還是呼叫了activator:

說另外一個故事,是先有物件,後執行構造方法。還是先執行構造方法後有物件呢?到底是編譯行為還是執行行為呢?

其實先建立物件,然後再進行建構函式。

而一切初始化其實是在建構函式中。

比如:

public class Cat
{
	private string a = "100";
	public string b = "100";
	
	public Cat()
	{
		a = "200";
		b = "200";
	}
}

那麼其.ctor () 為:

計算機遠比我們想象的要簡單的多,就是開闢一塊空間,然後往裡面填充資料。 至於定義什麼型別,那屬於程式的自我管理。

有點扯遠了,那麼反射的實現也是一樣的。

在CreateInstance中:

兩者建立物件的原理基本是一致的,反射只是在上面增加了一層動態獲取型別(其中包括校驗和建立例項的程式碼生成)。

玩一個有趣的東西,既然我們說了,其實建立物件其實不一定要建構函式的。且我們上面知道了RuntimeTypeHandle 可以建立物件,那就直接搞事情。

static void Main(string[] args)
{
	Type catType = typeof(Cat);
	Type handleType = Type.GetType("System.RuntimeTypeHandle");
	var  obj = Activator.CreateInstance(handleType);
	var bind =  BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
	var  hiMethod = handleType.GetMethod("Allocate", bind);
	var   cat= hiMethod.Invoke(obj, new  object[]{catType});
	
	Console.ReadKey();
}

得到的結果為:

好吧,接著往下看:

internal class Program
{
	static void Main(string[] args)
	{
		var type1 = typeof(Cat);
		Cat cat = new Cat();
		var  type2 =cat.GetType();
		
		Assembly assembly = Assembly.GetExecutingAssembly();
		var type3 = assembly.GetType("ConsoleApp1.Cat");
		
		var type4 = typeof(Cat);

		Console.WriteLine($"{type1.GetHashCode()} {type1.GetHashCode()} {type3.GetHashCode()} {type4.GetHashCode()}");
		Console.ReadKey();
	}
	
	static (string name, int age, uint height) GetStudentInfo1()
	{
		return ("Bob", 28, 175);
	}
}

他們的type也是同一個type:

internal class Program
{
	static void Main(string[] args)
	{
		var type1 = typeof(Cat);
		Cat cat = new Cat();
		var  type2 =cat.GetType();
		
		Assembly assembly = Assembly.GetExecutingAssembly();
		var type3 = assembly.GetType("ConsoleApp1.Cat");
		
		var type4 = typeof(Cat);

		Console.WriteLine($"{type1.GetHashCode()} {type2.GetHashCode()} {type3.GetHashCode()} {type4.GetHashCode()}");
		Console.ReadKey();
	}
	
	static (string name, int age, uint height) GetStudentInfo1()
	{
		return ("Bob", 28, 175);
	}
}

值得注意的是typeof 屬於語法糖:

現在知道了Type 就包含我們類的後設資料了,那麼這些後設資料到底有哪些呢?

裡面包含了描述類的全部資訊,有名稱空間啊,屬性啊,方法啊。這些都是有的。

這些不用去記,用的時候找找看,都有的。

唯一說一個值得的注意的地方哈。

是這樣的。有一個BindingFlags這個列舉,可以看到是是一個多選列舉。

然後這樣寫:

static void Main(string[] args)
{
	var type1 = typeof(Cat);
	var filter = BindingFlags.Public;
	var members = type1.GetMembers(filter);
	
	Console.ReadKey();
}

你發現看不到,這是為什麼呢?

static void Main(string[] args)
{
	var type1 = typeof(Cat);
	var members = type1.GetMembers();
	
	Console.ReadKey();
}

來看下是什麼樣的。

可以看到,這個public string b,其實是public | instance 這樣的bindingflag,而不是public。

也就是說預設的是公共且可例項化的。 這個bindingflag的處理,不是或的關係,而是且的關係。

這裡也是大家使用多選列舉值得注意的地方,我們的業務上不僅可以用來做或也可以用來做且,它是多選的意思。

如果需要看下反射方法是怎麼呼叫的,可以去檢視:System.RuntimeMethodHandle的InvokeMethod,這裡面水比較深,選擇性觀看。

知道這個有什麼用呢? 因為在執行invoke的時候會經過很多判斷,如果是為了增加效能,可以直接呼叫System.RuntimeMethodHandle的InvokeMethod,一般不需要,只是說下最佳化手段。

例子

反射的常用手段,主要是一些例子。

獲取某個namespace 下面的type:

static void Main(string[] args)
{
Assembly assembly = Assembly.GetExecutingAssembly();
var types = assembly.GetTypes().Where(u => u.Namespace == "ConsoleApp1");

Console.ReadKey();

}

這個比較常用,一般來說會先載入出types,得到一個集合,然後進行管理。

有一個初學者容易犯的錯誤,就是認為:

認為這種是屬性,其實這個英文是property,是特性的意思,這在有些中文書上直接說成屬性,這是錯誤的。

然後:

這種是attribute。

上面這種是欄位,叫做field。

然後全部的這些,叫做member,都是成員:

這些概念是要區分開的。

第三個小栗子,如果獲取靜態的值:

static void Main(string[] args)
{
	Type type = typeof(Cat);
	var binding = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
	var  a= type.GetProperties(binding);

	Cat.Test = "123456";
	foreach (var item in a)
	{
		var  test= item.GetValue(null);
		Console.WriteLine($"test:{test}");
	}
	
	Console.ReadKey();
}

上面要說明的是GetValue,對於靜態可以填空。

我們講述到前面的建構函式,都是預設的建構函式。

static void Main(string[] args)
{
	Type type = typeof(Cat);
	var cat = Activator.CreateInstance(type);
	
	Console.ReadKey();
}

這在一般情況下,很難適應,一般我們的建構函式在寫一些複雜一點的時候,都會傳入引數的。

public Cat(string a, string b)
{
	this.a = a;
	this.b = b;
}

這樣的。那麼怎麼例項化呢?

static void Main(string[] args)
{
	Type type = typeof(Cat);
	var constructorInfo = type.GetConstructor(new  Type[]{ typeof(string), typeof(string) });
	var  cat =constructorInfo.Invoke(new object[] { "123", "456"});
	
	Console.ReadKey();
}

其他的方法也是這樣。

上一節講述了反射,這一節講了一下反射的大致的行為和一些簡單的例子。下一節可能是io流相關的,以前寫過Java的,其實兩者都一樣,考慮是否重新整合一下。或者直接開篇非同步篇。

相關文章