C#学习笔记——dynamic关键字

一、简介

动态类型是C# 4中引入的一种特殊类型,它可以让C#在编译时不检查对象调用了哪些操作。这表示可以绕过编译器的类型检查,并在运行时解析这些操作。对于与其他动态语言(如Python或JavaScript)或COM对象(如Microsoft Office 组件)进行互操作非常有用,也可以更方便地使用动态类型保存反射得到的对象,进而调用该对象上的方法、访问对象的属性。。

可以将C#的动态类型(dynamic),看作是一个不进行编译检查的Object类型,它可以保存任何类型的实力(尽管这个比喻不恰当,但是有助于让新手理解dynamic)。

二、动态类型的声明

要声明一个动态类型的变量,只需使用dynamic关键字,就像这样:

dynamic x = 10;

 上面的代码会告诉编译器,x是一个动态类型的变量,因此它不会对x上的任何操作进行类型安全检查。例如,你可以对x执行以下操作,而不会引发编译错误:

x = x + "Hello"; // x 现在是一个字符串
x = x.ToUpper(); // x 仍然是一个字符串
x = 8;  // x 现在保存的是一个int类型的变量
x = x * 8; // x现在仍然是int

如果尝试在一个非动态类型的变量上执行这些操作,就会出现编译错误,因为编译器会检查类型能否兼容以及对象成员是否存在。例如:

int y = 10;
y = y + "Hello"; // 错误:无法从字符串转换为int
y = y.ToUpper(); // 错误 由于 int 不是 string 类型,没有 ToUpper 方法
y = y * 8; // 正确,int支持 * 运算

 三、动态类型的行为

当你使用动态类型时,编译器会生成一些特殊的代码来处理运行时绑定。这表名当你在动态类型的变量上调用一个方法或访问一个属性时,编译器不会生成正常的IL代码来调用或访问它们,而是生成一些代码来查找并执行它们(相当于反射的过程)。这个过程称为动态语言运行时(DLR),它是.NET Framework中的一个组件,负责处理动态操作。

DLR使用一种称为调用站点的机制来缓存和重用动态操作。调用站点是一个包含有关如何执行特定操作的信息的对象。例如,当你在一个动态类型的变量上调用一个方法时,DLR会创建一个调用站点来存储该方法的名称、参数和返回类型等信息。然后,DLR会使用反射或其他技术来查找并执行该方法,并将结果返回给调用站点。倘若再次在相同类型的变量上调用相同名称的方法,DLR就可以重用之前创建的调用站点,而不需要再次查找和执行该方法。

使用DLR可以提高操作的灵活性,但也有一些缺点。首先,动态操作比静态操作要慢得多,因为它们需要额外的步骤来解析和执行。其次,动态操作可能会引发运行时异常,如果它们找不到合适的成员或参数不匹配。最后,动态操作会损失一些编译器提供的功能,如智能感知、重构和代码分析等。

四、动态类型与var

虽然dynamicvar关键字看起来很相似,但它们实际上完全不同。var关键字允许你在编写代码的时候省略变量的类型,并让编译器根据初始化表达式来推断它的真实类型。例如:

var x = 10; // x 是 int 类型
var y = "Hello"; // y 是 string 类型

但是,一旦编译器推断出变量的类型,它就会像普通变量一样对其进行类型检查。因此,你不能对var变量执行与其静态类型不兼容或不存在的操作。例如:

var x = 10; // x is an int
x = x + "Hello"; 错误,无法将 string 类型转换为 int 类型
x = x.ToUpper();  // 错误 int 没有 ToUpper 方法
x = x * 8; // 正确,int可以执行 * 运算

另一方面,dynamic关键字允许你完全忽略变量的静态类型,并在运行时解析其上的所有操作。因此,你可以对dynamic变量执行任何操作,而不管它们是否存在或有效。例如:

