[翻译] The Beauty of Closures--C# in Depth

    原文链接:The Beauty of Closures--C# in Depth。

    闭包之美--C# in Depth。

    不足之处,愿各位不吝指出,交流学习。

    Some time soon, I want to write about the various Java 7 closures proposals on my blog. However, when I started writing that post I found it was difficult to start off without an introduction to closures. As time went by, the introduction became so long that I feared I'd lose most of my readers before I got to the Java 7 bit. As chapter 5 in the book is largely about anonymous methods and how they provide closures to C#, it seemed appropriate to write this article here.

 

    Most articles about closures are written in terms of functional languages, as they tend to support them best. However, that's also precisely why it's useful to have an article written about how they appear more traditional OO languages. Chances are if you're writing in a functional language, you know about them already. This article will talk about C# (versions 1, 2 and 3) and Java (before version 7).

 

    不久之后,我想在我的blog上写些关于Java 7闭包的建议。然而,在我开始写那篇文章之时,发现如果不先介绍闭包,写作将很难开始。随着时间的推移,介绍闭包的时间如此之长,我担心在写到Java 7方面之前,我将失去大部分读者。因为本书第五章很大程度上是关于匿名方法以及他们是如何在C#中提供闭包功能的,所以文章写在这里更合适。

 

    大多数关于闭包的文章是用函数式语言写的,因为它们对闭包的支持最好。然而,那恰恰说明出现一篇文章介绍闭包是如何出现在更传统的面向对象语言中是很有帮助的。如果你有机会用函数式写作时时,你已经了解他们。本篇将谈论c#(版本1,2,3)以及Java(版本7之前)。

What are closures?

    To put it very simply, closures allow you to encapsulate some behaviour, pass it around like any other object, and still have access to the context in which they were first declared. This allows you to separate out control structures, logical operators etc from the details of how they're going to be used. The ability to access the original context is what separates closures from normal objects, although closure implementations typically achieve this using normal objects and compiler trickery.

 

    It's easiest to look at a lot of the benefits (and implementations) of closures with an example. I'll use a single example for most of the rest of this article. I'll show the code in Java and C# (of different versions) to illustrate different approaches. All the code is also available for download so you can tinker with it.

 

什么是闭包?


    简而言之,闭包就是允许你封闭一些行为,以类似任何其他对象来传递,并且仍然有权访问他们最初声明时的上下文。这将帮助你从他们将被使用的细节中分离出控制结构,逻辑操作者等。这种访问原始上下文的能力正是闭包与普通对象的不同之处,尽管闭包的实现往往是基于普通对象和编译器处理。


    最简单的方法就是通过一个例子了解使用闭包的很多好处(及实现)。我将在本篇文章余下的大部分地方使用一个例子。我将用Java和不同版本的C#代码来阐述不同的实现。所有代码可以在此处下载,以便你自己可以修改它。

Example situation: filtering a list

    It's reasonably common to want to filter a list by some criterion. This is quite easy to do "inline" by just creating a new list, iterating over the original list and adding the appropriate elements to the new list. It only takes a few lines of code, but it's still nice to hide that logic away in one place. The difficult bit is encapsulating which items to include. This is where closures come in.

 

    Although I've used the word "filter" in the description, it's somewhat ambiguous between filtering items in the new list and filtering things out. Does an "even number filter" keep or reject even numbers, for instance? We'll use a different bit of terminology - a predicate. A predicate is simply something which matches or doesn't match a given item. Our example will produce a new list containing every element of the original list which matches the given predicate.

 

