heartstill

博客园 首页 新随笔 联系 订阅 管理

1.3.3 "匿名"的对象类型

2010-10-30 08:13 金旭亮 电子工业出版社 我要评论(0) 字号:T | T
一键收藏,随时查看,分享好友!

《.NET4.0面向对象编程漫谈(基础篇)》第1章.NET面向对象编程基础,本章主要向读者介绍.NET面向对象编程的基础知识,前两节介绍.NET是什么、怎样基于.NET平台开发应用程序、.NET程序的运行机理等内容,其目的是帮助读者形成一个对.NET平台的总体认识。本节为大家介绍"匿名"的对象类型。

AD:

1.3.3  "匿名"的对象类型

在C# 3.0以上版本中,我们可以使用var关键字定义一种奇怪的"没有类型"的变量(称为"隐式类型的局部变量"):

  1. var Sum = 100;  
  2. Console.WriteLine(Sum * 2);   //输出:200 

貌似CLR很聪明地可以动态推断出Sum是一个int类型的变量。

事实上,是C#编译器而不是CLR完成了类型推断的工作,C#编译器根据赋的值"100"推断Sum是一个int类型的变量,就直接生成了将常量"100"赋值给它的IL代码,CLR仅仅只是机械地执行罢了。

当使用var定义隐式类型的局部变量时,必须保证编译器能推断得出变量类型,否则,不能通过编译。

var只能用于方法内部定义局部变量,不能定义为类的字段。例如,以下代码将无法通过编译:

  1. class A  
  2. {  
  3. var Value=100;  

之所以在这里介绍var关键字,是因为我们可以利用它实现"不定义类而直接创建一个对象"的目的。

请看以下代码(示例程序UseAnonymousType):

  1. var v = new { Amount = 108Message = "Hello" }; 

上述代码创建了一个匿名类型对象v,它拥有两个字段:Amount为int型,而Message为string型。

匿名类型对象的用法与普通对象没有什么区别:

  1. Console.WriteLine("Amount:{0}, Message:{1}", v.Amount, v.Message); 

上述代码输出的结果是:

  1. Amount:108, Message:Hello 

等一下,这个例子好像违背了面向对象编程的基本原则了,没有定义类怎么就可以创建对象呢?

其实一切都是C#编译器在后面玩的魔术。

使用ildasm工具查看示例程序生成的程序集(UseAnonymousType.exe),可以看到有一个名字非常奇怪的类型生成(见图1-13)。

 
图1-13  C#编译器为匿名对象生成的"匿名类型"

再打开Main方法对应的IL代码,一切都真相大白。

原来C#编译器动态创建了一个类型(它的名字如图 1 13所示),它的构造函数包括Amount和Message两个参数。Main方法中的变量v被设置为此类型的局部变量。

Main方法先使用"108"和"Hello"作为实参调用此类型的构造函数创建对象,然后让变量v引用这个创建好了的对象。

读取v对象两个字段Amount和Message的值,是通过直接调用匿名类型所定义的get_Amount方法和get_Message方法实现的。

所以,C#的匿名类型特性并未违反"先定义好类再创建对象"这一面向对象编程原则。

对于匿名类型对象比较有趣的是我们可以写出这样的代码:

  1. var v = new { Amount = 108Message = "Hello" };  
  2. Console.WriteLine(v); 

上述代码输出:

  1. Amount = 108Message = Hello } 

这一运行结果引发出一连串的问题:

1)这里输出的结果是从哪儿来的?

2)Console.WriteLine怎么可以直接接收一个临时创建出来的匿名类型对象v?它怎么知道这个对象有几个字段?

如果读者掌握了面向对象的基础知识,并且会使用ildasm和Reflector这两个工具,那么解释这个现象一点也不难。

首先,从Main方法生成的IL代码中可以知道,"Console.WriteLine(v);"实际上调用的是Console.WriteLine方法的以下重载 形式:

  1. public static void WriteLine(object value); 

现在使用Reflector工具查看Console.WriteLine(object)方法的实现代码,会发现上述方法在内部调用参数value的ToString方法生成一个字符串,然后再输出此字符串。

现在回到ildasm,找到C#编译器生成的匿名类型中的ToString方法,打开它,看看里面的IL代码,就知道为何示例会得到那样的结果了,这个工作留给读者作为练习。

现在剩余最后两个问题,涉及C#为何要引入这样"隐晦"的语法特性:

1)为什么不直接指定数据类型而要使用隐式类型来定义变量?

2)匿名类型的对象到底有什么实际用途?

回答是:

隐式类型变量和匿名类型主要用于LINQ 中。

请看以下LINQ to SQL 代码:

  1. //从SQL Server数据库中提取产品信息  
  2. var productQuery =  
  3.  
  4. from prod in products  
  5. select new { prod.Color, prod.Price };  
  6. //显示找到的产品信息  
  7. foreach (var v in productQuery)  
  8. Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price); 

上述代码使用了匿名类型对象来生成数据库查询的结果。隐式类型的局部变量productQuery的真实类型为"IEnumerable<编译器自动生成的自定义匿名类型>",如果不使用C#的隐式类型变量和匿名类型特性,编写同样功能的代码会变得比较麻烦--因为现在必须"显式"定义一个用于封装查询结果(Color和Price)的类型。

posted on 2011-10-27 22:55  开始测试  阅读(294)  评论(0编辑  收藏  举报