dynamic x = 10; // x 是 int 然而编译器会忽略类型检查
x = x * 8;  // 正确,作为 int 类型,执行 * 运算
x = x + "Hello"; // 也是正确的,任何类型与string相加,最终都会变成string,因为clr会调用其他非string对象的ToString方法,得到字符串后进行相加,因此,这时候 x被赋值为string类型的实力,此时x的值为"80Hello"
x = x.ToUpper(); // 正确,由于x现在保存的是string,string类型有ToUpper 方法

总之,var关键字只是一种语法糖,让你在编写代码的时候不必显式地指定变量的类型,在编译的时候会根据声明变量的赋值表达式的值,自动推断出真实类型,并将var关键字替换为真正的类型;而dynamic关键字是一种语言特性,让你可以在运行时执行任意操作。

五、动态类型与object

另一个与动态类型相似的类型是object类型。object是所有.NET类型(包括值类型和引用类型)的基类,因此你可以将任何值赋给一个object变量。例如:

object x = 10; // x 是一个包含int值的对象
object y = "Hello"; // y 是一个包含string引用的对象

但是,当你将一个值赋给一个object变量时,它会失去其原始类型信息,并被视为一个普通对象。这意味着你不能直接在一个object变量上调用或访问其原始类型的成员。例如:

object x = 10;
x = x.ToUpper(); // 编译错误:对象没有名为ToUpper的方法
x = x * 8; // 编译错误:object 并不能执行 * 运算

要在一个object变量上执行其原始类型的操作,你需要使用强制转换(casting)将其转换回原始类型。例如:

object x = 10; // x is an object that contains an int value
x = (int)x + "Hello"; // ok: (int)x converts the object back to an int, then concatenates with a string
x = ((string)x).ToUpper(); // ok: (string)x converts the object back to a string, then calls ToUpper method
x = (int)x * 2; // ok: (int)x converts the object back to an int, then multiplies by 2

强制转换可以让你恢复对象中包含的值的原始类型信息,并在其上执行相应的操作。但是,强制转换也有一些缺点。首先,强制转换可能会引发运行时异常,如果对象中包含的值与目标类型不兼容。其次,强制转换可能会影响性能和可读性,因为它们需要额外的代码和运行时检查。

与之相反,在动态类型中保留了对象中包含值或引用的原始类型信息,并且可以直接在其上执行任何操作而无需强制转换。例如:

dynamic x = 10; // x is an object that contains an int value, but also remembers its original type
x = x + "Hello"; // ok: no casting needed, the dynamic type knows how to handle this operation
x = x.ToUpper(); // ok: no casting needed, the dynamic type knows how to call this method
x = x * 2; // ok: no casting needed, the dynamic type knows how to perform this operation

总之,object是所有.NET类型共有的基类;而dynamic是一种特殊类型,它允许你在运行时执行任意操作。

六、动态类型保存值类型是否会发生装箱操作

值类型赋值给dynamic类型也会发生装箱操作,dynamic本质也是引用类型,它保存的是对object的引用,而非值本身。对于object类型来说,保存值类型,总是会发生装箱操作。例如,下面的代码中,y变量就是对x变量的值进行了装箱:

int x = 10; // 值类型
dynamic y = x; // 装箱操作

装箱是将值类型转换为object类型或由此值类型实现的任何接口类型的过程。当CLR对值类型进行装箱时,它会在托管堆中创建一个System.Object实例,并将值复制到新的对象中。取消装箱则是从对象中提取值类型的过程。取消装箱必须显式进行。

七、动态类型的用途

动态类型的一个主要用途是与其他动态语言进行互操作。动态语言是一种在运行时而不是编译时检查类型的语言,例如Python或JavaScript。这些语言通常使用一种称为动态类型系统的机制,它允许对象在运行时改变其类型和行为。例如,在Python中,你可以这样做:

x = 10 # x 是 int 类型
x = str(x) + "Hello" # 此时 x 是字符串类型,其值为 "10Hello"
x = x.upper()

这与C#中的静态类型系统相反,它要求对象在编译时具有确定的类型和行为,并且不能在运行时改变它们。例如,在C#中,你不能这样做:

int x = 10; // x 是 int
x = x + "Hello"; // 编译错误,不能将字符串转为int
x = x.ToUpper(); 编译错误:int没有ToUpper方法
x = x * 8; // 可以,因为int类型能执行 * 运算

