Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#

Local Recursion with Anonymous Methods and Simple Threading

Rate me:
Please Sign up or sign in to vote.
4.68/5 (13 votes)
4 Oct 2008CPOL3 min read 34.2K   35   3
Anonymous methods help in keeping the "environment" of recursive processing encapsulated

Introduction

Recursive functions can get a little complex when dealing with several vars. Either these vars must become class-members (violating the rule of encapsulation) or they must be passed as parameters through all the recursive (self-)calls.

A local recursion can work on the local vars of the surrounding method.

Here We Go

C#
private void toolStripButton1_Click(object sender, EventArgs e) {
   Action<string> Recurse = null;
   Recurse = (sDir) => {
      Console.WriteLine(sDir);
      foreach (var S in System.IO.Directory.GetDirectories(sDir)) {
         Recurse(S);
      }
   };
   Recurse(@"..\..");
}

That's the basic one, just to show the principle: create an anonymous Action, then call it.
The only unusual feature is: you can't declare and initiate in one line.

C#
Action<string> Recurse = (sDir) => {
    foreach (var S in System.IO.Directory.GetDirectories(sDir)) {
        Recurse(S);
    }
};

will not work, because "Recurse" is used, before it is defined.

(Maybe you can disable this compiler-error, I don't know whether that's recommended.)

Let's Use the Benefits of Local Recursion

C#
private void GetDirInfo(string Root) {
   int dirCount = 0;
   int fileCount = 0;
   long sizeSum = 0;
   Action<DirectoryInfo> Recurse = null;
   Recurse = (parent) => {
      Console.WriteLine(parent.FullName);
      var fileInfos = parent.GetFiles();
      fileCount += fileInfos.Length;
      foreach (var FI in fileInfos) { 
         sizeSum += FI.Length; 
      }
      var children = parent.GetDirectories();
      dirCount += children.Length;
      foreach (var DI in children) { 
         Recurse(DI); 
      }
   };
   Recurse(new DirectoryInfo(Root));
   Console.WriteLine(string.Concat(
      "dirCount: ", dirCount, "\tfileCount: ", 
      fileCount, "\tsizeSum: ", sizeSum));
}  

This one collects information into 3 local vars. After that they can be displayed, without efforts.

Threading with Anonymous Methods

The other issue is, how to "transfer" a method-call into a side-thread. I use the Delegate.BeginInvoke() method, which takes threads from the threadpool. Other ways of threading may run faster, or may give more control on the side-thread-process - this approach is simple and is kind to system-resources:

C#
private void toolStripButton2_Click(object sender, EventArgs e) {
   Action action = () => GetDirInfo(@"..\..");
   action.BeginInvoke(ar => action.EndInvoke(ar), null);
}

Yeah, this one is pretty short, isn't it? It translates the synchronous call to an asynchronous one.

Ähm..., and Now I'm Sorry...

... that issue can be made still more simple, without anonymous methods:

C#
private void toolStripButton2_Click(object sender, EventArgs e) {
   Action<string> action = new Action<string>(GetDirInfo);
   action.BeginInvoke(@"..\..", action.EndInvoke, null);
} 

(From here on, I've made some changes to this article.)

What's that strange BeginInvoke-Parameter (..., action.EndInvoke, ...)??

Look (with the objectbrowser) at Action.BeginInvoke()s Signature:

C#
public virtual IAsyncResult BeginInvoke(System.AsyncCallback callback, object obj)

Browse on to see, what an "AsyncCallback" is - a delegate, defined as follows:

C#
public delegate void AsyncCallback(System.IAsyncResult ar)

The BeginInvoke()-Pattern (please follow the link) wants us to implement a separate method with that signature, and pass the methods address to BeginInvoke(), for that the side-thread can call back, when its job is done. To that Callback-Method the side-thread will pass an IAsyncResult (whatever that may be).

Usually I don't need that. My threaded functions "know" themselves, when they run out. But (see the link above) Microsoft insists on calling EndInvoke:

<cite> Important Note: 
Always call EndInvoke to complete your asynchronous call.</cite> 

And EndInvoke() can only be called after the side-thread has run out (otherwise it blocks the main-thread, until the side-thread finishes).

Now watch the signature of Action.EndInvoke():

C#
public virtual void EndInvoke(System.IAsyncResult result) 

Yeah, it is an AsyncCallback!
And for that Action.EndInvoke is a valid argument for Action.BeginInvoke().

One more param: (..., ..., null)??

(At this point, I have to say: I didn't design that.)
The last param of [Delegate].BeginInvoke() is to transfer data to the callback. That data will appear as "result.State" in the IAsyncResult, which is passed to the callback, when the side-thread calls back. As I said before: usually I don't need that.

Conclusion

Both issues can be assumed as a kind of "pattern": You can apply the recursion-stuff to mostly all recursive requirements.

Same to the threading-issue: It can translate any void method. (And I will take care to design my threading-stuff as void methods).

Ooooh!

Now I've "finished" (is it possible to finish any programming-issue at all?) my article, trying hard to explain that threading-stuff. I stumbled over this: Competition-winner "C# Jun 2006"

Follow that link, to get a deep understanding about asynchronous calls in .NET-Framework.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generaluh Pin
konikula6-Jan-10 20:29
konikula6-Jan-10 20:29 
GeneralGood work Pin
Donsw25-Jan-09 13:58
Donsw25-Jan-09 13:58 
GeneralGreat :) Pin
Bnaya Eshet7-Oct-08 3:19
Bnaya Eshet7-Oct-08 3:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.