技术开发 频道

C#3.0之匿名方法的实现与应用

 
【IT168 技术文档】前面的文章中,大家已经对C#3.0的新特性有了一个基本的了解,比如强大的LINQ语言和扩展方法的应用,今天给大家介绍的是C#3.0中添加的另一个重要的新特性:匿名方法。

1. 匿名溯源

    匿名的历史可谓由来已久,在C#2.0中匿名方法就已经大量使用在委托(delegate)的应用场景中。下面我举几个例子大家可以简单回顾一下:

1) 当我们需要调用一个回调方法时,不需要构建委托对象,只需要将回调方法名传入,CLR会替我们完成委托对象的创建工作。
//example 1 
public static void CallBackWithoutANewDelegateObject()
{
//这里QueueUserWorkItem方法需要一个委托作为参数,
//但我们仅仅传递给回调方法名,CLR可以自动为我们构造出委托对象的代码
ThreadPool.QueueUserWorkItem(SomeAsyncTask, 5);
}

private static void SomeAsyncTask(object o)
{
Console.WriteLine(o);
}
   这个例子中我们没有构造出任何委托对象,而仅仅传递了回调方法名称,CLR帮我构建了创建委托对象的代码。
   2)我们甚至不用显式定义回调方法,只需要使用delegate关键字内联的写出方法代码执行调用。
//example 2
public static void CallbackWithoutNewingADelegateObject()
{
//我们在这儿以内联形式写出回调方法体,而没有定义任何的回调方法
ThreadPool.QueueUserWorkItem(
delegate(object o) { Console.WriteLine(o); },
5);
}
   这个例子中我们并没有定义上面的方法SomeAsyncTask,而是内联写出方法体。

   3)我们甚至可以不写出调用方法的参数,CLR也会为我们生成正确的委托对象。
//example 3 
this.BT_LOGIN.Click +=
delegate { MessageBox.Show("Button Login has been clicked!"); };
    这是匿名方法最常见的使用场景,即在添加一个控件的事件处理函数时直接使用delegate写出方法代码而不需要另外定义方法函数。当我们的事件处理函数很简短时(比如上面的代码我们仅仅弹出一个对话框显示一条信息而已。)我们就可以使用这种方法,如果另外定义一个函数就会显得很累赘。而且方法的参数也可以省略(比如这里的Object sender, EventArgs e)

2. C#3.0中的匿名方法

1) 隐式类型变量 (Implicitly typed local variables)
var var1 = 1; 
var var2 = 2;
var var3 = var1 + var2;

var var4 = "I'm a string.";
    这里我们可以看到我们并没有指定变量的类型(int , string, …),但编译器会帮我们完成这一点。熟悉脚本语言的朋友们可能会对此次语法感到惊喜,但要注意的是,C#仍然是强类型的语言,所有类型都会在编译期确定,而不是像脚本那样等到运行时,下面这张图很清楚的说明了这一点:



    大家可以看到在VS编辑器的智能提示中,编译器已经找到了变量的实际类型。这个特性在结合LINQ语言进行数据查询时显得格外有用:

    比如我们现在有一个UserInfo类,它包含了一个用户的许多信息,姓名,年龄,住址等等。

class UserInfo 
{
public string firstname;
public string lastname;
public int age;
public string address;
//...
}
   我们现在要根据年龄对数据进行一些检索,但我们希望检索结果只需要包含用户的姓名就够了,也就是firstname,和lastname这两个字段。这时候,匿名方法就可以派上用场了。
var result = from userinfo in infoList 
where userinfo.age > 20 && userinfo.age < 35
select new { userinfo.firstname, userinfo.lastname };
    大家可以看到,我们并没有返回UserInfo的整个类型,而是返回了一个只包含firstname和lastname的数据类型,编译器能够自动为我们识别出result的类型。下面我们只需要一个foreach语句就可以把数据打印出来。
foreach (var var_info in result)
{
Console.WriteLine(var_info.firstname + " " + var_info.lastname);
}
    这里也同样用到了匿名类型(var var_info),下面这张图可以看出编译器可以识别出var_info的实际型别。



    有些文章在介绍C#3.0特性时会把它作为单独的特性,但我觉得这个特性也是属于编译器自动探测类型的范畴,所以仍然将它归类到匿名类型中来。
