C#基础系列-dynamic
一、前言
dynamic是.NET4.0引入的新类型(静态类型),在编译器中会跳过编译阶段的静态类型(类、int、string、bool、委托等)检查,让dynamic定义类型具备object对象一样的能力。在编译阶段对于dynamic定义的对象假定支持任何操作,在代码运行阶段进行检查、绑定、执行,所以如果检测出代码无效则会在运行时捕获异常信息。
C#是静态语言,提供的是类型安全操作,在编译阶段会对类型进行严格的检查,要求类型符合定义的操作。对于什么是静态语言和动态语言,静态语言(强类型语言)是在编译时变量要明确类型的语言,要求变量使用之前必须声明数据类型如JAVA/C++/C#等;动态语言(弱类型语言)时在运行时确定数据的类型,变量使用之前不需要类型声明如Python/PHP/JS/Ruby等。所以对C#引入动态语言dynamic类型总结一下。
二、内容
动态语言运行时在平台中功能
由上述图所示在.NET中的动态编程是公共语言运行时+动态语言运行时来实现的。C#作为静态语言,通过表达式树+调用站点缓存+动态对象互操作性实现了动态语言到IL(中间语言),在CLR中编译成本机执行代码,执行程序。
通过以下关于动态语言相关的问题了解C#中动态类型的实现的原理、如何使用、在实际编程的功能与作用等。
1、引入动态语言,C#就是动态语言?C#一直就是静态语言,引入动态语言(dynamic)并没有改变其语言类型,只是引入动态语言的特性,使得C#在一定程度上支持动态语言的使用。因为动态语言特有的灵活性,在一些编程的场景中减少代码量和复杂度、减少类型转换,以一种特性看待其作用,如果不使用动态语言则要通过object和反射的机制来是实现。
2、如何在C#静态语言中实现这个动态特性?.NET4.0引入了动态语言运行时(DLR),在.NET中是公共语言运行时(CLR),在这个运行时上引入DLR接口(API)来支持动态类型。在代码编译阶段依据关键字,创建的动态类型编译器会跳过类型检查(无法提供智能提示),因为其不属于CLR类型。动态语言运行时提供C#编译器对动态语言动态执行的代码库,它不具备JIT、垃圾回收等功能,通过DLR提供的绑定器(binder)和调用点(callsite)、元对象把动态代码转换成表达树,然后把表达式树编译为IL(中间语言)代码,最后CLR编译为本地代码执行。总结一句话就是通过动态语言运行时(DLR)帮助C#编译器识别动态语言类型,在运行时使用特定方式转换成IL,编译本地代码,执行代码。
3、C#中动态语言有那些局限性?C#是静态语言,本身是不支持动态类型,使用特性的方式让其支持就产生一定局限,比如不能作为参数的扩展方法;委托和动态类型不能隐式转换;动态类型不能调用构造函数和静态方法;动态类型不能作为泛型参数的约束。
// 创建一个动态类型变量,赋予字符串值,使用字符串方法,输出字符串
using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AttributeDemo { class Program { static void Main(string[] args) { dynamic dy = "dynamic"; // 关键字创建动态类型 var str = dy.Substring(1); Console.WriteLine(str); Console.ReadKey(); } } }
// AttributeDemo.Program using AttributeDemo; using Microsoft.CSharp.RuntimeBinder; using System; using System.Runtime.CompilerServices; internal class Program { private static void Main(string[] args) { object arg = "dynamic";
// 创建用于调用Substring函数的调用点 if (<>o__0.<>p__0 == null) { <>o__0.<>p__0 = CallSite<Func<CallSite, object, int, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.None, "Substring", null, typeof(Program), new CSharpArgumentInfo[2] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.Constant, null) })); } object arg2 = <>o__0.<>p__0.Target(<>o__0.<>p__0, arg, 1);
// 创建用于动态转换成字符串输出的调用点 if (<>o__0.<>p__1 == null) { <>o__0.<>p__1 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "WriteLine", null, typeof(Program), new CSharpArgumentInfo[2] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) })); } <>o__0.<>p__1.Target(<>o__0.<>p__1, typeof(Console), arg2); Console.ReadKey(); } }
//编译器生成的内嵌类型为
[CompilerGenerated]
private static class <>o__0
{
// Fields
public static CallSite<Func<CallSite, object, int, object>> <>p__0;
public static CallSite<Action<CallSite, Type, object>> <>p__1;
}
上述代码的第一部分是C#语言通过关键字dynamic创建动态类型,然后使用字符串方法,输出字符串内容,第二部分是使用ILSpy反编译exe或者dll文件,通过反编译内容可以看到编译器对动态类型做的编译工作,生成内嵌类型、创建两个调用点(包含缓存)使用表达式树。
4、如何使类型具备动态的行为?使用ExpandObject、使用DynamicObject、实现IDynamicMetaObjectProvider接口。
using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AttributeDemo { class Program { static void Main(string[] args) { // 1、使用ExpandObject来创建动态类型 dynamic dyobj = new ExpandoObject(); //对象dyobj具备动态类型的行为 dyobj.Name = "dynamic";// 对动态对象添加数据 dyobj.Age = 20; dyobj.AddMethod = (Func<int, int>)(x => x + 1);// 动态对象添加方法 Console.WriteLine("Name:{0},Age:{1}", dyobj.Name, dyobj.Age); Console.WriteLine("1+1={0}",dyobj.AddMethod(1)); Console.ReadKey(); } } }
// 继承动态类型基类,使得类型创建的对象具备动态属性和行为,通过重写基类方法可以检测其动态类型的属性和方法的使用情况
using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AttributeDemo { class Program { static void Main(string[] args) { // 1、使用继承DynamicObject基类的类MyDynamicObject来创建动态类型 dynamic dyobj = new MyDynamicObject(); // 不能使用MyDynamicObject定义变量,其是经过编译器检查的。 dyobj.CallMethod(); dyobj.Name = "dynamic"; dyobj.Age = 24; Console.ReadKey(); } }
/// <summary> /// 继承动态行为基类(DynamicObject),重写方法执行和属性设置方法 /// </summary> public class MyDynamicObject : DynamicObject { public override bool TrySetMember(SetMemberBinder binder, object value) { Console.WriteLine(binder.Name + " 属性被设置," + "设置的值为: " + value); return true; } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { Console.WriteLine(binder.Name + " 方法正在被调用"); result = null; return true; } } }
// 使用继承IDynamicMetaObjectProvider接口的类MyDynamicObject来创建动态类型
using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace AttributeDemo { class Program { static void Main(string[] args) { // 1、使用继承IDynamicMetaObjectProvider接口的类MyDynamicObject来创建动态类型 dynamic dyobj = new MyDynamicObject(); dyobj.Name = "dynamic"; dyobj.Age = 20; dyobj.CallMethod(); Console.ReadKey(); } } public class MyDynamicObject : IDynamicMetaObjectProvider { public DynamicMetaObject GetMetaObject(Expression parameter) { Console.WriteLine("开始获得元数据......"); return new MyDynamicMetaObject(parameter, this); // return new DynamicMetaObject(parameter, BindingRestrictions.Empty); } } public class MyDynamicMetaObject : DynamicMetaObject { internal MyDynamicMetaObject(Expression expression, MyDynamicObject value) : base(expression, BindingRestrictions.Empty, value) { } // 重写响应成员调用方法 public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { // 获得真正的对象 MyDynamicObject target = (MyDynamicObject)base.Value; Expression self = Expression.Convert(base.Expression, typeof(MyDynamicObject)); var restrictions = BindingRestrictions.GetInstanceRestriction(self, target); // 输出绑定方法名 Console.WriteLine(binder.Name + " 方法被调用了"); return new DynamicMetaObject(self, restrictions); } public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { MyDynamicObject target = (MyDynamicObject)base.Value; Expression self = Expression.Convert(base.Expression, typeof(MyDynamicObject)); Console.WriteLine(binder.Name + " 属性被设置," + "设置的值为: " + value.Value); var restrictions = BindingRestrictions.GetInstanceRestriction(self, target); return new DynamicMetaObject(self, restrictions); //return base.BindSetMember(binder, value); } } }
5、关于动态语言运行时的三个核心概念:
<1>ExpressTree(表达式树),通过CLR运行时用抽象语法树(AST)生成代码并执行,并且通过表达式树与动态语言进行交互(javascript、Python)
<2>CallSite(调用点)创建动态调用站点的实例,使用负责此调用站点上动态操作的运行时绑定的联编程序进行初始化;动态类型调用一个方法,生成一个调用点且调用的都是静态函数,缓存下来。
<3>Binder(绑定器)使用调用点生成静态函数,通过绑定器确定方法如何调用,调用的方法等信息,绑定器同样可以缓存下来。
6、关于C#中Var、匿名对象、反射、动态类型区别与联系
<1>Var是C#中语法糖,在代码编译器编译阶段通过右侧的类型推断出左侧类型,在IL中会换成静态类型,这个关键字使用于Linq操作中,不确定右侧具体的类型(类型太复杂)的情况,带来编码上的便利。
<2>匿名对象在编译器编译时,通过匿名类型创建匿名对象,并且只是可以设置属性值,访问属性值。
<3>反射和动态类型相比,代码量多,没有缓存的机制,在性能不占优势。
三、总结
dynamic是特性的形式在静态语言中实现动态的行为,虽然其具备灵活性的特点,但是在明确的类型中还是使用静态类型,减少性能的损失。除非在特点的场景中动态语言带来减少代码量和复杂度、减少类型转换、针对无法确定类型。是否使用动态类型这就是代码实现的权衡问题了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?