• 重学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();
    }
    

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

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

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

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

    如果检查失败会报: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(@"
    		12");
    	Console.WriteLine(person.Descendants("FirstName").FirstOrDefault().Value);
    }
    

    那么如果有了动态类型怎么写呢?

    static void Main(string[] args)
    	{
    		//XElement person = XElement.Parse(@"
    		//    12");
    		//Console.WriteLine(person.Descendants("FirstName").FirstOrDefault().Value);
    		dynamic person = DynamicXml.Parse(@"
    			12");
    
    		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获取,如果失败就抛出异常。

    该系列持续更新。

  • 相关阅读:
    Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿
    十、视图解析原理与源码分析
    大二Web课程设计——海贼王中乔巴专题漫画(可以很好的应付老师的作业)HTML+CSS
    SVN 修改版本库地址url路径
    15 Docker容器存储架构:docker存储驱动简介
    【德哥说库系列】-ASM管理Oracle 19C单实例部署
    html超文本标记语言 (HyperText Mark up Language)
    ARM开发(5)ARM的接口技术(串行通信与并行通信,同步串行通信与异步串行通信,波特率,串行通信术语,uart,i2c,spi三种协议简单引入)
    原生js预览ofd文件
    react-antd组件 input输入框: 实现按回车搜索
  • 原文地址:https://www.cnblogs.com/aoximin/p/16897159.html