`
leonardleonard
  • 浏览: 799250 次
社区版块
存档分类
最新评论

C#2.0匿名函数

阅读更多
C# 2.0中提供了通过delegate实现匿名函数功能,能有效地减少用户记代码工作,例如


以下为引用:

...
button1.Click += new EventHandler(button1_Click);
...
void button1_Click(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...




可以被简化为直接使用匿名函数构造,如


以下为引用:

...
button1.Click += delegate(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...




关于匿名函数的使用方法可以参考Jeffrey Richter的Working with Delegates Made Easier with C# 2.0一文。简要说来就是C#编译器自动将匿名函数代码转移到一个自动命名函数中,将原来需要用户手工完成的工作自动完成。例如构造一个私有静态函数,如


以下为引用:

class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(delegate(Object obj) { Console.WriteLine(obj); }, 5);
}
}




被编译器自动转换为


以下为引用:

class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(new WaitCallback(__AnonymousMethod$00000002), 5);
}

private static void __AnonymousMethod$00000002(Object obj) {
Console.WriteLine(obj);
}
}




而这里自动生成的函数是否为static,编译器根据使用此函数的地方是否static决定。这也是为什么C# 2.0规范里面禁止使用goto, break和continue语句从一个匿名方法里跳出,或从外面跳入其中的原因,因为他们代码虽然写在一个作用域里面,但实际上实现上并不在一起。
更方便的是编译器可以根据匿名函数使用的情况,自动判断函数参数,无需用户在定义时指定,如


以下为引用:

button1.Click += delegate(Object sender, EventArgs e) { MessageBox.Show("The Button was clicked!"); };




在不使用参数时,完全等价于


以下为引用:

button1.Click += delegate { MessageBox.Show("The Button was clicked!"); };





相对于匿名函数的实现来说,比较复杂的是匿名函数对于其父作用域中变量的使用及其实现。MS的Grant Ri在其blog上有一系列的讨论文章。
Anonymous Methods, Part 1 of ?
Anonymous Methods, Part 2 of ?
Anonymous Method Part 2 answers

需要解决的问题有两个:一是不在一个变量作用域中的匿名函数如何访问父函数和类的变量;二是匿名函数使用到的变量的生命周期必须与其绑定,而不能与父函数的调用生命周期绑定。这两个问题使得C#编译器选择较为复杂的独立类封装方式实现匿名函数和相关变量生命周期的管理。

首先,匿名函数使用到的父函数中局部变量,无聊是引用类型还是值类型,都必须从栈变量转换为堆变量,以便在其作用域外的匿名函数实现代码可以访问并控制生命周期。因为栈变量的生命周期与其所有者函数是一致的,所有者函数退出后,其堆栈自动恢复到调用函数前,也就无法完成变量生命周期与函数调用生命周期的解耦。
例如下面这个简单的匿名函数中,使用了父函数的局部变量,虽然此匿名函数只在父函数里面使用,但C#编译器还是使用独立类对其使用到的变量进行了包装。


以下为引用:

delegate void Delegate1();

public void Method1()
{
int i=0;

Delegate1 d1 = delegate() { i++; };

d1();
}




自动生成的包装代码类似如下


以下为引用:

delegate void Delegate1();

private sealed class __LocalsDisplayClass$00000002
{
public int i;

public void __AnonymousMethod$00000001()
{
this.i++;
}
};

public void Method1()
{
__LocalsDisplayClass$00000002 local1 = new __LocalsDisplayClass$00000002();
local1.i = 0;

Delegate1 d1 = new Delegate1(local1.__AnonymousMethod$00000001);

d1();
}




但对于有多个局部变量作用域的情况就比较复杂了,例如Grant Ri在其例子中给出的代码


以下为引用:

delegate void NoArgs();

void SomeMethod()
{
NoArgs [] methods = new NoArgs[10];
int outer = 0;
for (int i = 0; i < 10; i++)
{
int inner = i;
methods[i] = delegate {
Console.WriteLine("outer = {0}", outer++);
Console.WriteLine("i = {0}", i);
Console.WriteLine("inner = {0}", ++inner);
};
methods[i]();
}
for (int j = 0; j < methods.Length; j++)
methods[j]();
}




就需要一个类封装变量outer;一个类封装变量i;另外一个类封装inner和匿名函数,并引用前面两个封装类的实例。因为变量outer、i和inner有着不同的作用域,呵呵。伪代码如下:


以下为引用:

private sealed class __LocalsDisplayClass$00000008
{
public int outer;

};
private sealed class __LocalsDisplayClass$0000000a
{
public int i;

};
private sealed class __LocalsDisplayClass$0000000c
{
public int inner;

public __LocalsDisplayClass$00000008 $locals$00000009;
public __LocalsDisplayClass$0000000a $locals$0000000b;

public void __AnonymousMethod$00000007()
{
Console.WriteLine("outer = {0}", this.$locals$00000009.outer++);
Console.WriteLine("i = {0}", this.$locals$0000000b.i);
Console.WriteLine("inner = {0}", ++this.inner);
}
};

public void SomeMethod()
{
NoArgs [] methods = new NoArgs[10];

__LocalsDisplayClass$00000008 local1 = new __LocalsDisplayClass$00000008();
local1.outer = 0;

__LocalsDisplayClass$0000000a local2 = new __LocalsDisplayClass$0000000a();
local2.i = 0;

while(local2.i < 10)
{
__LocalsDisplayClass$0000000c local3 = new __LocalsDisplayClass$0000000c();
local3.$locals$00000009 = local1;
local3.$locals$0000000b = local2;
local3.inner = local1.i;

methods[local2.i] = new NoArgs(local3.__AnonymousMethod$00000007);
methods[local2.i]();
}

for (int j = 0; j < methods.Length; j++)
methods[j]();
}





总结其规律就是每个不同的局部变量作用域会有一个单独的类进行封装,子作用域中如果使用到父作用域的局部变量,则子作用域的封装类引用父作用域的封装类。相同作用域的变量和匿名方法由封装类绑定到一起,维护其一致的生命周期。

相对于MS较为复杂的实现,Delphi.NET对嵌套函数则使用较为简单的参数传递方式,因为嵌套函数没有那么复杂的变量生命期管理要求,如


以下为引用:

procedure SayHello;
var
Name: string;

procedure Say;
begin
WriteLn(Name);
end;
begin
Name := 'Flier Lu';

Say;
end;




系统生成函数Say代码时,将使用到的上级变量如Name放入到一个自动生成的类型($Unnamed1)中,然后作为函数参数传递给Say函数,伪代码类似


以下为引用:

type
$Unnamed1 = record
Name: string;
end;

procedure @1$SayHello$Say(var UnnamedParam: $Unnamed1);
begin
WriteLn(UnnamedParam.Name);
end;

procedure SayHello;
var
Name: string;
Unnamed1: $Unnamed1;
begin
Name := 'Flier Lu';

Unnamed1.Name := Name;

Say(Unnamed1);
end; 
 
分享到:
评论

相关推荐

    C#2.0标准文档(官方语言规范)

    2. 局部类型推断:C# 2.0中的匿名方法和lambda表达式引入了局部类型推断,使得变量的类型可以根据上下文自动推断,简化了代码编写,如`var`关键字的使用。 3. 委托与事件:C# 2.0对委托进行了加强,支持匿名方法和...

    C#2.0完全参考手册源代码

    4. **局部类型推断(Lambda表达式)**:C# 2.0引入了匿名函数的升级版——lambda表达式,它允许更简洁地表示函数或委托。例如,`(x, y) =&gt; x + y`定义了一个接受两个参数并返回它们之和的函数。 5. **泛型**:C# ...

    完全手册:c#2.0程序设计详解电子教程

    《完全手册:C#2.0程序设计详解电子教程》是一部深入探讨C# 2.0编程技术的全面性资源,旨在帮助读者掌握C#语言的基础与高级特性,以及利用这些特性进行高效软件开发。本教程涵盖了从入门到进阶的各个方面,包括语法...

    开发人员必看的C#2.0教程...

    11. **Lambda表达式**:虽然C# 3.0才正式引入,但这个CHM文件可能也会涉及,lambda表达式是LINQ的基础,它提供了一种简洁的编写函数式代码的方式。 12. **Linq(Language Integrated Query)**:虽然不是C# 2.0的一...

    C#2.0示例

    1. **匿名方法**:C# 2.0引入了匿名方法,允许在不需要定义单独的方法的情况下编写代码块。例如,`delegate`关键字可用于创建匿名委托,这在处理事件或需要临时代码块时非常有用。在Web应用程序中,可能会在事件处理...

    c#2.0新特性(修订)

    1. **匿名方法**:C# 2.0引入了匿名方法,允许在不定义单独函数的情况下直接传递代码块作为参数。这为事件处理和LINQ(Language Integrated Query)奠定了基础。 2. **迭代器**:迭代器使得开发者可以自定义迭代...

    C#2.0新特性

    C# 2.0是.NET Framework 2.0的一部分,发布于2005年,它引入了许多增强功能,进一步提升了开发人员的生产力和代码的可读性。以下是C# 2.0中的主要新特性: 1. **匿名方法**: 匿名方法允许在不定义单独的方法的...

    C#2.0锐利体验

    2. **匿名方法**(C#2.0.锐利体验系列课程(2):匿名方法、迭代器.rar): 匿名方法允许我们在不定义单独的方法的情况下,直接传递代码块作为参数。这在处理事件处理或 LINQ 查询时非常有用。此外,匿名方法与lambda...

    c#2.0 宝典 源文件21-25

    2. **匿名方法**:匿名方法允许我们在不定义单独函数的情况下直接提供代码块作为参数,如`delegate { /* code here */ }`。这在处理事件或需要快速实现回调功能时非常有用。 3. **迭代器**:迭代器通过`yield ...

    《C# 2.0实例自学手册 通过200个例子掌握Web开发捷径》一书配套光盘

    1. **匿名方法与Lambda表达式**:C# 2.0引入了匿名方法,简化了事件处理和回调函数的编写,而Lambda表达式则是匿名方法的进一步发展,它使得代码更加简洁,特别是在LINQ查询中。 2. **迭代器**:迭代器允许自定义...

    C#2.0

    本篇文章将深入探讨C#2.0中引入的四个关键特性:泛型、匿名方法、迭代器和不完整类型(partial type),并解释它们如何改变C#编程的面貌。 ### 泛型 #### 19.1 泛型的概念与优势 泛型是C#2.0中最具革命性的特性之...

    C#2.0宝典源文件

    1. **匿名方法**:这是C# 2.0引入的一个重要特性,允许开发者定义没有名字的函数,可以直接作为参数传递或作为局部变量。这对于简化代码,特别是处理事件处理程序时非常有用。 2. **迭代器**:迭代器使程序员能够...

    C#2.0.锐利体验系列课程(4):杂项技术,以及C#语言的未来发展

    Lambda表达式提供了一种更简洁的方式来表示匿名函数,尤其在配合LINQ使用时更为便捷。 5. 枚举和结构体的比较:C# 2.0强化了枚举和结构体的比较,允许直接使用"=="和"!="操作符进行值比较,提高了代码的可读性。 ...

    C#2.0实例自学手册:

    Lambda表达式是一种简洁的匿名函数表示,常用于 LINQ 查询和事件处理,它大大简化了代码并提升了可读性。 总的来说,《C#2.0实例自学手册》会带你深入理解这些特性,并通过实例教你如何实际应用它们。通过学习这...

    程序天下:c#2.0实例自学手册

    8. **Lambda表达式**:虽然在C#3.0中引入,但与匿名方法密切相关,lambda表达式提供了一种更简洁的编写匿名函数的方式。 9. **增强的异常处理**:C#2.0改进了异常处理,如using语句和try-finally语句,使得资源清理...

    C#2.0新特性(CHM)

    本资源《C#2.0新特性(CHM)》是一部关于这一版本的详细指南,它涵盖了C# 2.0的关键更新,帮助开发者掌握这一阶段的编程技术。 一、匿名方法 匿名方法是C# 2.0引入的一个重要特性,允许在不定义单独函数的情况下直接...

    C#2.0锐利体验ppt

    4. 匿名方法:匿名方法允许在不定义单独函数的情况下,直接在需要的地方插入代码块。这对于事件处理和LINQ查询非常有用。 5. 预处理器指令(#region):通过使用#region和#endregion,开发者可以组织代码,使其在...

    程序天下C#2.0实用开发参考大全 2

    《程序天下C#2.0实用开发参考大全》是一本专为C# 2.0开发者设计的详尽参考资料,旨在帮助程序员深入理解和熟练运用C#语言进行软件开发。本书全面覆盖了C# 2.0的核心概念、语法特性、编程技巧以及实际应用案例,旨在...

    c#2.0自学手册源代码

    Lambda表达式提供了一种更简洁的编写匿名函数的方式。 10. **枚举与枚举标志(Flags Attribute)**:枚举可以使用Flags属性标记,表示可以进行按位逻辑运算,常用于表示多种状态组合。 通过分析《C# 2.0 自学手册...

    C#2.0完全自学手册

    《C#2.0完全自学手册》是一本深入解析C# 2.0编程语言的教程,旨在帮助读者全面掌握这一版本的C#语言。C#是由微软公司开发的一种面向对象的编程语言,广泛应用于Windows桌面应用、Web应用以及游戏开发等领域。C# 2.0...

Global site tag (gtag.js) - Google Analytics