4

重学c#系列——动态类型[二十二] - 敖毛毛

 1 year ago
source link: https://www.cnblogs.com/aoximin/p/16897159.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

重学c#系列——动态类型[二十二]

该系列准备继续完善,一共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();
}

我们来反编译看一下到底是怎么样的?
可以看到是这样的:

先看反射这块,反射这块好理解哈:

1289794-20221119113909280-1033932979.png

然后动态类型这块,其实原理还是反射了,先检查类型是否存在然后去调用。

1289794-20221119114035406-1329081335.png

那么看来呢,我好想也没有找到动态类型跳过检查这一块。

如果检查失败会报:runtime-binder.runtimebinderException。

反射和动态类型不同的是,反射是运行时候生成的检查,而动态类型是编译的时候生成的,这个根据上面应该好理解吧。

1289794-20221119114932432-440114204.png
1289794-20221119114948146-236549450.png

也就是说反射是可以跳过检查的,但是动态类型不行。

只是说一下,个人觉得其实也没什么关系。因为其实不怎么影响效率。该用动态类型就用动态类型,该用反射就用反射,两种的方向不一致。

通过上面il编译后的,我们发现根本就没有这个dynamic 这个东西,会编译成别的东西,起到了包装器的作用。

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

当我们去运行它的时候获取类型的时候:

1289794-20221119115916461-609419118.png

这个a 本身还是system.string.

对il来说没有动态类型这个概念。

然后有一个值得注意的误区:

就是有些人认为使用了dynamic 认为不存在装箱和拆箱?

这是一个非常值得实验的问题。

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

为什么会这样呢? 因为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的还是调用我们自己的呢?

1289794-20221119133149306-1660899649.png

事实证明会调用自己的,而不走TryGetMember。

说明先进性反射,如果反射找不到,然后判断是否继承dynamicObject,如果继承那么就调用TryGetMember获取,如果失败就抛出异常。

该系列持续更新。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK