[翻译]C#闭包内幕(Looking Inside C# Closures)

    如果你像我一样,当你看到为你产生的新的语言特性时,你很好了解新语言特性。C#中的闭包没有区别。在C#闭包的表面下有很多事情发生。查看C#3.0编译器生成的所有代码能真正的帮助你明白发生了什么。 Reflector的帮助下,我们可以学到很多。我用一个相当简单的C#3.0程序开始:

ContractedBlock.gif ExpandedBlockStart.gif Code
class Program
    {
        
static void Main(string[] args)
        {
            
int counter = 0;
            IEnumerable
<int> values = Utilities.Generate(20, () => counter++);

            Console.WriteLine(
"Current Counter: {0}", counter);
            
foreach (int num in values)
                Console.WriteLine(num);

            Console.WriteLine(
"Current Counter: {0}", counter);

            
foreach (int num in values)
                Console.WriteLine(num);

            Console.WriteLine(
"Current Counter: {0}", counter);
        }
    }

    
public static class Utilities
    {
        
public static IEnumerable<T> Generate<T>(int num, Func<T> generator)
        {
            
int index = 0;
            
while (index++ < num)
                
yield return generator();
        }
    }

这个程序的输出相当简单,但是给我们展示了一些关于闭包和延迟执行的东西。

 

Current Counter: 0

0

1

2

...

17

18

19

Current Counter: 20

20

21

...

38

39

Current Counter: 40

 

    这里可以看到几点:第一,注意到在定义队列后,counter的值是0.那是因为枚举使用延迟执行。枚举没有发生直到一些调用代码要检查枚举。通过查看第一和第二次的枚举后的counter的值,你可以看到这些。注意到在一旦完成枚举队列后,counter的值为20。然后,你看到在再次枚举队列后,counter的值为40。并且,注意到每次你枚举队列时,返回的队列改变。Charlie Calvert这里很好的揭示了这一概念。

    现在,让我们看一看它在内部是如何工作的。闭包是一种数据结构,拥有表达式和为了计算表达式必要绑定到包含变量的环境。好吧,这有些拗口。有时,在代码中能更容易明白。因此让我们来启动Reflector,看看编译器生成的。Reflector有一个很好的选项你可以确定Reflector应该反汇编到哪个版本的.NET。为了帖子,我选择Reflector生成.NET1.1的代码。现在,那导致在代码的体积上有很大的增长,它的可读性,事实上,C#编译器甚至没有编译过反汇编的代码(更多的是在一分钟内)。但是,它非常清楚的展示了C#为你对所有那些新特性做了些什么。因此,为了文章剩下的部分,我用反汇编的代码,并且修改它以便它是有效的C#。编译器产生的奇怪的变量名称被合法的名称替换掉。构造那些没有编译已经被重新的。

现在我将下结论:在大多数情况下,C#3.0创建来处理闭包(closures)、连续(continuations)(枚举方法(enumerator method))和其它新C#3.0特性的状态。这没有魔法,仅仅很多生成的代码。

 

创建一个枚举(Enumerator)类(Creating an Enumerator class)

让我们使用泛型方法开始。泛型创建队列,使用yield返回上下文相关的关键字。Yield Return创建了一个嵌套枚举类来生成队列,并且它处理所有的创建和使用队列的工作。

ContractedBlock.gif ExpandedBlockStart.gif Code
 public static class Utilities
    {
        
// Methods
        public static IEnumerable<T> Generate<T>(int num, Func<T> generator)
        {
            GenerateEnumerator
<T> d__ = new GenerateEnumerator<T>(-2);
            d__.currentNumber 
= num;
            d__.generatorFunc 
= generator;
            
return d__;
        }

        
// Nested Types
        private sealed class GenerateEnumerator<T> :
        IEnumerable
<T>, IEnumerable, IEnumerator<T>, IEnumerator, IDisposable
        {
            
// Fields
            private int state;
            
private T current;
            
public Func<T> generatorFunc;
            
public int currentNumber;
            
private int initialThreadId;
            
public int index;
            
public Func<T> generator;
            
public int num;

            
// Methods
            public GenerateEnumerator(int initialState)
            {
                
this.state = initialState;
                
this.initialThreadId = Thread.CurrentThread.ManagedThreadId;
            }

            
public bool MoveNext()
            {
                
switch (this.state)
                {
                    
case 0:
                        
this.state = -1;
                        
this.index = 0;
                        
while (this.index++ < this.num)
                        {
                            
this.current = this.generator();
                            
this.state = 1;
                            
return true;
                        }
                        
break;

                    
case 1:
                        
while (this.index++ < this.num)
                        {
                            
this.current = this.generator();
                            
this.state = 1;
                            
return true;
                        }
                        
break;
                }
                
return false;
            }

            IEnumerator
<T> IEnumerable<T>.GetEnumerator()
            {
                Utilities.GenerateEnumerator
<T> d__;
                
if ((Thread.CurrentThread.ManagedThreadId ==
                
this.initialThreadId) && (this.state == -2))
                {
                    
this.state = 0;
                    d__ 
= (Utilities.GenerateEnumerator<T>)this;
                }
                
else
                    d__ 
= new Utilities.GenerateEnumerator<T>(0);
                d__.num 
= this.currentNumber;
                d__.generator 
= this.generatorFunc;
                
return d__;
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                
return ((IEnumerable<T>)this).GetEnumerator();
            }

            
void IEnumerator.Reset()
            {
                
throw new NotSupportedException();
            }

            
void IDisposable.Dispose()
            {
            }

            T IEnumerator
<T>.Current
            {
                
get { return this.current; }
            }

            
object IEnumerator.Current
            {
                
get { return this.current; }
            }
        }
    }

 

