深入理解C#中的匿名类型

C#编程语言的发展过程中,.NET 3.5的引入带来了许多新特性,其中之一就是匿名方法。匿名方法指的是没有名称的对象。在.NET 3.5之前,C#中已经有了匿名委托的概念。如果了解委托的内部机制,应该知道匿名委托是如何向CLR声明的。实际上,在MSIL中并没有匿名的概念。因此,所看到的匿名实际上是对一种抽象层次,它让避免了维护不必要的类型的复杂性。在这篇文章中,将讨论MSIL中的匿名类型。

基础概念

匿名类型是在它们被使用之前没有声明的类型。假设在编写程序时需要临时存储数据,要么声明一个具体的类来存储数据,要么使用如ArrayList、HashTable等集合来存储键值对。C#3.5及以后的版本允许动态创建一个类型并直接在程序中使用它。可以使用'var'或隐式类型声明来确保可以像使用普通类型一样使用属性。

var myruntimeObject = new { FirstProperty = 10, SecondProperty = new DateTime(), ThirdProperty = "string type" }; Console.WriteLine("Type of myruntimeObject is {0}", myruntimeObject.GetType().Name); Console.WriteLine("Type of FirstProperty is {0}", myruntimeObject.FirstProperty.GetType().Name); Console.WriteLine("Type of SecondProperty is {0}", myruntimeObject.SecondProperty.GetType().Name); Console.WriteLine("Type of ThirdProperty is {0}", myruntimeObject.ThirdProperty.GetType().Name); Console.Read();

在上面的例子中,使用'var'创建了一个匿名类型。'var'将给一个机会在代码中获得智能感知。可以看到变量持有一个在运行时生成的匿名类型的实例。代码中的'var'的强大之处也可以进行测试。只需将鼠标悬停在'var'关键字上,将看到该类型被定义为匿名。

在之前的代码中,在声明匿名对象之后,试图查看声明的对象的类型以及成员的确切样子。让看看下面的输出:

因此,CLR在运行时创建的实际类型看起来像<>f_AnnonymousType0`3。将在本文后面讨论它,但可能想知道的是,编译器足够智能地声明成员的类型。FirstProperty被声明为Int32,第二个为DateTime,第三个为String。类型在代码中自评估并相应声明。

现在,如果稍微改变一下值:

var myruntimeObject = new { FirstProperty = 10d, SecondProperty = new DateTime(), ThirdProperty = "string type" };

代码说FirstProperty是double。10d被评估为double值。也可以自由地声明其成员的匿名类型。

var myruntimeObject = new { FirstProperty = 10, SecondProperty = new DateTime(), ThirdProperty = "string type", MoreProperty = new { N1 = "" } };

在这里,MoreProperty隐式声明了另一个匿名类型。但是,匿名类型的使用有一个限制。不能在任何对象声明中声明方法,甚至没有办法为其成员提供访问性说明符。匿名类型中的所有成员都是只读属性,因此如果尝试:

myruntimeObject.FirstProperty = 20;

它将提示一个错误。

关于匿名类型的另一个重要事情是,编译器足够智能地优雅地使用匿名类型。如果下一个指令尝试创建完全相同的类,它不会创建一个新的匿名类型。因此,如果使用:

var myruntimeObject = new { FirstProperty = 10, SecondProperty = new DateTime(), ThirdProperty = "string type", MoreProperty = new { N1 = "" } }; var myruntimeObject2 = new { FirstProperty = 30, SecondProperty = new DateTime(), ThirdProperty = "string type2", MoreProperty = new { N1 = "blah blah" } };

两者在内部都表示相同的类型。

内部结构

现在,既然知道了匿名类型的基础,让深入研究它的内部结构。在IL方面,'var'和匿名类型什么都不是。'var'实际上被表示为实际类型,而匿名类型被映射到一个实际类型。

<>f__AnonymousType0<j__TPar> moreproperty = new <>f__AnonymousType0<j__TPar>(""); <>f__AnonymousType1<j__TPar, j__TPar, j__TPar, j__TPar> myruntimeObject = new <>f__AnonymousType1<j__TPar, j__TPar, j__TPar, j__TPar>(10, new DateTime(), "string type", moreproperty);

因此,对于类型来说,它似乎是一个相当大的名称,并且还包括一个名为j__TPar的泛型参数。这让对象在参数不同时引用相同的类型。由于反射API不足以显示实际的泛型类型,它显示了相同的数字表示。

正如看到的,编译器为MoreProperty生成的类型看起来像上面的例子。应该注意到的第一件事是类上方的DebuggerDisplayAttribute。有了这个属性,Visual Studio调试器只显示而不是整个类型名称。

Type采用泛型类型参数j__TPar>在其构造函数中构建类。应该注意到参数被标记为Readonly,所以它是不可写的。此外,该类型通过使用泛型EqualityComparer重写ToString、GetHashCode和Equals。这看起来像一个很好的实现。

最后,为了总结,必须说匿名类型是一个有趣的特性,它被广泛使用,并最终改变了编码方式。它在处理LINQ时非常有用。将在本系列的后面部分讨论LINQ的内部结构。但是,应该有一个规定来定义匿名类型中的方法,认为这将比预期更早地被添加。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485