重學c#系列——動態型別[二十二]

2022-11-19 15:02:03

前言

該系列準備繼續完善,一共108篇,持續更新。

正文

為什麼有動態型別呢?

是因為很多東西天生就是動態型別的。

比如xml 和 json、cvs、資料庫表,這些本來就是資料型別的。

在反射系列中提及到,為什麼有傳送呢? 是因為只有在執行的時候你才能知道他是什麼型別。

同樣的xml、json這些也是隻有執行的時候才知道他有什麼樣的型別,當我們載入xml、json 對映成一個物件的時候,裡面的屬性也只有載入完後我們的程式才知道。

那這樣就有一個問題啊,那就是載入的時候我們的程式才知道有這個屬性,那麼這種沒法處理了嗎?

這個時候人就介入了,比如json開發人員是知道有一個欄位叫做fistname的,那麼使用動態型別,可以是dynamicObj.fistname 這樣獲取就可以了。

動態獲取屬性的值,這似乎反射也可以做到啊。是的啊,當然反射可以做到,那有沒有可能dynamic 就是反射獲取欄位值的語法糖呢?

這樣就可以了:

static void Main(string[] args)
{
	var type = typeof(String);
	var chars = new char[] { '1','2','3','4','5','6'};
	object x = Activator.CreateInstance(type, chars);
	PropertyInfo property = type.GetProperty("Length");
	var y = property.GetValue(x);
	dynamic a = "123456";
	var b = a.Length;
	var c = "123456";
	var d = c.Length;
	Console.ReadKey();
}

我們來反編譯看一下到底是怎麼樣的?
可以看到是這樣的:

先看反射這塊,反射這塊好理解哈:

然後動態型別這塊,其實原理還是反射了,先檢查型別是否存在然後去呼叫。

那麼看來呢,我好想也沒有找到動態型別跳過檢查這一塊。

如果檢查失敗會報:runtime-binder.runtimebinderException。

反射和動態型別不同的是,反射是執行時候生成的檢查,而動態型別是編譯的時候生成的,這個根據上面應該好理解吧。

也就是說反射是可以跳過檢查的,但是動態型別不行。

只是說一下,個人覺得其實也沒什麼關係。因為其實不怎麼影響效率。該用動態型別就用動態型別,該用反射就用反射,兩種的方向不一致。

通過上面il編譯後的,我們發現根本就沒有這個dynamic 這個東西,會編譯成別的東西,起到了包裝器的作用。

static void Main(string[] args)
{
	dynamic a = "123456";
	var t = a.GetType();
	Console.WriteLine(t);
	Console.ReadKey();
}

當我們去執行它的時候獲取型別的時候:

這個a 本身還是system.string.

對il來說沒有動態型別這個概念。

然後有一個值得注意的誤區:

就是有些人認為使用了dynamic 認為不存在裝箱和拆箱?

這是一個非常值得實驗的問題。

internal class Program
{
	static void Main(string[] args)
	{
		dynamic a = 1;
		Console.WriteLine(a);
	}
}

為什麼會這樣呢? 因為dynamic 繼承obejct,實際上可以理解為object,int 到 object 自然存在裝箱。

實際上dynamic 十分簡化原理是這樣的:

object a = "test";
var t = typeof(x);
callSite b = factory.create(t);
var reuslt = b.invoke(a, "param")

然後動態型別是不支援呼叫擴充套件方法的,這個反射也不支援。這個後面講到擴充套件方法的本質的時候,就知道為什麼不支援了。

動態型別的場景:

如果不使用動態型別是這樣的:

static void Main(string[] args)
{
	XElement person = XElement.Parse(@"<Person>
		<FirstName>1</FirstName><LastName>2</LastName></Person>");
	Console.WriteLine(person.Descendants("FirstName").FirstOrDefault().Value);
}

那麼如果有了動態型別怎麼寫呢?

static void Main(string[] args)
	{
		//XElement person = XElement.Parse(@"<Person>
		//    <FirstName>1</FirstName><LastName>2</LastName></Person>");
		//Console.WriteLine(person.Descendants("FirstName").FirstOrDefault().Value);
		dynamic person = DynamicXml.Parse(@"<Person>
			<FirstName>1</FirstName><LastName>2</LastName></Person>");

		Console.WriteLine(person.FirstName);
	}
}

public class DynamicXml : DynamicObject
{
	private XElement Element { get; set; }

	public DynamicXml(
		XElement element)
	{
		Element = element;
	}

	public static DynamicXml Parse(string text)
	{
		return new DynamicXml(XElement.Parse(text));
	}

	public override bool TryGetMember(GetMemberBinder binder, out object result)
	{
		bool success = false;
		result = null;
		XElement firstDescendant = Element.Descendants(binder.Name).FirstOrDefault();
		if (firstDescendant != null)
		{
			if (firstDescendant.Descendants().Any())
			{
				result = new DynamicXml(firstDescendant);
			}
			else
			{
				result = firstDescendant.Value;
			}

			success = true;
		}

		return success;
	}

	public override bool TrySetMember(SetMemberBinder binder, object value)
	{
		bool success = false;
		XElement firstDescendant = Element.Descendants(binder.Name).FirstOrDefault();
		if (firstDescendant != null)
		{
			if (value.GetType() == typeof(XElement))
			{
				firstDescendant.ReplaceWith(value);
			}
			else
			{
				firstDescendant.Value = value.ToString();
			}

			success = true;
		}

		return success;
	}
}

如果定義了自己的動態型別那麼更加方便。

因為本來我們就要傳遞字串的,如果字串傳遞錯誤本來就會報錯,封裝成一個動態類,從工程的角度來說就是更加優雅了。

然後又一個問題,那就是本身如果我們的 DynamicXml 就有這個屬性呢?是呼叫TryGetMember的還是呼叫我們自己的呢?

事實證明會呼叫自己的,而不走TryGetMember。

說明先進性反射,如果反射找不到,然後判斷是否繼承dynamicObject,如果繼承那麼就呼叫TryGetMember獲取,如果失敗就丟擲異常。

該系列持續更新。