Original article: http://csharpindepth.com/Articles/Chapter5/Closures.aspx
 
 
 
The first section is omitted...
 
 
 
Most closuresArticleThey are all functional languages, because they often provide the most comprehensive support for closures. When you are using a functional language, you may have clearly understood what a closure is, so I want to write an article about the usefulness of closures in the classic OO language. In this article, I want to talk about the closure of C # (1, 2, 3) and Java (earlier than 7.
 
What is a closure? 
 
To put it simply, the closure allows you to encapsulate some behaviors and deliver them like an object, and it can still access the context of the first declaration. In this way, the control structure and logical operations can be separated from the call details. The ability to access the original context is an important feature of the difference between closures and general objects, although there are only some compiler skills in implementation.
 
 
 
It is easier to use examples to observe the advantages (and implementations) of closures. I will use a single example to explain most of the content below. In this example, Java and C # (different versions) are used to illustrate different implementations. AllCodeClick here to download.
 
Scenario: Filter list 
 
Filtering a list based on certain conditions is a common requirement. Although writing a few lines of code to traverse the list, it is easy to pick out the elements that meet the conditions and put them in the "inline" method of the new list, however, it is more elegant to extract the judgment logic. The only difficulty is how to encapsulate the logic of "determining whether an element meets the condition". The closure can solve this problem.
 
 
 
Although I mentioned the word "filter" above, it may have two completely different meanings: "filter out the elements into the list" or "filter out the elements and throw them away ". For example, does "even number filtering" retain or filter out the "even number? So we use another term "assertion ". Assertions simply refer to whether something meets certain conditions. In our example, a new list containing the original list that meets the asserted conditions is generated.
 
 
In C #, it is more natural to express an asserted that through Delegate. In fact, C #2.0 has a predicate <t> type. (By the way, for some reason, LINQ prefers func <t, bool>. I don't know why, and there are few explanations. However, these two generic classes have the same role .) There is no delegate in Java, so we will use an interface with only one method. Of course, we can also use interfaces in C #, but it will make the Code look messy, and we cannot use anonymous functions and implementations that conform to the closure features in lambda expressions-C. The following interface/delegate is for your reference:
 
 
 // Declaration for system. predicate <t>Public Delegate BoolPredicate <t> (t obj)
 
 
 // Predicate. JavaPublic InterfacePredicate <t> {BooleanMatch (T item );} 
 
 
