[翻译]关键字“dynamic”和“object”(及“var”)有什么不同?

来自CSharpFAQ的:What is the difference between “dynamic” and “object” keywords?

 

让我们快速看看object关键字先。我不会对它讲太多,因为它在C#1.0就已经有了。这个关键字没有更多东西除了作为System.Object的快捷方式,System.Object是C#类层次的根类型。(然而,正如Eric Lippert在他博客中指出,并非一切类型都继承源于object[中] [英])这是一个强大的机制,这样你几乎可以分配任何实例值到此类型。

这是个小例子用来演示使用object关键字的好处和问题。

object obj = 10;
Console.WriteLine(obj.GetType());
// 输出 System.Int32 因为
// 这是这个对象里存储的值的类型。

// 一个编译错误, 因为
// 在编译时obj的类型是System.Object。
// obj = obj + 10;

// 你需要显式转换obj到期望的类型。
obj = (int)obj + 10;

// 然而, 这样并不表示你真的安全。
// 你可能转换到一个错误的类型
// 而编译器并不会发现。
// 这里你会在运行时得到一个异常,
// 因为obj是int类型,不是string类型。
// obj = (string)obj + 10;

 
// 如果你将其转换到一个错误的数值类型,
// 你也会得到一个运行时异常
// 虽然有隐式转换的语法在这里。
// obj = (double)obj + 10;

你可以看到,尽管obj存储一个int值,如果没有转换,编译器仍然不会让你执行任何数学操作。它看起来好像是帮助你确定是否你真有一个int类型,但它不是。你可以转换到一个完全不同的类型但编译器却检查不出。结果便是,你得到一个运行时异常。

所以你不得不执行显示转换,这并不保证什么,仅仅因为如果不转换,编译器不会让你运行。

新的dynamic关键字来了。它告诉编译器不要强制附加规则在你的代码上。

dynamic dyn = 10;
Console.WriteLine(dyn.GetType());
// 跟"object"一样.
// 输出 System.Int32 因为
// 这是这个对象里存储的值的类型。

// 没有编译错误,因为
// 编译器不会在编译时
// 试图识别dynamic对象的类型。

dyn = dyn + 10;

// 同样,这个操作会在所有数值或
// 其他支持“+”操作符的类型上成功运算。
dyn = 10.0;
dyn = dyn + 10;

dyn = "10";
dyn = dyn + 10;

这是一个object和dynamic间的主要区别----用dynamic相当于你告诉编译器此对象的类型只在运行时确定,编译器不会试图干预。最终,你可以写更少的代码。但我要强调一下,相对于使用原有的object关键字,这样做不会增加任何危险。同样,也不会减少任何危险,所以当操作任何对象需要类型检查技术时(如反射),则使用dynamic对象会更好。

接下来通常会出现如下问题:“既然dynamic对象可以是任意对象并且编译器不会检查它是什么,这是否意味着你可以传递一个dynamic对象给我的信任方法/系统并使它崩溃?”

让我们假设拥有一个简单方法。

public static void Print(string arg)
{
    Console.WriteLine(arg);
}

现在我们看看你能怎么传递dynamic对象给它。

dynamic dyn = 10;

// 你将于运行时在这里得到一个异常.
Print(dyn);

你可以看见,虽然编译器允许你传递一个dynamic对象给你的方法,但你的方法永远不会得到这个对象如果它是个错误的类型。在方法实际执行之前已经有一个异常抛出。只有当它包含一个恰当值时,你才能传递dynamic对象给你的方法,在这里是string类型。

  dynamic dyn = "10";
  Print(dyn);
再次强调,这跟你使用object关键字相比不会有太多行为上的区别。

// 不会编译通过.
//Print(obj);

// 编译, 但这里在运行时会有个异常。
//Print((string)obj);

// 这里的代码会正常工作,因为现在obj是个string型,
// 但你不需要转换。

obj = "10";
Print((string)obj);

有人说(int)obj这种写法并不难理解,为什么要再来个dynamic呢?好吧,有些情况下你不得不执行较多的转换操作,这使得你的代码很难理解。还有些情况下简单的转换并不能达到目的,你需要调用反射方法,比如InvokeMember或GetProperties。一个很好的例子便是COM互操作,这就是为什么它要修改以使用新dynamic特性(更多信息点这里“how-to”)。

同时,用dynamic关键字和dynamic语言运行时让很多以前不可能实现或难以实现的方案变得可行,包括与动态语言互操作。我在之前的博客里强调过这两个情形: Introducing ExpandoObject Creating Wrappers with DynamicObject.

你可能想去MSDN walkthrough看看,它展示你该怎么从C#和Visual Basic调用IronPython库,一个真的很酷的介绍, Using Dynamic Languages to Build Scriptable Applications,作者Dino Viehland。另一个好的介绍展示一些例子并解释设计原则背后是这个特性Dynamic Binding in C# 4,作者Mads Torgersen。

结论是我们不需要担心会有人能用dynamic特性破坏你的代码。它没有更多(同时,也没有更少)的危险相对于object关键字。

所以,如果你经常使用object关键字并且不得不执行一堆转换"并且/或者"使用反射来调用对象的方法或属性,你可能应该留意一下dynamic关键字。在很多时候它比object更方便并且编写更少的代码。


译者添加章节:

原文没有提到var关键字,相信很多人会在这时想到var。
 
var在C#3.0中产生,其在MSDN中的定义:在方法范围中声明的变量可以具有隐式类型 var。隐式类型的本地变量是强类型变量(就好像您已经声明该类型一样),但由编译器确定类型。

使用var关键字申明变量,编译器会根据分配的实例类型确定此变量的类型,即类型推断。

var va = 10;
Console.WriteLine(va.GetType());
// 输出 System.Int32 因为
// 这是这个对象里存储的值的类型。
// 而va的编译时类型也已经被编译器确定为int型。

// 没有编译错误,因为
// va在第一句赋值时
// 便被编译器确定其类型是int。

va = va + 10;

// 编译错误,因为
// va变量已经确定是int型,而10.0是double型,
// 且double型向int型不可隐式转换。
// va = 10.0;
 

跟object比,var属于强类型;

跟dynamic比,var并无任何运行时的动态特性,仅仅是编译器在编译时反向推断其类型,同直接用其编译时类型声明变量功能上是一样的。它仅仅是编译器提供的语法糖。dynamic可不是语法糖,dynamic是需要运行时支持的。

 

posted @ 2010-01-30 00:49  甜番薯  阅读(1373)  评论(0编辑  收藏  举报