zoukankan      html  css  js  c++  java
  • 《深入理解C#》整理9-静态语言中的动态绑定

    一、何谓、何时、为何、如何

    1、何谓动态类型

    C#是一门静态类型语言。编译器知道代码中表达式的类型,知道任何类型中可用的成员。它应用了相当复杂的规则来决定哪个成员应该在何时使用。这包括了重载决策;在(动态类型出现)之前的唯一途径是根据对象在执行时的类型,来选择虚方法的实现。决定使用哪个成员的过程称为绑定(binding),对于静态类型的语言来说,绑定发生在编译时。而在动态类型的语言中,所有的绑定都发生在执行时。编译器或解析器可以检查语法是否正确,但却无法检查所调用的方法或所访问的属性是否真的存在。

    值得一提的是,C# 4全新的动态特性不包含在执行时解释C#源代码的功能。当然,有时候,无论采取什么方式,都能完成同样的工作。由于让编译器在执行前进行了更多的准备工作,因此静态系统的性能往往比动态系统更优。

    2、动态类型什么时候有用,为什么

    动态类型立足于两个有利要点:

    ①通常我们需要知道调用的成员名称、要传入的参数以及要调用的对象,但C#编译器通常需要得更多。至关重要的是,为了准确地确定成员(模型重载),我们需要知道所调用的对象的类型和参数的类型。我们有时无法在编译时知道这些类型,即使你确实能保证代码运行时成员会存在并且正确。

    ②动态类型的第二个重要的特性是,对象可以通过分析提供给它的名称和参数来响应某个调用。其行为就像是该类型正常地声明了成员一样,即使直到执行时我们才能知道成员的名称。

    使用动态语言进行编程还有一个特性,它往往是使用适当解释器进行编程的实验性风格。这一点并非与C# 4直接相关,但C# 4可以与运行在DLR(Dynamic Language Runtime,动态语言运行时)上的动态语言进行丰富的互操作,这意味着如果你要处理的问题可以从这种风格中受益,你就可以直接使用C#返回的结果,而不用后来再将其移植到C#中。

    3、C# 4如何提供动态类型

    C# 4引入了一个新的类型,称为dynamic。编译器对待该类型的方式与普通的CLR类型不同。任何使用了动态值的表达式都会从根本上改变编译器的行为。编译器不会试图弄懂代码的确切含义,不会恰当地绑定各个成员的访问,不会执行重载决策。它只是通过解析源代码,找出要执行的操作的种类、名称、所涉及的参数以及其他相关信息。编译器也不会发出(emit)IL来直接执行代码,而是使用所有必要的信息生成调用DLR的代码。剩下的工作将在执行时进行。

    当DLR在执行时绑定相关调用时,确定应该发生什么事情的过程非常复杂。在此期间,不仅仅要考虑方法重载等常规的C#规则,而且该对象本身也需要动态确定。

    二、关于动态的快速指南

    dynamic的主要规则如下:

    • 几乎所有CLR类型都可以隐式转换为dynamic
    • 所有dynamic类型的表达式都可以隐式转换为CLR类型
    • 使用dynamic类型值的表达式通常会动态地求值
    • 动态求值表达式的静态类型通常被视为dynamic

    三、幕后原理

    1、DLR简介

    DLR即为动态语言运行时(Dynamic Language Runtime),它是所有动态语言和C#编译器用来动态执行代码的库。它真的仅仅只是一个库。尽管它同样以运行时为名,却与CLR(Common Language Runtime,公共语言运行时)不在同一个级别——它不涉及JIT编译、本地API封送(marshal)、垃圾回收等内容。而是建立在大量.NET 2.0和.NET 3.5的功能之上,特别是DynamicMethod和Expression类型。

    尽管DLR不直接操作本地代码,但在某种程度上我们可以认为它做着与CLR类似的工作:正如CLR将IL(中间语言)转换为本地代码一样,DLR将用绑定器、调用点(call site)、元对象(metaobject),以及其他各种概念表示的代码转换为表达式树,后者可以被编译为IL,并最终由CLR编译为本地代码。如下图,DLR一个很重要的部分是多级缓存(multilevel cache)

    image-20201031143749735

    2、DLR核心概念

    DLR的目的可以非常笼统地概括为,基于执行时才能知道的信息以高级形式表示并执行代码。

    2.1、调用点

    即调用方法的地方,它有点像是DLR的原子——可以被视为单个执行单元的最小代码块。一个表达式可能包含多个调用点,但其行为是建立在固有方式之上的,即一次对一个调用点求值。调用点在代码中表示为一个System.Runtime.CompilerServices.CallSite

    2.2、接收器和绑定器

    除了调用点之外,我们还需要其他信息来判断代码的含义以及如何执行。在DLR中,有两个实体可以用来进行判断:接收器(receiver)和绑定器(binder)。调用的接收器是被调用的成员所在的对象。绑定器取决于调用语言,并且是调用点的一部分。C#特定的绑定器为Microsoft.CSharp.RuntimeBinder. Binder

    DLR总是将更高的优先级赋予接收器:如果动态类型知道如何处理调用,则将会使用该对象提供的执行路径。一个对象如果实现了新的IDynamicMetaObjectProvider接口,就具备了动态特性,他只包含一个成员:GetMetaObject,要正确实现GetMetaObject,需要借助于表达式树

    2.3、规则和缓存

    如何执行一个调用所作出的决策,称为规则(rule)。从根本上来说,它包含两个逻辑元素:调用点表现为这种行为时所处的环境以及行为本身。规则的第二部分是当规则匹配时所使用的代码,它表示为一个表达式树。它也可以存储为一个编译好的供调用的委托,但使用表达式树意味着可以对缓存进行深度优化。DLR中的缓存包含3个级别:L0、L1、L2。缓存以不同方式将信息存储于不同的作用域中。每个调用点包含自己的L0和L1缓存,而L2缓存可能被多个类似的调用点共享:

    image-20201031152652447

    共享L2缓存的调用点是由它们的绑定器决定的——每个绑定器都包含一个与之相关的L2缓存。编译器(或其他创建了调用点的东西)决定要使用多少个绑定器。它可以只对多个表示类似代码的调用点使用一个绑定器,如果执行时的上下文相同,这些调用点应该以相同的方式执行。事实上,C#编译器没有使用这个功能——它会为每个调用点都创建一个新的绑定器,因此对于C#开发者来说,L1和L2没有太大区别。但真正的动态语言,如IronRuby和IronPython都进一步使用了该功能

    C#编译器生成代码来简单地执行调用点的L0缓存(通过Target属性访问的委托)。L0缓存包含单一的规则,将在调用时进行检查。如果规则匹配,将执行相关的行为。如果不匹配(或如果为第一次调用,还没有任何规则),将调用L1缓存,继而调用L2缓存。如果L2缓存找不到任何匹配的规则,将要求接收器或绑定器来解决这个调用。其结果将被放入缓存供以后使用。

    L1和L2缓存以相当标准的方式审核它们的规则——每级缓存都包含一组规则,每条规则都会检查是否匹配。L0缓存则略有不同。行为的两个部分(检查规则和委派给L1缓存)将被合并为单独的方法,然后进行JIT编译。对L0缓存进行更新时将根据新的规则重新构建方法

    3、C#编译器如何处理动态

    在面对动态代码时,C#编译器的主要工作是解决什么时候需要动态行为,以及获取所有必需的上下文,这样绑定器和接收器在执行时就有足够的信息来处理调用

    ①如果使用了动态,那么它就是动态的。即如果调用的任何一部分为动态的,该调用即为动态的,并将以动态值的执行时类型来匹配重载;

    ②你不能将所有CLR类型都转换为object,CLR类型与dynamic之间对于转换的限制与此类似;

    ③动态表达式并不总是动态地求值,在某些情况下,CLR完全可以使用普通的静态求值路径对表达式进行求值,即使个别子表达式为动态的;

    ④动态求值的表达式并不总是动态类型

    ⑤创建调用点和绑定器

    4、更加智能的C#编译器

    C# 4能够让你跨越静态和动态的边界,不只是因为一些代码可以静态绑定,一些可以动态绑定,它还能够在一次绑定的过程中,将这两种概念相结合。它能记住调用点中任何需要知道的信息,然后在执行时与动态值的类型进行合并

    ①在执行时保存编译器行为:计算出绑定器行为的理想模式是,假设源代码中没有动态值,我们知道值的确切类型:即执行时实际值的类型。这仅适用于表达式中的动态值;任何在编译时知道的类型,都仍将用于查找,如成员决策;

    ②动态代码的编译时错误:动态类型的缺点之一,就是将一些通常在编译时可以检测到的错误,推迟到了执行时,进而抛出异常;

    5、动态代码的约束

    ①不能动态处理扩展方法,这是由于动态代码不知道调用所在的源文件中using指令到底引入了哪些命名空间。也就是说在执行时它不知道哪些扩展方法是可用的。这不仅意味着不能调用动态值的扩展方法,还意味着不能将动态值作为参数传入扩展方法。编译器推荐了两种变通方案。如果你知道使用哪个重载,可以在方法内将动态值转换为正确的类型。或者,假设你知道扩展方法所在的静态类型,可以像调用普通的静态方法那样进行调用。

    image-20201031155124230

    ②委托与动态类型之间转换的限制:在转换Lambda表达式、匿名方法或方法组时,编译器需要知道委托(或表达式)的确切类型。你不能不加转换就将它们设置为简单的Delegate或object变量。对于dynamic来说也是如此。强制转换可以满足编译器的要求。如果稍后要动态地执行该委托,那么这样做在某些情况下就非常有用。你还可以将动态类型作为委托的参数。

    有必要指出的是,LINQ与dynamic的交互不会导致任何损失。你可以拥有一个以dynamic为元素类型的强类型集合,你还可以使用扩展方法、Lambda表达式甚至查询表达式。该集合可以包含不同类型的对象,它们在执行时将表现出恰当的行为。

    ③构造函数和静态方法:你可以通过指定动态实参的方式来动态调用构造函数和静态方法,但你不能对一个动态类型调用构造函数或静态方法。因为你无法指定具体的类型。如果你遇到要使用这种动态的情况,可以考虑使用实例方法,例如创建工厂类型。你会发现可以使用简单的多态和接口获得动态行为,而不必使用静态类型。

    ④类型声明和泛型类型参数:你不能声明一个基类为dynamic的类型。你同样不能将dynamic用于类型参数的约束,或作为类型所实现的接口的一部分。你可以将其用于基类的类型实参,或在声明变量时将其用于接口

    四、实现动态行为

    C#语言没有为实现动态行为提供任何帮助,而.NET框架则不然。能够动态响应的类型必须实现IDynamicMetaObjectProvider,大多数情况下内嵌的两个实现都能完成大部分工作。我们将研究这两个类型,并介绍一个非常简单的IDynamicMetaObjectProvider实现,这三种方法互不相同

    1、使用ExpandoObject

    System.Dynamic.ExpandoObject只有一个无参的公共构造函数。除了各个接口的显式实现外,它没有公共方法。比较重要的接口为IDynamicMetaObject Provider和IDictionary<string,object>。(它实现的其他接口均为IDictionary<string, object>所扩展的接口。)它还是封闭的,所以不能继承它从而实现有用的行为。只有用dynamic引用或实现某个接口时,才能使用ExpandoObject。相关示例如下:

    image-20201031160900367

    2、使用DynamicObject

    DynamicObject与DLR的交互比ExpandoObject要更加强大,且比实现IDynamic MetaObjectProvider要简单得多。尽管它并不是一个真正的抽象类,但只有继承它才能做些有用的事情——而且唯一的构造函数还是受保护的,因此将其视为抽象类可能更加实际。你可能需要覆盖4类方法:

    • TryXXX()调用方法,表示对对象的动态调用;
    • GetDynamicMemberNames(),返回可用成员的列表;
    • 普通的Equals()、GetHashCode()和ToString()方法仍然可以照常覆盖;
    • GetMetaObject(),返回DLR使用的元对象。

    3、实现IDynamicMetaObjectProvider

    实现IDynamic MetaObjectProvider的难点并不是接口本身,而是构建该接口的唯一方法所返回的Dynamic MetaObject。DynamicMetaObject有点像DynamicObject,它包含很多方法,我们可以覆盖它们,并影响相应的行为。但在被覆盖的方法内,它不会直接处理所需的行为,而会构建一个表达式树来描述行为以及行为产生的环境。这种额外的间接层就是它称为元对象的原因。示例:

    无标题

    作者:Jscroop
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    ASP.NET动态生成控件
    WGCLOUD如何禁用指令下发功能
    C#基于NAudio工具对Wav音频文件剪切(限PCM格式)
    C#多线程与多任务
    我的JQuery插件 Confirmer
    我的JQuery插件 submenu
    关于jQuery在asp.net中使用ajax的探讨
    发布jQuery表单验证插件 JQuery.validator.js
    TreeView递归绑定地区列表
    Uploadify(JQuery上传插件)在asp.net中使用例子
  • 原文地址:https://www.cnblogs.com/Jscroop/p/13911652.html
Copyright © 2011-2022 走看看