所有的有趣的添加的都在类GenerateEnumerator中。你可以看到这个类包含IEnumerator<T>IEnumerator的实现。它处理关联到在列表中的当前位置的状态。当你调用产生的任何时候它创建嵌套类。所有的IEnumerator方法在嵌套类中被处理。

闭包也是类和对象(Closures are also classes and objects)

同样的技术被用在闭包,包围在例子中的main方法中的generate方法:

ContractedBlock.gif ExpandedBlockStart.gif Code
class Program
    {
        
private sealed class GeneratedClosure
        {
            
// Fields
            public int counter;

            
// Methods
            public int GeneratedMethod1()
            {
                
return this.counter++;
            }
        }

        
private static void Main(string[] args)
        {
            GeneratedClosure closureObject 
= new GeneratedClosure();
            closureObject.counter 
= 0;

            IEnumerable
<int> values = Utilities.Generate<int>(20,
            
new Func<int>(closureObject.GeneratedMethod1));

            Console.WriteLine(
"Current Counter: {0}", closureObject.counter);

            
using (IEnumerator<int> generatedEnumerator = values.GetEnumerator())
            {
                
while (generatedEnumerator.MoveNext())
                {
                    
int num = generatedEnumerator.Current;
                    Console.WriteLine(num);
                }
            }
            Console.WriteLine(
"Current Counter: {0}", closureObject.counter);

            
foreach (int num in values)
            {
                Console.WriteLine(num);
            }
            Console.WriteLine(
"Current Counter: {0}", closureObject.counter);
        }

    }

 

    编译器创建GeneratedClosure类来包含绑定在表达式需要的环境中的变量。这是一个只包含一个字段,计数器的环境,所以是一个简单的类。注意到字段是公开的,并且闭包类型包含将被绑定到委托(GeneratedMethod1)。GeneratedClosures实现了环境和绑定的变量。闭包中的所有表达式的执行产生在这个嵌套类的上下文。通过查看看起来像C#1.1相等的Main(),你可以领会到我的意思。代替一个简单的int局部变量,编译器创建了一个GeneratedClosured的实例。然后,编译器初始化绑定的变量(closureObject.counter.

      Lambda表达式被一个绑定到实例方法closureObject.GeneratedMethod1的委托。那确保委托在闭包环境的上下文中被计算。

    在这可以看到一些额外的C#行为。虽然Main对队列枚举了一次以上,但编译器只创建了闭包的一个实例。每次环境被重用。这就是为什么最后是40,而不是20.因为同样的原因,第二个队列包含数字20-39。注意到在两种情况下,Main检查闭包内部的绑定变量。那就是从外部范围,闭包环境中的如何改变是可以见的(和可修改的)。最后,我不知道为什么第一次foreach循环和第二次完全不同。如果有人知道,我很感兴趣。

    我希望这次小小的闭包内部之旅会有用。基本要点(最少对我来说)就是:编译器使用大量的常见的构造来创建环境。虽然有助于查看内部代码来理解是如何运行,那只是需要为了帮助弄明白新特性。有助于通过剥去覆盖来移除神秘。在大多数日常工作中,最好使用新语法,让编译器来做工作,把事情完成。但现在,下次有人提到“闭包(closures)”,并且讨论他们是否有用,现在你知道闭包是什么,为什么它是一个有用的构造,并且在C#编译器是如何为你合起来的。

 

源文档 <http://srtsolutions.com/blogs/billwagner/archive/2008/01/22/looking-inside-c-closures.aspx>

转载于:https://www.cnblogs.com/qishichang/archive/2009/06/20/1507540.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值