The code used for filtering in both languages is relatively simple. First first, we need to explain that here I will avoid using C # extension method to make the Code look simpler and clearer. -If you have used LINQ, pay attention to the extension where method. (There are some differences in their delayed execution, but I will avoid them here)
 
 
  // In listutil. CS   static   class  listutil { Public   static  ilist 
  
    filter 
   
     (ilist 
    
      source, predicate 
     
       predicate) {list 
      
        ret = 
        New  List 
       
         (); 
         foreach  (T item 
         in  source) {
         If  (predicate (item) {ret. add (item) ;}< span style = "color: # 0000ff"> return  RET ;}
       
      
     
    
   
  
 
// In listutil. JavaPublic ClassListutil {Public Static<T> List <t> filter (list <t> source, predicate <t> predicate) {arraylist <t> ret =NewArraylist <t> ();For(T item: Source ){If(Predicate. Match (item) {ret. Add (item );}}ReturnRET ;}} 
 
 
(I have written a dump method in both languages to output the content of the specified list)
 
 
 
Now we have defined the "filter" method, and the next step is to call it. To demonstrate the important role of a closure, I will first use a simple case that can be solved without using a closure, and then go further to a difficult case.
 
Filter Case 1: Find a short string (fixed length) 
Our demand scenarios are simple and basic, but we hope you can see their differences. We will have a string list, and then generate another string list containing only "short" according to this list. Creating a list is simple-creating an asserted is difficult.
 
 
 
In C #1.0, only one asserted logic can be expressed through a separate method, and then a delegate is created to point to the method. (Of course, because the code is generic, it cannot be compiled in C #1.0, but pay attention to how the delegate instance is created-this is the focus)
 
 
 // In example1a. CSStatic VoidMain () {predicate <String> Predicate =NewPredicate <String> (Matchfourlettersorfewer); ilist <String> Using words = listutil. Filter (sampledata. Words, predicate); listutil. Dump (using words );}Static BoolMatchfourlettersorfewer (StringITEM ){ReturnItem. Length <= 4 ;} 
 
There are three implementation methods in C #2.0. First, use the same code above. Second, use method group conversion to simplify the code. Third, using anonymous functions, assertions are directly written in the call context. Using method group conversion is a waste of time-it just setsNew predicate <string> (matchfourlettersorfewer)ChangedMatchfourlettersorfewer. In the sample code, it is implemented (inExample1b. CS). Relatively speaking, anonymous functions are much more interesting:
 
 
 Static VoidMain () {predicate <String> Predicate =Delegate(StringITEM ){ReturnItem. Length <= 4 ;}; ilist <String> Using words = listutil. Filter (sampledata. Words, predicate); listutil. Dump (using words );} 
 
 
In this way, an external independent method is no longer needed to encapsulate the asserted logic, and the assertions are placed on the used points. Very good and powerful. How does it work? If you use ildasm or reflector to check the generated code, you will find that the code generated in the first version is the same to a large extent. The Compiler just helped us complete some work. We will see more powerful capabilities later.
 
 
In C #3.0, apart from the above three methods, there are also lambda expressions. In this article, lambda expressions are just a simplified form of anonymous functions. (The biggest difference between the two types of things is that the lambda expression in LINQ can be converted into an Expression Tree, but this is irrelevant to this Article.) use the lambda expression:
 
 
 Static VoidMain () {predicate <String> Predicate = item => item. Length <= 4; ilist <String> Using words = listutil. Filter (sampledata. Words, predicate); listutil. Dump (using words );} 
 
 
Because it is used on the right side<=It looks like there is a big arrow pointingItem. LengthIn order to keep the consistency, please read it. In fact, it can be written as equivalentPredicate <string> predicate = item => item. Length <5;
 
 
 
There is no delegate in Java-only interfaces defined above can be implemented. The simplest method is to define a class and implement the interface, for example:
 
 
  // In fourletterpredicate. java   Public   class  fourletterpredicate  implements  predicate 
  
   {< span style =" color: # 0000ff "> Public  
    Boolean  match (string item) {
    return  item. length () <= 4 ;}< span style = "color: #008000"> // In example1a. java  
    Public  
    static  
    void  main (string [] ARGs) {predicate 
   
     predicate = 
     New  fourletterpredicate (); List 
    
      shortwords = listutil. filter (sampledata. words, predicate); listutil. dump (Distributed words) ;}
    
   
  
 
No gorgeous language features are used here. To implement a small logic, it uses an entire independent class. According to Java conventions, classes should be placed in separate files, which makesProgramPoor readability. Of course, you can use Nested classes to avoid this problem, but the logic still leaves the place where you use it-equivalent to the C #1.0 solution of the cool version. (The nested version of the implementation code is not provided here. If you need it, you can check the package code.Example1b. java.) Java can write the code in an inline way through the Anonymous class, and the code has evolved under the illumination of the anonymous class:
 
 
 // In example 1C. JavaPublic Static VoidMain (string [] ARGs) {predicate <string> predicate =NewPredicate <string> (){Public BooleanMatch (string item ){ReturnItem. Length () <= 4 ;}}; list <string> shortwords = listutil. Filter (sampledata. Words, predicate); listutil. Dump (shortwords );} 
 
As you can see, compared with the code of C #2.0 and C #3.0, this seems a little cool, but at least the code is put where it should be. This is the closure currently supported by Java ...... Next, we will go to the second example.
 
Filter Case 2: Find a short string (variable length) 
 
So far, our assertions do not need to access the original "context"-the length is hard-coded, and then the string is passed in as a parameter. Now, you need to change the length of the string that can be specified by the user.
 
 
 
First, let's go back to C #1.0. In fact, it does not support true closures-we cannot find a simple place to store the variables we need. Of course, we can declare a variable in the context of the current method to solve this problem (such as using static member variables), but this is obviously not a good solution-There is only one reason, class immediately becomes thread unsafe. The solution is not to store the status in the current context, but to the new class. In this way, the code looks very similar to the original Java code. The difference is that delegate is used here, while interface is used in 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 = New Variablelengthmatcher (maxlength); predicate <string> predicate = matcher. Match; ilist <string> condition words = listutil. Filter (sampledata. Words, predicate); listutil. Dump (condition words );} 
 
The changes to C #2.0 and C #3.0 are much smaller: you only need to change hard-coded constants to variables. Regardless of the principle behind this-I will study this problem after reading the Java code later.
 
 
  // 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 (Distributed words) ;}
 
// In example2c. CS (C #3)Static VoidMain () {console. Write ("Maximum length of string to include?");IntMaxlength =Int. Parse (console. Readline (); predicate <String> Predicate = item => item. Length <= maxlength; ilist <String> Using words = listutil. Filter (sampledata. Words, predicate); listutil. Dump (using words );} 
 
 
The Java version of the Code (using the version of the anonymous class) is also relatively simple, but it is a bit uncomfortable-the parameter must be declared as final. Let's take a look at the code before you know how it works:
 
 
 // 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 is the difference between C # and Java code?In JavaValueCaptured by anonymous classes. In C,Variable itselfCaptured by Delegate. To prove that C # captures the variable itself, let's change the code of C #3.0 so that the variable value changes after the variable is filtered to see if the change is reflected in the next filter:
 
 
// In example2d. CSStatic VoidMain () {console. Write ("Maximum length of string to include?");IntMaxlength =Int. Parse (console. Readline (); predicate <String> Predicate = item => item. Length <= maxlength; ilist <String> Using words = listutil. Filter (sampledata. Words, predicate); listutil. Dump (using words); console. writeline ("Now for words with <= 5 letters:"); Maxlength = 5; shortwords = listutil. Filter (sampledata. Words, predicate); listutil. Dump (shortwords );} 
 
 
Note: We only change the value of the local variable, but did not re-create the delegate instance or perform other equivalent operations. Because delegate directly accesses this local variable, it can actually know the changes of the variable. Next, modify the variables directly in the asserted logic:
 
 
// In example2e. CSStatic VoidMain (){IntMaxlength = 0; predicate <String> Predicate = item =>{ maxlength ++;ReturnItem. Length <= maxlength ;}; ilist <String> Using words = listutil. Filter (sampledata. Words, predicate); listutil. Dump (using words );} 
 
 
I don't want to go into details about how these are implemented. Chapter 5th of C # In depth describes these details. I just hope that some of your ideas on "local variables" will be completely reversed.
 
 
 
We have seen how C # modifies the captured variables. What about Java? There is only one answer: you cannot modify the captured variables. It has been declared as final, so this problem is actually very untraceable. In addition, even if your character values are bad and suddenly you can make changes to the variable, you will also find that the asserted logic does not respond to the changes at all. The value of a variable is copied and stored in an anonymous class when the asserted value is declared. However, for variables referenced, the changes to its members can still be known. For example, if you reference A stringbuilder and append it, you can see the change of stringbuilder in the anonymous class.
 
Comparison capture policy: complexity vs Function 
Obviously, Java has many design limitations, but it is also easy to understand. It is not prone to conceptual confusion. The behavior of local variables is similar to that of General variables. In most cases, the code looks easier to understand. For example, the following code uses Java runable interface and. Net action delegate to execute some operations without parameters or returning any values. First look at the C # code:
 
 
 // In example3a. CSStatic VoidMain (){// First build a list of actionsList <action> actions =NewList <action> ();For(IntCounter = 0; counter <10; counter ++) {actions. Add () => console. writeline (Counter ));}// Then execute themForeach(Action actionInActions) {Action ();}} 
 
What will be output? In fact, we only declare one counter variable-so in fact, all actions capture the same counter variable. The result is that 10 numbers are output for each row. To "correct" the code to the expected effect (for example, to output 0 to 9), you need to use another local variable in the loop body:
 
 
 // In example3b. CSStatic VoidMain (){// First build a list of actionsList <action> actions =NewList <action> ();For(IntCounter = 0; counter <10; counter ++ ){IntCopy = counter; actions. Add () => console. writeline (copy ));}// Then execute themForeach(Action actionInActions) {Action ();}} 
 
In this way, each time the loop body is executed, it will get a copy of counter, instead of itself-so each action gets a different variable value. If you look at the code generated by the compiler, you will fully understand that this result is reasonable, but for most programmers who first see the code, the intuitive result is often the opposite. (Including me)
 
 
 
In Java, there is no such situation as the first example-you cannot capture the counter variable because it is not declared as final. Use final variables to get the following code similar to C:
 
 
 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 ();}} 
 
With the semantics of "Capture variable values", the code is clear and intuitive. Although the Code looks coorse and not as cool as C #, Java enforces the only correct way to write the code. But at the same time, when you need behavior like the original C # code (sometimes there is such a requirement), it will be more troublesome to implement it using Java. (You can use an array with only one element, reference this array, and then operate on the array elements. The code looks messy ).
 
What do I want to talk about? 
In the example, we can see that there are not many advantages of closure. Of course, we split the control structure and the asserted logic, but this does not make the code more concise than the original one. This kind of thing often happens, and new features often seem to be as good as you think in simple situations and play such a big role. The common benefit of closures is thatCombatabilityIf you think this is a bit nonsense, that's right-this is also part of the problem. When you are proficient or even somewhat infatuated with closures, the relationship between the two will become more and more obvious; otherwise, it will not be easy to see its mysterious.
 
 
 
Closures are not designed to provide combatability. What it does is to make the delegate implementation easier (or there is only one interface for the method, which is abbreviated as delegate below ). If there is no closure, writing a loop structure is actually easier than passing the delegate that encapsulates some related logic to another method to execute the loop. Even if you can use delegate to call the "method added to an existing class", you still cannot put the logic code in the most appropriate place without the information storage convenience provided by closures, some information must be stored by external context of the method.
 
 
 
It can be seen that the closure makes delegate easier to use. This means it is worth designing the API as a form of using delegate. (I think this situation does not apply. NET 1.1 can only be used to process threads and subscribe to events in delegate.) When you start to solve the problem using delegate, how to solve the problem becomes obvious. For example, the most common one is to create a predicate <t> that concatenates two assertions with and or (including other logical operators.
 
 
 
When you load the results of a delegate into another list or process a new delegate, there will be totally different combinations, if you consider logic as a data that can be passed, all different types of options are feasible.
 
 
The advantage of this encoding method is far more than that mentioned above-the whole of LINQ is based on this method. The filter we created is just an example of converting ordered data into another set of data. There are also operations such as sorting, grouping, and joining another group of data and projecting. Although writing these operations using the traditional encoding method is not very painful, the complexity will increase if there are more and more conversion operations in the "Data Pipeline". In addition, the ability of LINQ to delay object execution and data flow. This method of executing multiple operations in one loop significantly saves a lot of memory than executing one operation in multiple loops. Even if each individual conversion operation is designed to be smart and efficient, still unable to strike a balance in complexity-using closures to encapsulate concise code snippets and the combination of well-designed APIs can well remove complexity.
 
Conclusion 
 
You may not be impressed with the closure at the beginning. Of course, it makes implementing your interface or delegate easier (depending on the language ). Its power can only be reflected after the relevant class library uses its features, allowing you to place custom behavior in the appropriate place. When the same class library allows you to combine several simple steps in a more natural way to implement some important actions, the complexity is only the sum of several steps-not greater than the sum. I don't agree that some people advocate that combatability is a silver bullet that solves complexity, but it is certainly a useful technique, and it can be implemented in many places thanks to closures.
 
 
 
The most important feature of lambda expressions is conciseness. Let's take a look at the previous Java and C # code. The Java code is obviously clumsy and lengthy. Many Java closure initiatives aim to solve this problem. I will post an article later on my views on these different initiatives.