例子场景:过滤一个列表


    通常需要用某种规则来过滤一个列表。可以很简单通过创建一个新的列表,迭代原始列表并且把合适的元素加到新列表中来内联。这仅仅需要很少的几行代码,但是仍然可以在一个地方很好隐藏这种逻辑。难于处理的方面在于封装需要包含的项。这正是闭包胜任之处。


    尽管我在描述中使用了“过滤”这个词,在过滤项进新列表和过滤出去方面是存在一些疑惑的。一个"偶数过滤器"是持有还是抛弃偶数呢,比如?我们将使用一个不同的技术--谓词。谓词是判断给出的项是匹配还是不匹配。我们的例子将会产生新的列表,新列表中的来自原始列表的每项都匹配给定的谓词。

 

    In C# the natural way of representing a predicate is as a delegate, and indeed .NET 2.0 contains a Predicate<T> type. (Aside: for some reason LINQ prefers Func<T,bool>; I'm not sure why, given that it's less descriptive. The two are functionally equivalent.) In Java there's no such thing as a delegate, so we'll use an interface with a single method. Of course we could use an interface in C# as well, but it would be significantly messier and wouldn't let us use anonymous methods and lambda expressions - which are precisely the features which implement closures in C#. Here are the interface/delegate for reference:

 

    在C#中,最自然表现谓词的方式是一个代理,并且实际在.NET 2.0中包含了Predicate<T>类型。(备注:因为某些原因,LINQ更喜欢Func<T,bool>;我不确定为什么,也就不在多加叙述。两者在功能上是等同的。)在JAVA中没有像代理这样的事物,所以我们将使用一个包含单一方法的接口。当然,在C#中也可以有使用接口,but it would be significantly messier(不懂咋译)并且使得我们不能使用匿名方法和lambda表达式-这两者在C#中恰恰准确实现闭包特性。以下是引用的接口/代理:

 

//  Declaration for System.Predicate<T>
public   delegate   bool  Predicate < T > (T obj) 

 

//  Predicate.java
public   interface  Predicate < T >
{
    boolean match(T item);

 

    The code used to filter the list is very straightforward in both languages. I should point out at this stage that I'm going to steer clear of extension methods in C# just to make the example simpler - but anyone who has used LINQ should be reminded of the Where extension method. (There are differences in terms of deferred execution, but I'll avoid those for the moment.)

 

     在C#和JAVA两种语言中用来过滤列表的代码都很简单明了。此时我要指出我将避开C#中的扩展方法以便使得例子更简单--但是谁已经使用LINQ的,应该想起where的扩展方法。(在不同的执行时存在差异,但,此时我将不讲那些。)

 

//  In ListUtil.cs
static   class  ListUtil
{
    
public   static  IList < T >  Filter < T > (IList < T >  source, Predicate < T >  predicate)
    {
        List < T >  ret  =   new  List < T > ();
        
foreach  (T item  in  source)
        {
            
if  (predicate(item))
            {
                ret.Add(item);
            }
        }
        
return  ret;
    }

 

 

//  In ListUtil.java
public   class  ListUtil
{
    
public   static   < T >  List < T >  filter(List < T >  source, Predicate < T >  predicate)
    {
        ArrayList < T >  ret  =   new  ArrayList < T > ();
        
for  (T item : source)
        {
            
if  (predicate.match(item))
            {
                ret.add(item);
            }
        }
        
return  ret;
    }

 

