匿名类型 vs 动态类型

46

匿名类型(var)和动态类型(dynamic)在C# 3.0和即将推出的C# 4.0之间有哪些真正的区别?


8
匿名类型和 var 关键字并不是同一回事。var 关键字可以随时指向任何类型,编译时会转换为真实的类型引用。var 关键字是语法糖,匿名类型是为你生成的类型。尽管它们经常同时使用,但并不相同。 - Jason Jackson
请查看 http://stackoverflow.com/questions/17991907/implicitly-typed-vs-anonymous-type 了解匿名类型。 - MSTdev
5个回答

107

您似乎在混淆三个完全不同的、正交的事情:

  • 静态 vs. 动态 类型检查
  • 显式 vs. 隐式 声明类型
  • 命名 vs. 匿名 类型

这三个方面是完全独立的,它们之间没有任何联系。

静态 vs. 动态 类型检查指的是类型检查发生的时间:动态类型检查发生在运行时,而静态类型检查则发生在运行前

显式 vs. 隐式 声明类型指的是类型是否在源代码中显式地声明出来:显式声明类型意味着程序员必须在源代码中写出类型,而隐式声明类型则表示类型系统会自动推断出类型。

命名 vs. 匿名 类型指的是类型是否具有名称。

C# 4.0中的dynamic关键字表示该变量、参数、方法、字段、属性等是动态类型,也就是其类型将在运行时进行检查。所有未使用dynamic关键字声明类型的都是静态类型。类型是静态还是动态不仅决定了类型检查发生的时间,在C# 4.0中,它还决定了方法分派发生的时间。在C#中,除运行时子类型多态性外,方法分派是在运行前根据静态类型进行的,而在C# 4.0中,对于动态类型的对象,方法分派是在运行时根据运行时类型进行的。

在C# 3.0中,var关键字意味着这个局部变量将会是隐式类型的。也就是说,程序员不需要显式地写出类型,类型系统会自行推断。这与动态类型无关,至少在C# 3.0中是这样。变量的类型与你显式写出类型时一样强烈地静态类型化。这只是一种方便:例如,当类型系统可以“清晰地”推断出foo是一个HashMap<int, string>时,为什么你还要在HashMap<int, string> foo = new HashMap<int, string>();中写两遍类型名称呢?因此你可以写成var foo = new HashMap<int, string>();。请注意,这并没有任何动态或匿名的特点。它的类型是静态的,并且有一个名称:HashMap<int, string>。当然,在C# 4.0中,如果类型系统确定赋值语句的右边是动态的,那么左边变量的类型就会是动态的。
在C# 3.0中,匿名类型指的是没有名称的类型。实际上,真正的匿名类型需要对公共类型系统进行不兼容的更改,所以编译器将会给类型生成一个非常长、非常随机、唯一的非法名称,并将该名称放在匿名类型出现的任何地方。但从程序员的角度来看,该类型没有名称。这有什么用处呢?有时你有一些中间结果,只需要短暂使用然后再抛弃掉。给这样的临时类型取一个自己的名称会使它们被提高到它们根本不应该有的重要性水平。但是,这也与动态无关。

那么,如果类型没有名称,程序员怎么引用它呢?好吧,她不能!至少不能直接引用。程序员可以做的是描述这个类型:它有两个属性,一个叫做“name”,类型为string,另一个叫做“id”,类型为int。那就是我想要的类型,但我不在乎它被称作什么。

这里是所有部分开始组合的地方。在C#中,你必须通过明确写下类型的名称来声明本地变量的类型。但是,你如何写下没有名字的类型的名称呢?这就是var的用处所在:因为自从C# 3.0以来,这个说法实际上已经不再正确了:你不再需要写下名称,你也可以让编译器来处理。因此,虽然我在上面的第一段中所写的是正确的,即隐式类型和匿名类型之间没有任何关系,但是匿名类型如果没有隐式类型将会非常无用。

