学习C#语言时,经常会遇到C#匿名类型对象问题,这里将介绍C#匿名类型对象问题的解决方法。
C#匿名类型对象
在很多情况下,我们需要一种能够临时将一批具有一定关联的数据存放起来的对象;或者在某些情况下,我们对仅一个对象的“形状”(如属性的名字和类型等)比较感兴趣。例如前面我们提到的Book,当它和其他商品放在一起进行查询时,我们可能仅对其名称和价格感兴趣,并且希望将这两种属性放在另外一个单独的临时对象中以备今后使用。这时,我们关注的仅仅是这个临时对象具有Name和Price的属性感兴趣,至于它究竟是什么类型就无关紧要了。然而,为了使这样一个对象得以存在,我们不得不为这个无关紧要的类型写上一大堆“样本代码”,无非就是定义一个如BookAsGood的类,其中无非也就是形如 m_name和m_price的私有域和名为Name与Price的公共可读写方法。
而在C# 3.0中,我们无须为这些无关紧要的类型浪费时间。通过使用“匿名类型”,只要在需要一个这样的对象时使用没有类型名字的new表达式,并用前面提到的对象初始化器进行初始化即可。如:
- var b1 = new { Name = "The First Sample Book", Price = 88.0f };
- var b2 = new { Price = 25.0f, Name = "The Second Sample Book" };
- var b3 = new { Name = "The Third Sample Book", Price = 35.00f };
- Console.WriteLine(b1.GetType());
- Console.WriteLine(b2.GetType());
- Console.WriteLine(b3.GetType());
首先,前面三行声明并初始化了三个具有C#匿名类型对象,它们都将具有公共可读写属性Name和Price。我们可以看到,匿名类型的属性连类型都省掉了,完全是由编译器根据相应属性的初始化表达式推断出来的。这三行称作“C#匿名类型对象初始化器”,编译器在遇到这样的语句时,首先会创建一个具有内部名称的类型(所谓的“匿名”只是源代码层面上的匿名,在最终编译得到的元数据中还是会有这样一个名字的),这个类型拥有两个可读写属性,同时有两个私有域用来存放属性值;然后,和对待对象初始化器一样,编译器产生对象声明代码,并依次为每个属性赋值。
上面代码的最后三行用来检验匿名类型在运行时的类型,如果尝试编译并运行上述代码,会得到类似下面的输出:
- lover_P.CSharp3Samples.Ex03.Program+<Projection>f__0
- lover_P.CSharp3Samples.Ex03.Program+<Projection>f__1
- lover_P.CSharp3Samples.Ex03.Program+<Projection>f__0
这表明编译器的确为C#匿名类型对象创建了实际的类型,并且该类型在代码中是不可访问的,因为类型的名字不符合C#语言命名规则(其中出现了+、<、>等非法字符)。
另外,我们还发现一个有趣的现象,由于b1和b2在初始化的时候其属性的顺序和推断出来的类型完全一致,它们的运行时类型也是一样的;而b2因为属性出现的顺序不同于另外两个对象,因此具有不同的运行时类型。通过下面的代码,我们可以验证这一事实:
- // 正确的赋值,b1和b3具有相同的类型
- b1 = b3;
- // 错误的赋值,b1和b2的类型不同
- b1 = b2;
- //如果尝试编译这段代码,对于第二个赋值我们会得到一条编译错误
- Cannot implicitly convert type ’lover_P.CSharp3Samples.Ex03.Program.
- <Projection>f__1’ to ’lover_P.CSharp3Samples.Ex03.Program.
- <Projection>f__0’。
这实际上是C# 3.0编译器固有的特性,在同一个程序集中,编译器将为属性出现顺序和类型完全相同的C#匿名类型对象生成唯一的一个类型。而一旦属性的出现顺序或类型有所不同,编译器就会生成不同的类型。另外,在两个程序集之中,即使属性出现的顺序和类型一致,编译器也可能会生成不同的类型,因此具有C#匿名类型对象是不能跨程序集访问的。
【编辑推荐】