由于动态语言和静态语言之间的类型系统不兼容,因此在.NET Framework中使用它们通常很困难。但是,使用动态类型,你可以轻松地与动态语言进行互操作,因为它们都使用DLR来处理运行时绑定。例如,你可以使用dynamic关键字来调用Python代码中定义的函数,就像下面的伪代码:

// 创建Python引擎和作用域
var engine = Python.CreateEngine();
var scope = engine.CreateScope();

// 执行Python代码来定义函数
engine.Execute(@"
def add(x, y):
    return x + y
", scope);

// 获取动态对象的函数
dynamic add = scope.GetVariable("add");

//使用动态参数调用函数并获得动态结果
dynamic result = add(10, 20);

// 输出结果应该是30
Console.WriteLine(result);

另一个动态类型的用途是与COM对象进行互操作。COM(Component Object Model)是一种允许不同应用程序之间交换数据和功能的技术。例如,Microsoft Office Automation就是一种使用COM对象来控制Office应用程序(如Word或Excel)的技术。在.NET Framework中,你可以使用互操作服务(Interop Services)来与COM对象进行通信,但这通常需要编写大量的样板代码和强制转换。例如,要创建一个Excel工作簿并添加一些数据,你需要这样做:

//创建一个Excel应用对象作为对象
object excelApp = new Excel.Application();

// 设置为可见
bool visible = true;
excelApp.GetType().InvokeMember("Visible", BindingFlags.SetProperty, null, excelApp, new object[] { visible });

// 添加一个新的工作部作为对象
object workbooks = excelApp.GetType().InvokeMember("Workbooks", BindingFlags.GetProperty, null, excelApp, null);
object workbook = workbooks.GetType().InvokeMember("Add", BindingFlags.InvokeMethod, null, workbooks, null);

// 获取激活的表单作为对象
object activeSheet = excelApp.GetType().InvokeMember("ActiveSheet", BindingFlags.GetProperty, null, excelApp, null);

// 获取一组单元格作为对象
object range = activeSheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null, activeSheet, new object[] { "A1", "C3" });

// 将范围作为对象数组设置
object[,] values = new object[3, 3] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
range.GetType().InvokeMember("Value", BindingFlags.SetProperty, null, range, new object[] { values });

使用动态类型,就可以简化这个过程,并直接调用或访问COM对象的成员,而无需任何样板代码或强制转换。例如:

// 用动态类型的方式创建excel对象
dynamic excelApp = new Excel.Application();

// 设置可见性为true
excelApp.Visible = true;

// 作为动态类型添加excel工作簿
dynamic workbook = excelApp.Workbooks.Add();

// 获取激活的表用作对象
dynamic activeSheet = excelApp.ActiveSheet;

// 获取作为动态对象的单元格范围
dynamic range = activeSheet.Range["A1", "C3"];

// 将范围的值设置为对象数组
range.Value = new object[3, 3] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };

总之,动态类型可以让你更容易地与其他动态语言或COM对象进行互操作,而不必过于担心类型系统的差异或复杂性。

八、动态类型的局限性

虽然动态类型很强大,也很实用,但它也有一些局限性和风险。以下是一些需要注意的事项:

  • (1) 动态类型会损失编译器提供的一些功能和保证,如智能感知、重构、代码分析、错误检测等。
  • (2) 动态类型会降低代码的可读性和可维护性,因为它们隐藏了变量的真实类型和行为。
  • (3) 动态类型会增加运行时异常的风险,如果它们找不到合适的成员或参数不匹配。
  • (4) 动态类型会影响代码的性能和效率,因为它们需要额外的步骤来解析和执行操作。

所以说,在使用动态类型时,应该权衡利弊,并尽量避免滥用它们。通常情况下,都应该优先使用静态类型来编写清晰、安全、高效的代码,并仅在必要时使用动态类型来处理特殊情况。

引用:一文带你全面了解C#中的动态编程(dynamic) - 知乎 (zhihu.com)
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值