但请注意,相反的情况并不成立:隐式类型在没有匿名类型的情况下仍然非常有用。var foo = HashMap<int, string> 是完全有意义的,并且看不到任何匿名类型。


1
太棒了!这正是我在看到这个问题时想要的信息,而你已经提供了。谢谢! - Animesh
“非常长、非常随机、独特而不合法”是描述基韦斯特杜瓦尔街的绝佳诠释。 - Jacob Stamm

22

匿名类型是由编译器为您创建的真实的、编译器生成的类型。其中好处在于,编译器可以在以后的操作中重用这个类型,因为它是一个POCO。

我对动态类型的理解是,它们是迟绑定的,这意味着CLR(或DLR)将在执行时评估对象,然后使用鸭子类型来允许或拒绝成员访问对象。

所以我猜区别在于匿名类型是编译器可见的真正的POCO,而动态类型是迟绑定的动态对象。


20

dynamic 类型本质上是 object,但它将通过 DLR 或其他提供程序(例如反射)在运行时解决所有方法/属性/运算符等调用。

这使其类似于开启了 Option Strict Off 的 VB,非常适合调用 COM 或 DLR 类型。

dynamic 没有编译时的类型检查;相反,匿名类型是真正的静态类型、经过类型检查的“野兽”(你可以在 Reflector 中看到它们,尽管它们不太好看)。

此外,匿名类型可以完全由编译器处理;dynamic 需要广泛的运行时支持,因此匿名类型是 C# 的一个特性,而 dynamic 将主要由 .NET 4.0 实现(带有一些 C# 4.0 的支持)。


7

这里有三个时间点,每个时间点都有一个角色。

  • 设计时间 - 程序员
  • 编译时间 - C# 编译器
  • 运行时间 - .NET 运行时

匿名类型是由编译器声明和命名的。这个声明基于程序员的规范(他如何使用该类型)。由于这些类型是在程序员离开过程后命名的,因此对程序员来说它们似乎没有名称,所以被称为“匿名”。

  • 程序员说:某个类型有名称和地址
  • 编译器说:有一个名为 xyz 的类型,具有名称和地址属性和字段,均为字符串。
  • 运行时说:我无法区分 xyz 和程序员创建的任何类型之间的区别。

C# 中的动态类型允许您调用可能存在或不存在于编译时的方法。这对于调用未经编译的 Python 或 JavaScript 很有用。

  • 程序员说:将这个汽车实例视为动态类型。现在,发出嘎嘎声。
  • 编译器说:动态类型?应该没问题。我不会抱怨,因为我无法检查它。
  • 运行时尝试让汽车实例发出嘎嘎声。

3

没有什么比写一些代码更能澄清问题的了:

// anonymous types
var anonType = new {Id = "123123123", Name = "Goku", Age = 30, DateAdded = new DateTime()};
// notice we have a strongly typed anonymous class we can access the properties with
Console.WriteLine($"Anonymous Type: {anonType.Id} {anonType.Name} {anonType.Age} {anonType.DateAdded}");
// compile time error
//anonType = 100;

// dynamic types
dynamic dynType = 100.01m;
Console.WriteLine($"Dynamic type: {dynType}");
// it's ok to change the type however you want
dynType = new List<DateTime>();
Console.WriteLine($"Dynamic type: {dynType}");

// mix dynamic and anonymous
dynamic dynamicAnonymousType = new {Id = 8000, FirstName = "Goku", Gender = "male", IsSuperSaiyan = true};
// Wasn't sure this would work but it does! However, you lose intellisense on the FirstName so you have to type it manually.
Console.WriteLine($"FirstName: {dynamicAnonymousType.FirstName}");
dynamicAnonymousType = 100;
Console.WriteLine(dynamicAnonymousType);
// runtime error
Console.WriteLine($"Id: {dynamicAnonymousType.FirstName}");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接