Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Designing With Lambdas - Part III

In the previous two installments of the series we discussed how we can use lambdas to encapsulate more processing logic and to create contextual APIs. In this post I will show another example of the latter.

Navigating directory structures

This time around we will try to create a safe way to browse and create directories without losing track of the current location. When we write code to read files or directory information from a directory structure, and we need to look into more than one directory, we have to be careful to always be sure of what the current working directory is or use only absolute paths. Neither approach is without its inconveniences.

What I'll try to show here is a way to enforce the working directory as a lambda context. That can be a mouthful so let me try to put it in simpler terms. I'd like to have a way to assert that when a particular code runs, the working directory will be changed to a specified location, and after that code finishes the working directory is restored automatically.

Without lambdas, the context setting could resemble the following:

string previousDir = Environment.CurrentDirectory;
try
{
	Environment.CurrentDirectory = @"c:\myapp";
	DoStuff();

	try
	{
		Environment.CurrentDirectory = 
				@"c:\myapp\subdirs\resources\images\toolbar";
		DoOtherStuff();
	}
	finally
	{
		Environment.CurrentDirectory = @"c:\myapp";
	}

	DoMoreStuff();
}
finally
{
	Environment.CurrentDirectory = previousDir;
}

All those try and finally are there to make sure we restore the appropriate working directory after we are done with each one. I don't know about you, but having a lot of those in my code would add a lot of noise, obscuring the real intent of the code.

What if we could, once again, encapsulate this pattern in a function that accepts a delegate? Here's what a way more revealing code would look like.

Dir.Change(@"c:\myapp", path =>
{
	//we're in c:\myapp
	Dir.Change(subdir =>
	{
		//we're in c:\myapp\subdir
		string dirName = DateTime.Now.ToString("yyyy-MM-dd");
		//we can ask it to create the directory if 
		//  not found (the "true" parameter)
		Dir.Change(dirName, true, dailyLogDir =>
		{
			//we're in c:\myapp\subdir\2008-04-29 (for example)
			using(StreamWriter wr = File.CreateText("logfile.txt"))
			{
				wr.WriteLine("Hello file.");
			}
		});

		//we're back in c:\myapp\subdir

		//we can pass in a deeper path
		Dir.Change(@"resources\images\toolbar", imagesDir =>
		{
			//we're in c:\myapp\subdir\resources\images\toolbar
			//listing all files here
			foreach(string file in imagesDir.GetFiles("*.png"))
				Console.WriteLine("Toolbar icon: " + file);

		});
	});
});

Within each Dir.Change block we can be certain that the current working directory will be the one we specified (unless your own code intentionally changes it.) When the code block finishes, we will be back to whatever directory we were before the block, guaranteed.

Note in line 4 that the name of the directory can be gathered from the parameter's name subdir. This is not always possible because we could be dealing with multi-level directory changes (line 22) or dynamically generated directory names (line 10). Additionally, the rules for naming directories are not the same as for naming C# identifiers. For these reasons, it's also possible to pass a string containing the name of the desired directory.

Here's the code that makes this possible.

public static class Dir
{
	public static void Change(Action<DirectoryInfo> execute)
	{
		Change(false, execute);
	}

	public static void Change(string path, Action<DirectoryInfo> execute)
	{
		Change(path, false, execute);
	}

	public static void Change(bool createIfNeeded, Action<DirectoryInfo> execute)
	{
		string path = execute.Method.GetParameters()[0].Name;
		Change(path, createIfNeeded, execute);
	}

	public static void Change(string path, bool createIfNeeded, 
							Action<DirectoryInfo> execute)
	{
		string previousDir = Environment.CurrentDirectory;

		try
		{
			if(createIfNeeded && !Directory.Exists(path))
				Directory.CreateDirectory(path);

			var di = new DirectoryInfo(path);
			Environment.CurrentDirectory = path;
			execute(di);
		}
		finally
		{
			Environment.CurrentDirectory = previousDir;
		}
	}

	public static bool TryChange(string path, Action<DirectoryInfo> execute)
	{
		if(Directory.Exists(path))
		{
			Change(path, false, execute);
			return true;
		}
		else
		{
			return false;
		}
	}
}

So there you have it. Another example of incorporating lambdas to design APIs that attempt to reduce code noise and eventually read more naturally. Hopefully you're starting to see a pattern here in terms of what a context can be and how lambda-aware APIs can help formalizing that context.


Posted 04-29-2008 2:30 PM by sergiopereira

[Advertisement]

Comments

Reflective Perspective - Chris Alcock » The Morning Brew #84 wrote Reflective Perspective - Chris Alcock &raquo; The Morning Brew #84
on 04-30-2008 3:14 AM

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew #84

DotNetKicks.com wrote Designing with Lambdas Part III
on 04-30-2008 4:14 AM

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Dew Drop - April 30, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - April 30, 2008 | Alvin Ashcraft's Morning Dew
on 04-30-2008 9:09 AM

Pingback from  Dew Drop - April 30, 2008 | Alvin Ashcraft's Morning Dew

Jeevan James wrote re: Designing With Lambdas - Part III
on 04-30-2008 5:37 PM

You could enhance this further by using extension methods to give you a cleaner syntax. For example, if you had this method:

public static DirectoryInfo Change(this DirectoryInfo directory, string subdirectory, Action<string> action)

{

   string newdir = Path.Combine(directory.FullName, subdirectory);

   if (!Directory.Exists(newdir))

       Directory.CreateDirectory(newdir);

   string olddir = Environment.CurrentDirectory;

   Environment.CurrentDirectory = newdir;

   action(newdir);

   Environment.CurrentDirectory = olddir;

   return new DirectoryInfo(newdir);

}

You could then use it like so:

new DirectoryInfo(@"c:\myapp")

   .Change("subdir", dir =>

   {

       // Do something

   })

       .Change("subsubdir", dir =>

       {

           // Do something

       });

sergiopereira wrote re: Designing With Lambdas - Part III
on 04-30-2008 6:35 PM

@Jeevan

Well that would certainly work, but it would also cause your "subsubdir" code to be outside your "subdir" code, which makes the code potentially harder to read, but that may just be a matter of taste. I like to have my nested directory code to mimic some of that structure.

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)