2) Lambda表达式

    前面提到在C#2.0中,我们用一个delegate关键字匿名地调用了一个委托方法,简化了程序员的工作,但同时我们也发觉程序的可读性降低了不少。下面我用一个例子来说明Lambda表达式是如何增强代码的可读性的。
还是以上面的UserInfo查询作为例子,下面的代码同样取出年龄在20-35之间的用户群。
var result1 = infoList.FindAll(p => (p.age > 20 && p.age < 35));
    这里仅仅只用了一行代码就完成了查询,是不是很神奇呢!我们来分析一下这句话的语义:首先是调用了infoList的FindAll方法,这个方法的原型如下:
// Summary: 
// Retrieves all the elements that match the conditions defined by the specified predicate.
//
// Parameters:
// match:
// The System.Predicate<T> delegate that defines the conditions of the elements
// to search for.
//
public List<T> FindAll(Predicate<T> match);
    由于篇幅原因,我删掉一部分注解,大家可以注意到它的参数Predicate<T>毫无疑问是一个委托类型,这就证明了我们前面所说的Lambda表达式确实是一个委托的简化。接着在参数中第一个字母是p,指示了我们返回的数据,这里编译器可以通过前面infoList的类型判断出p的类型来。下面是指示符”=>”,表示返回的数据集合要符合后面的查询条件。

   这里再举一个复杂一点的例子加以说明:
var groupBooksNumAndClicks = 
from book in books.Tbl_books
from type in books.Tbl_types
where book.Type_id == type.Id
group book by type.Name into g
select new { type = g.Key, booksSum = g.Count(), clicksSum = g.Sum(p => p.Clicks) };
    如果大家还有印象的话,这是我以前在介绍LINQ语言时写的一个数据库查询的例子,Tbl_books这张表包含了书籍的所有信息(书名,价格,等等),Tbl_types这张表包含了所有的书籍目录(比如数学类,计算机类等等), 我们要取出相同类别的书籍并计算每一类书籍总的点击量,有兴趣的朋友可以自行研究一下。

3. 匿名方法机理

   上面介绍了我们应当怎样使用匿名类型,下面我们通过阅读一些IL代码来看看编译器究竟为我们做了哪些工作。下面的代码演示了一个简单的匿名类型和匿名类型变量的调用,我们来看看编译器是怎么处理的。
namespace AnonymousTest 
{
class UserInfo
{
public string firstname;
public string lastname;
public int age;
public string address;
//...
}

class Program
{
static void Main(string[] args)
{
List<UserInfo> infoList = new List<UserInfo>();

var result = from userinfo in infoList
where userinfo.age > 20 && userinfo.age < 35
select new { userinfo.firstname, userinfo.lastname };

foreach (var var_info in result)
{
Console.WriteLine(var_info.firstname + " " + var_info.lastname);
}
}
}
}
    很简单的一段代码,首先我们定义了一个UserInfo类,其中定义了一些属性,firstname,lastname等等,这里为了简单,我们直接使用公有变量,而不使用属性来表示了。接下来就是我们的Program类,在这个类中我们定义了集合变量,由于这里仅仅为了做演示,所以实际上我并没与往集合中添加成员,但已足够让CLR生成它的结构然后我们定义了result变量去取得它的检索结果,注意我们返回的是一个匿名类型。接着我们使用freach循环将其打印出来。
    下面我们打开VS的命令行窗口,输入命令ildasm,在打开的窗口中,选择File->Open打开我们编译好的文件,大家会看到如下界面:


    大家可以看到,CLR在IL层实际上是为我们新建了一个特殊的类,其中包含了firstname, secondname的属性,当然与之对应的还定义了一些set,get方法,此外,CLR还为这个类额外重载了以下方法,像Equals,ToString等等。这样我们定义的一些匿名类型就能够被编译器所识别出来。

    以上只是对匿名类型做了一个简单的介绍,有兴趣的朋友们可以从两方面继续研究它,一方面是工程上的应用,一方面继续探索IL代码的密码,无论是哪一个方面,我想你都会有很大收获的。

相关文章:

    令人激动的“扩展—— c#3.0扩展方法的实现与应用
    http://tech.it168.com/msoft/2007-08-28/200708281720581.shtml

    C#3.0之LINQ数据库应用
    http://tech.it168.com/msoft/2007-08-20/200708200956437.shtml
0
相关文章