    (In both languages I've included a Dump method in the same class which just writes out the given list to the console.)

Now that we've defined our filtering method, we need to call it. In order to demonstrate the importance of closures, we'll start with a simple case which can be solved without them, and then move on to something harder.

 

    (在两种语言中,我已经在相同的类中加入了一个名为Dump的方法,仅仅把给定的列表输出到控制台。)

现在已经定义了自己的过滤方法,我们需要调用该方法。为了阐述闭包的重要性,我们从一个没有闭包将无法解决的简单例子开始,然后再涉及一些更难的。

Filter 1: Matching short strings (length fixed)

    Our sample situation is going to be really basic, but I hope you'll be able to see the significance anyway. We'll take a list of strings, and then produce another list which contains only the "short" strings from the original list. Building a list is clearly simple - it's creating the predicate which is the tricky bit.

In C# 1, we have to have a method to represent the logic of our predicate. The delegate instance is then created by specifying the name of the method. (Of course this code isn't actually valid C# 1 due to the use of generics, but concentrate on how the delegate instance is being created - that's the important bit.)

 

过滤器1:匹配短字符串(长度固定)

    我们的例子事实上是基础性的,但是不管怎样,我希望你们能明白它的重要性。我们将取得一个字符串列表,然后产生仅仅包含原列表中“短”字符串的另一个列表。构造一个列表是很简单的--棘手的是生成谓词。在C#1.0中,我们必须有一个方法来实现谓词的逻辑。然后,通过指定方法名称来生成代理实例。(当然,因为范型的存在,这并不是完全的C#1.0的代码,但是重要的是,我们应该关注于代理实例是如何被创建的。)

 

//  In Example1a.cs
static   void  Main()
{
    Predicate < string >  predicate  =   new  Predicate < string > (MatchFourLettersOrFewer);
    IList < string >  shortWords  =  ListUtil.Filter(SampleData.Words, predicate);
    ListUtil.Dump(shortWords);
}

static   bool  MatchFourLettersOrFewer( string  item)
{
    
return  item.Length  <=   4 ;

 

    In C# 2, we have three options. We can use exactly the same code as before, or we can simplify it very slightly using the new method group conversions available, or we can use an anonymous method to specify the predicate's logic "inline". The enhanced method group conversion option isn't worth spending very much time on - it's just a case of changing new Predicate<string>(MatchFourLettersOrFewer) to MatchFourLettersOrFewer. It's available in the downloadable code though (in Example1b.cs). The anonymous method option is much more interesting:

 

    C#2.0中,我们有三种选择。我们可以用前面一样的代码来实现,或者可以用一个新的方法组转换变量来简化,或者可以用一个匿名方法来指定谓词的逻辑内联。这增强型的方法组转换选择并不值得投入太多的时间--仅仅是将new Predicate<string>(MatchFourLettersOrFewer)转换为MatchFourLettersOrFewer。下载的代码中可以找到(in Example1b.cs)。匿名方法相对来说更有趣些。

 

 

static   void  Main()
{
    Predicate < string >  predicate  =   delegate ( string  item)
        {
            
return  item.Length  <=   4 ;
        };
    IList < string >  shortWords  =  ListUtil.Filter(SampleData.Words, predicate);
    ListUtil.Dump(shortWords);

  

    We no longer have an extraneous method, and the behaviour of the predicate is obvious at the point of use. Good stuff. How does this work behind the scenes? Well, if you use ildasm or Reflector to look at the generated code, you'll see that it's pretty much the same as the previous example: the compiler has just done some of the work for us. We'll see later on how it's capable of doing a lot more...

 

    In C# 3 you have all the same options as before, but also lambda expressions. For the purposes of this article, lambda expressions are really just anonymous methods in a concise form. (The big difference between the two when it comes to LINQ is that lambda expressions can be converted to expression trees, but that's irrelevant here.) The code looks like this when using a lambda expression:

 

    我没有采用另外的方法,并且谓词正好满足使用需要。好家伙,背后是如何实现的呢?如果你用ildasm或者Reflector工具查看生成的代码。你将明白这与前面例子是一样的:编译器只是为我们做一些工作。稍后我们将看到它在更多方面大展拳脚……

 

    C#3.0中,我们有跟前面一样的选择,但是多了lambda表达式。本文的目的为了说明,lambda表达式实际上只是匿名方法的一种简要形式。(它们之间来自LINQ的最大不同在于lambda表达式可以被转换成表达式树,但那与本文不相干。)使用lambda表达式的代码如下:   

 

static   void  Main()
{
    Predicate < string >  predicate  =  item  =>  item.Length  <=   4 ;
    IList < string >  shortWords  =  ListUtil.Filter(SampleData.Words, predicate);
    ListUtil.Dump(shortWords);

 

 

    Ignore the fact that by using the <= it looks like we've got a big arrow pointing at item.Length - I kept it that way for consistency, but it could equally have been written as Predicate<string> predicate = item => item.Length < 5;

 

    In Java we don't have to create a delegate - we have to implement an interface. The simplest way is to create a new class to implement the interface, like this:

 

    忽略这样一个事实:通过使用“<=”,看起来像是我们用一个大箭头指向item.Length--我这样写是为了保持一致性,但是它可以等价地写成 Predicate<string> predicate = item => item.Length < 5;

 

    在JAVA中,我们不能创建代理--我们不得不实现一个接口。最简单的方式就是新建一个类并实现接口,就像这样:

 

//  In FourLetterPredicate.java
public   class  FourLetterPredicate implements Predicate < String >
{
    
public  boolean match(String item)
    {
        
return  item.length()  <=   4 ;
    }
}

//  In Example1a.java
public   static   void  main(String[] args)
{
    Predicate < String >  predicate  =   new  FourLetterPredicate();
    List < String >  shortWords  =  ListUtil.filter(SampleData.WORDS, predicate);
    ListUtil.dump(shortWords);

   

    That doesn't use any fancy language features, but it does involve a whole separate class just to express one small piece of logic. Following Java conventions, the class is likely to be in a different file, making it harder to read the code which is using it. We could make it a nested class instead, but the logic is still away from the code which uses it - it's a more verbose version of the C# 1 solution, effectively. (Again, I won't show the nested class version here, but it's in the downloadable code as Example1b.java.) Java does allow the code to be expressed inline, however, using anonymous classes. Here's the code in all its glory:

 

    那并没有使用任何花哨的语言特性,但是它仅仅包含一个独立的类只为了表达一个小块逻辑。依据JAVA语言约定,这个类或许应该在不同文件中,这就使得调用它的代码变得更难阅读。我们可以把它放在嵌套类中来代替,但是逻辑仍然远离调用它的代码--这是一个更有效的C#1.0的解决方案。(我也不会在这里展示嵌套类的版本,它在下载的代码Example1b.java中可以看到)Java允许被内联地表达,但是,是使用匿名类。下面的代码将显示它的闪光之处:

   

//  In Example 1c.java
public   static   void  main(String[] args)
{
    Predicate < String >  predicate  =   new  Predicate < String > ()
    {
        
public  boolean match(String item)
        {
            
return  item.length()  <=   4 ;
        }
    };
    
    List < String >  shortWords  =  ListUtil.filter(SampleData.WORDS, predicate);
    ListUtil.dump(shortWords);

 

    As you can see, there's a lot of syntactic noise here compared with the C# 2 and 3 solutions, but at least the code is visible in the right place. This is Java's current support for closures... which leads us nicely into the second example.

 

    正如我们所见,与C#2.0和3.0解决方案相比,上面代码存在许多syntactic noise(?),但是,至少可以在正确的地方看到代码。这正是Java当前对闭包的支持……这将让我们很好地引出第二个例子。

Filter 2: Matching short strings (variable length)

过滤器2:匹配短字符串(长度可变)

 

    So far our predicate hasn't needed any context - the length is hard-coded, and the string to check is passed to it as a parameter. Let's change the situation so that the user can specify the maximum length of strings to allow.

 

    First we'll go back to C# 1. That doesn't have any real closure support - there's no simple place to store the piece of information we need. Yes, we could just use a variable in the current context of the method (e.g. a static variable in the main class from our first example) but this is clearly not a nice solution - for one thing, it immediately removes thread safety. The answer is to separate out the required state from the current context, by creating a new class. At this point it looks very much like the original Java code, just with a delegate instead of an interface:

 

    到目前为止,我们谓词不需要任何的上下文--长度是硬编码的,并且字符串的校验是作为参数传入的。让我们来改变场景使得用户可以指定字符串允许的最大长度。

 

    首先,我们目光返回到C#1.0。那并没有对闭包任何的实际支持--那没有简单的位置来存储我们需要的一部分信息。是的,我们可以在当前方法的上下文中用一个变量来保存(例如:第一个例子中主类中的静态变量)但是,首先这显然不是一个理想的解决方案,它没有线程安全性。为了解决这个问题,我们可以通过新建一个类,从当前上下文中分离需要的状态。在一点上来看,这跟前面的Java代码十分相像,只是用一个代理去代替接口而已:

 

//  In VariableLengthMatcher.cs
public   class  VariableLengthMatcher
{
    
int  maxLength;

    
public  VariableLengthMatcher( int  maxLength)
    {
        
this .maxLength  =  maxLength;
    }

    
///   <summary>
    
///  Method used as the action of the delegate
    
///   </summary>
     public   bool  Match( string  item)
    {
        
return  item.Length  <=  maxLength;
    }
}

//  In Example2a.cs
static   void  Main()
{
    Console.Write( " Maximum length of string to include?  " );
    
int  maxLength  =   int .Parse(Console.ReadLine());

     VariableLengthMatcher matcher  =   new  VariableLengthMatcher(maxLength);
    Predicate < string >  predicate  =  matcher.Match;
    IList < string >  shortWords  =  ListUtil.Filter(SampleData.Words, predicate);
    ListUtil.Dump(shortWords);

    The change to the code for both C# 2 and C# 3 is simpler: we just replace the hard-coded limit with the parameter in both cases. Don't worry about exactly how this works just yet - we'll examine that when we've seen the Java code in a minute.

 

    在C#2.0和3.0代码中的改变就更简单了:我们在案例中用参数替换掉硬编码的不足。不用担心这具体是如何运行的--一会我们看过Java的代码就可以验证了。

 

//  In Example2b.cs (C# 2)
static   void  Main()
{
    Console.Write( " Maximum length of string to include?  " );
    
int  maxLength  =   int .Parse(Console.ReadLine());

    Predicate < string >  predicate  =   delegate ( string  item)
    {
        
return  item.Length  <=  maxLength;
    };
    IList < string >  shortWords  =  ListUtil.Filter(SampleData.Words, predicate);
    ListUtil.Dump(shortWords);

 

 

//  In Example2c.cs (C# 3)
static   void  Main()
{
    Console.Write( " Maximum length of string to include?  " );
    
int  maxLength  =   int .Parse(Console.ReadLine());

    Predicate < string >  predicate  =  item  =>  item.Length  <=  maxLength;
    IList < string >  shortWords  =  ListUtil.Filter(SampleData.Words, predicate);
    ListUtil.Dump(shortWords);

   

    The change to the Java code (the version using anonymous classes) is similar, but with one little twist - we have to make the parameter final. It sounds odd, but there's method in Java's madness. Let's look at the code before working out what it's doing:

   

    Java代码(使用匿名类的版本)的改变也是类似的,但是有一点别扭的是,我们必须把参数定义为fianal。那听起来是奇怪的,但正是Java的疯狂行事方式。在弄清楚代码的作用之前,让我们先看看代码:

 

//  In Example2a.java
public   static   void  main(String[] args) throws IOException
{
    System. out .print( " Maximum length of string to include?  " );
    BufferedReader console  =   new  BufferedReader( new  InputStreamReader(System. in ) );
    
final   int  maxLength  =  Integer.parseInt(console.readLine());
    
    Predicate
< String >  predicate  =   new  Predicate < String > ()
    {
        
public  boolean match(String item)
        {
            
return  item.length()  <=  maxLength;
        }
    };
    
    List
< String >  shortWords  =  ListUtil.filter(SampleData.WORDS, predicate);
    ListUtil.dump(shortWords);

   

    So, what's the difference between the Java and the C# code? In Java, the value of the variable has been captured by the anonymous class. In C#, the variable itself has been captured by the delegate. To prove that C# captures the variable, let's change the C# 3 code to change the value of the parameter after the list has been filtered once, and then filter it again:

 

    那么,Java和C#代码之间有什么不同之处呢?在Java中,变量的值已经被匿名类捕获。在C#中,变量本身已经被代理捕获。为了证明C#捕获了变量,在列表被过滤一次之后,我们修改C#3.0代码来改变参数的值,然后再过滤一次:

 

//  In Example2d.cs
static   void  Main()
{
    Console.Write( " Maximum length of string to include?  " );
    
int  maxLength  =   int .Parse(Console.ReadLine());

    Predicate < string >  predicate  =  item  =>  item.Length  <=  maxLength;
    IList < string >  shortWords  =  ListUtil.Filter(SampleData.Words, predicate);
    ListUtil.Dump(shortWords);

    Console.WriteLine( " Now for words with <= 5 letters: " );
    maxLength  =   5 ;
    shortWords  =  ListUtil.Filter(SampleData.Words, predicate);
    ListUtil.Dump(shortWords);

   

    Note that we're only changing the value of the local variable. We're not recreating the delegate instance, or anything like that. The delegate instance has access to the local variable, so it can see that it's changed. Let's go one step further, and make the predicate itself change the value of the variable:

 

    注意到我们只是改变本地变量的值。我们并不重新创建代理实例,或者其他类似的事物。代理实例已经访问了本地变量,所以可以看到列表变化了。让我们更进一步,让谓词本身去改变变量的值,如下:

  

//  In Example2e.cs
static   void  Main()
{
    
int  maxLength  =   0 ;

    Predicate < string >  predicate  =  item  =>  { maxLength ++ return  item.Length  <=  maxLength; };
    IList < string >  shortWords  =  ListUtil.Filter(SampleData.Words, predicate);
    ListUtil.Dump(shortWords);

 

    I'm not going to go into the details of how all this is achieved - read chapter 5 of C# in Depth for the gory stuff. Just expect to have some of your notions of what "local variable" means turned upside down.

    Having seen how C# reacts to changes in captured variables, what happens in Java? Well, it's a pretty simple answer: you can't change the value of a captured variable. It has to be final, so the question is moot. However, if somehow you could change the value of the variable, you'd find that the predicate didn't respond to it. The values of captured variables are copied when the predicate is created, and stored in the instance of the anonymous class. For reference variables, don't forget that the value of the variable is just the reference, not the current state of the object. For example, if you capture a StringBuilder and then append to it, those changes will be seen in the anonymous class.

 

    我将不会深入这些是如何完成的细节中--可以阅读《C# in Depth》的第五章来了解这些。只是希望引起你的注意:怎样的本地变量是完全颠倒的。(译不准确)。

    已经看过了C#在捕获变量时是如何影响变化的,Java中又是怎样的呢?答案很简单:你不能改变一个捕获的变量的值。它是用final修改的,所以问题是无意义的。然而,如果你以某种方式修改了变量的值,你将发现谓词并不会响应它。当谓词创建时,已捕获的变量的值将会被复制,然后存储在匿名类型的实例中。对于引用变量,不要忘记变量的值仅仅是个引用,并不是对象当前的状态。例如,如果你捕获了一个StringBuilder,然后作append(附加)操作,那些改变可以在匿名类中看到。

Comparing capture strategies: complexity vs power

比较捕获策略:复杂vs强大

 

    Clearly the Java scheme is more restrictive, but it does make life significantly simpler, too. Local variables behave in the same way they've always done, and in many cases the code is easier to understand, too. For example, look at the following code, using the Java Runnable interface and the .NET Action delegate - both of which represent actions taking no parameters and returning no value. First let's see the C# code:

   

    显然,JAVA体系限制性更多,但是,它也使得生活更简单。本地变量的行为正如它们所做的那样,并且在很多情况下代码也更容易理解。例如:查看以下代码,使用Java的Runnable接口和.Net的Action委托--两者操作都没有参数并且没有返回任何值。首先,我们来看C#的代码:

 

//  In Example3a.cs
static   void  Main()
{
    
//  First build a list of actions
    List < Action >  actions  =   new  List < Action > ();
    
for  ( int  counter  =   0 ; counter  <   10 ; counter ++ )
    {
        actions.Add(()  =>  Console.WriteLine(counter));
    }

    
//  Then execute them
     foreach  (Action action  in  actions)
    {
        action();
    }

    What's the output? Well, we've only actually declared a single counter variable - so that same counter variable is captured by all the Action instances. The result is the number 10 being printed on every line. To "fix" the code to make it display the output most people would expect (i.e. 0 to 9) we need to introduce an extra variable inside the loop:

 

    输出是什么?我们已经仅仅定义了一个counter变量--所以同一个counter变量被所有Actions实例捕获。结果就是数字10将打印在每行上。为了修正代码以便让代码输出大多数人期望的结果(例如:0到9),我们在循环内容需要一个额外的变量:

 

//  In Example3b.cs
static   void  Main()
{
    
//  First build a list of actions
    List < Action >  actions  =   new  List < Action > ();
    
for  ( int  counter  =   0 ; counter  <   10 ; counter ++ )
    {
        
int  copy  =  counter;
        actions.Add(()  =>  Console.WriteLine(copy));
    }

    
//  Then execute them
     foreach  (Action action  in  actions)
    {
        action();
    }

 

    Each time we go through the loop we're said to get a different instance of the copy variable - each Action captures a different variable. This makes perfect sense if you look at what the compiler's actually doing behind the scenes, but initially it flies in the face of the intuition of most developers (including me).

    Java forbids the first version entirely - you can't capture the counter variable at all, because it's not final. To use a final variable, you end up with code like this, which is very similar to the C# code:

 

    每次循环时我们都将得到copy变量的一个不同的实例--每一个Actions获取到一个不同的变量。 如果你看到编译器在背后为你处理的这一切,你将感觉很好,但是,如果你正看着编译器在背后实际上所做的一切,你将感觉很好,但是实际上,but initially it flies in the face of the intuition of most developers (including me). (不晓得咋译)

 

    Java完全不允许第一种版本--你根本获取不到counter变量,因为它不是final修饰。为了使用一个final变量,你需要像这编码,跟C#的代码有点类似:

 

//  In Example3a.java
public   static   void  main(String[] args)
{
    
//  First build a list of actions
    List < Runnable >  actions  =   new  ArrayList < Runnable > ();        
    
for  ( int  counter = 0 ; counter  <   10 ; counter ++ )
    {
        final  int  copy  =  counter;
        actions.add( new  Runnable()
        {
            
public   void  run()
            {
                System. out .println(copy);
            }
        });
    }
    
    
//  Then execute them
     for  (Runnable action : actions)
    {
        action.run();
    }

 

    The meaning is reasonably clear with the "captured value" semantics. The resulting code is still less pleasant to look at than the C# due to the wordier syntax, but Java forces the correct code to be written as the only option. The downside is that when you want the behaviour of the original C# code (which is certainly the case on occasion) it's cumbersome to achieve in Java. (You can have a single-element array, and capture a reference to the array, then change the value of the element when you want to, but that's a nasty kludge).

 

    意思与获取值在主义上是符合的。相比C#而言,结果代码仍然看起来不那么舒服,因为冗长的语法,但是Java强制正确的代码必须只能这样写。可惜的是当你想得到前面C#代码那样的行为(当然在特定的场合),这在java中实现是比较笨重的。(你有一个只包含一个元素的数组,并且获得数组的引用,然后你修改该元素为你想要的值,但是这样是很丑陋的)。

What's the big deal?

    In the example, we've only seen a little bit of benefit in using a closure. Sure, we've separated out the control structure side from the logic required in the filtering itself, but that's not made the code that much simpler on its own. This is a familiar situation - a new feature often looks less-than-impressive when used in simplistic examples. The benefit that closures often bring, however, is composability. If that sounds like a bit of a stretch, I agree - and that's part of the problem. Once you're familiar with closures and possibly a little addicted to them, the connection seems very obvious. Until that time, it seems obscure.

 

    在这个例子中,我们已经看到了使用闭包的一些好处。当然,基于逻辑需要,我们从过滤器本身分离了控制结构,但是那并没有使得代码本身更简单。这里有一个熟悉的场景--当把一个新的特性使用到过于简单的例子中将不会那么印象深刻。闭包带来好处就是组合性。如果那听起来有点夸张的话,我同意--那也往往是问题的一部分。一旦你对闭包熟悉了并且可能上瘾的时候,联系将更加明显。在那之前,都是模糊的。

 

    Closures don't inherently provide composability. All they do is make it simpler to implement delegates (or single-method interfaces - I'll stick to using the term delegates for simplicity). Without some support for closures, it's easier to write a small loop than it is to call another method to do the looping, providing a delegate for some piece of the logic involved. Even with "just add a method to an existing class" support for delegates, you still end up losing the locality of the logic, and you often need more contextual information than it's easy to provide.

 

    闭包并没有内在的提供组合性。它所作的一切都是为了实现委托变得更加简单而已(或者是只包含一个方法的接口,为了简化,我更倾向于使用代理)。如果没有闭包的一些支持,将不得不在循环中调用另外的方法处理而不是简单地在循环中通过代理传入处理逻辑。尽管只是通过“将一个方法放入一个已存在的类中“来支持代理,你仍然丢失逻辑所在的位置,并且你往往需要比其本身容易提供的更多的上下文信息。

 

    So, closures make it simpler to create delegates. That means it's more worthwhile to design APIs which use delegates. (I don't believe it's a coincidence that delegates were almost solely used for starting threads and handling events in .NET 1.1.) Once you start thinking in terms of delegates, the ways to combine them become obvious. For instance, it's trivial to create a Predicate<T> which takes two other predicates, and represents either the logical AND or OR of them (or other boolean operations, of course).

 

    所以,闭包使得创建代理更加容易了。那意味着在设计API时,使用代理是更加值得的。(我并不认为在.NET1.1中几乎都是使用代理来开启线程和处理事件是一种巧合。)一旦你开始思考使用代理,组合事物将变得明显。例如:我们很容易创建一个使用其他两个predicate的Predicate<T>,可以代表逻辑”与“与逻辑”或“(当然可以是其他的布尔操作)。

 

    A different sort of combination comes when you feed the result of one delegate into another, or curry one delegate to create a new one. All sorts of options become available when you start thinking of the logic as just another type of data.

 

    当你把代理结果放入另一个代理中,或者通过一个代理创建一个新的代理时,往往会有不同的组合排序。因为其他类型的数据也有相同的逻辑,所以此处所有的排序选择都将可用。

 

    The use of composition doesn't end there though - the whole of LINQ is built on it. The filter we built using lists is just one example of how one sequence of data can be transformed into another. Other operations include ordering, grouping, combining with another sequence, and projecting. Writing each of those operations out longhand hasn't been too painful historically, but the complexity soon mounts up by the time your "data pipeline" consists of more than a few transformations, In addition, with the deferred execution and data streaming provided by LINQ to Objects, you incur significantly less memory overhead than with in the straightforward implementation of just running one transformation when the other has finished. The complexity isn't taken out of the equation by the individual transformations being particularly clever - it's removed by the ability to express small snippets of logic inline with closures, and the ability to combine operations with a well-designed API.

 

    组合的应用不仅仅是那些--整个LINQ就是构建在此之上。我们用列表创建的过滤器仅仅是在如何将一系列数据转换成另一种类型的一个例子而已。其他的操作,例如排序,分组,联合其他一系列数据,还有设计(?)。以往写那些操作中每一个草稿(?)并不是太痛苦,但是直到你的“数据管道”的组成并不只是一些转换的时候,很快便变得复杂了,另外,由于延迟执行和LINQ to Objects提供的数据流,相对于在另一个完成时直接进行转换操作明显耗费更少内存。通过特别巧妙的独立转换,复杂性便不是问题了--它被通过闭包实现的逻辑内联来表达小代码片断以及采用良好设计的API合并操作的能力所消除。

Conclusion

结论

    Closures are a little underwhelming to start with. Sure, they let you implement an interface or create a delegate instance pretty simply (depending on language). Their power only becomes evident when you use them with libraries which take advantage of them, allowing you to express custom behaviour at just the right place. When the same libraries also allow you to compose several simple steps in a natural manner to implement significant behaviour, you get a whole which is only as complex as the sum of its parts - instead of as complex as the product of its parts. While I don't quite buy into the idea of composability as the silver bullet against complexity which some would advocate, it's certainly a powerful technique, and one which is applicable in many more situations due to closures.

 

    闭包一开始并不能给人留下深刻印象。当然,它们让你实现一个接口或者创建一个代理实例很容易(取决于语言)。只有当你使用突出它们优势的类库时,它们的强大也就显而易见了,它们能允许你表达在恰当的位置表达自定义的行为。当这些相同的类库也允许你用一种更自然的方式来组合一些简单的步骤去实现重要的行为时,你就得到了所有部分一样的复杂性,而不是和产品部分一样的复杂性(?).While I don't quite buy into the idea of composability as the silver bullet against complexity which some would advocate(?)这确实是一个很强大的技术,并且因为有闭包的作用将会在更多场景中适用。

 

    One of the key features of lambda expressions is the brevity. When you compare the Java code from earlier with the C# code, Java looks extremely clumsy and heavyweight. This is one of the issues which the various proposals for closures in Java aim to address. I will give my perspective on these different proposals in a blog post in the not-too-distant future.

 

    lambda表达式的其中一个关键优点就是简洁。当你比较前面java和C#的代码时,java代码显得极度笨重。这只是在java定位中关于闭包的众多建议中一个,不久之后,我将在我的blog中发表文章,从我的视角来表达关于那些不同的建议。

 

 

 

 

转载于:https://www.cnblogs.com/waterfrost/archive/2011/07/05/2096977.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值