匿名类型(var)和动态类型(dynamic)在C# 3.0和即将推出的C# 4.0之间有哪些真正的区别?
匿名类型(var)和动态类型(dynamic)在C# 3.0和即将推出的C# 4.0之间有哪些真正的区别?
您似乎在混淆三个完全不同的、正交的事情:
这三个方面是完全独立的,它们之间没有任何联系。
静态 vs. 动态 类型检查指的是类型检查发生的时间:动态类型检查发生在运行时,而静态类型检查则发生在运行前。
显式 vs. 隐式 声明类型指的是类型是否在源代码中显式地声明出来:显式声明类型意味着程序员必须在源代码中写出类型,而隐式声明类型则表示类型系统会自动推断出类型。
命名 vs. 匿名 类型指的是类型是否具有名称。
C# 4.0
中的dynamic
关键字表示该变量、参数、方法、字段、属性等是动态类型,也就是其类型将在运行时进行检查。所有未使用dynamic
关键字声明类型的都是静态类型。类型是静态还是动态不仅决定了类型检查发生的时间,在C# 4.0中,它还决定了方法分派发生的时间。在C#中,除运行时子类型多态性外,方法分派是在运行前根据静态类型进行的,而在C# 4.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中,如果类型系统确定赋值语句的右边是动态的,那么左边变量的类型就会是动态的。那么,如果类型没有名称,程序员怎么引用它呢?好吧,她不能!至少不能直接引用。程序员可以做的是描述这个类型:它有两个属性,一个叫做“name”,类型为string
,另一个叫做“id”,类型为int
。那就是我想要的类型,但我不在乎它被称作什么。
这里是所有部分开始组合的地方。在C#中,你必须通过明确写下类型的名称来声明本地变量的类型。但是,你如何写下没有名字的类型的名称呢?这就是var
的用处所在:因为自从C# 3.0以来,这个说法实际上已经不再正确了:你不再需要写下名称,你也可以让编译器来处理。因此,虽然我在上面的第一段中所写的是正确的,即隐式类型和匿名类型之间没有任何关系,但是匿名类型如果没有隐式类型将会非常无用。
但请注意,相反的情况并不成立:隐式类型在没有匿名类型的情况下仍然非常有用。var foo = HashMap<int, string>
是完全有意义的,并且看不到任何匿名类型。
匿名类型是由编译器为您创建的真实的、编译器生成的类型。其中好处在于,编译器可以在以后的操作中重用这个类型,因为它是一个POCO。
我对动态类型的理解是,它们是迟绑定的,这意味着CLR(或DLR)将在执行时评估对象,然后使用鸭子类型来允许或拒绝成员访问对象。
所以我猜区别在于匿名类型是编译器可见的真正的POCO,而动态类型是迟绑定的动态对象。
dynamic
类型本质上是 object
,但它将通过 DLR 或其他提供程序(例如反射)在运行时解决所有方法/属性/运算符等调用。
这使其类似于开启了 Option Strict Off
的 VB,非常适合调用 COM 或 DLR 类型。
dynamic
没有编译时的类型检查;相反,匿名类型是真正的静态类型、经过类型检查的“野兽”(你可以在 Reflector 中看到它们,尽管它们不太好看)。
此外,匿名类型可以完全由编译器处理;dynamic
需要广泛的运行时支持,因此匿名类型是 C# 的一个特性,而 dynamic
将主要由 .NET 4.0 实现(带有一些 C# 4.0 的支持)。
这里有三个时间点,每个时间点都有一个角色。
匿名类型是由编译器声明和命名的。这个声明基于程序员的规范(他如何使用该类型)。由于这些类型是在程序员离开过程后命名的,因此对程序员来说它们似乎没有名称,所以被称为“匿名”。
C# 中的动态类型允许您调用可能存在或不存在于编译时的方法。这对于调用未经编译的 Python 或 JavaScript 很有用。
没有什么比写一些代码更能澄清问题的了:
// 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}");