Delegates are deceptively great. Without them, something as simple as the OnClick
event of a button would cease to work. In a nutshell, they offer a simple way to pass a function as a parameter which in turn can be invoked anywhere, anytime. Pointers for functions, very useful!
In .NET v2.0, the anonymous delegate was quietly introduced, well at least it slipped my notice. I read about them at the same time I read about Lambda functions. I saw them laid bare without the syntactic sugar in all their simple glory. As I was stuck on a .NET v2.0 project, I found sugar-free anonymous delegates useful to, say, recursively find all controls of a given Type
and execute a function on the matching controls.
More recently, I was working on a robust Windows service responsible for various IO operations. It was vitally important each file operation (heretofore fileop) had its own comprehensive try
/catch
block. As the number of fileops increased, the code looked like one huge catch
block.
It was clear it would be nice to have just one try
/catch
block in a static
utility class which could catch every IO exception conceivable. Then, I could replace each try
/catch
block with one-line:
bool isBackedUp = FileUtil.FileOp(_logger, () => File.Copy(latestFilename,
backupFilename, true));
Notice the file copy on the other side of the lambda function syntax, () =>
, is what is executed in the try
block below:
public delegate void FileOperation();
internal static bool FileOp(ILogger logger, FileOperation fileOp)
{
bool success = false;
try
{
fileOp.DynamicInvoke();
success = true;
}
catch (ArgumentException argEx)
{
logger.Error(argEx, "Bad arguement(s) passed");
}
catch (DirectoryNotFoundException dirEx)
{
logger.Error(dirEx, "The specified path is invalid");
}
catch (FileNotFoundException fileEx)
{
logger.Error(fileEx, "The specified file was not found");
}
catch (PathTooLongException pathEx)
{
logger.Error(pathEx,
"The specified path, file name, or both exceed the system-defined maximum length");
}
catch (IOException ioEx)
{
logger.Error(ioEx, "An I/O error has occurred");
}
catch (NotSupportedException supportEx)
{
logger.Error(supportEx, "The requested operation was not supported");
}
catch (SecurityException secEx)
{
logger.Error(secEx, "The caller does not have the required permission.");
}
catch (UnauthorizedAccessException accessEx)
{
logger.Error(accessEx, "The caller does not have the required permission");
}
catch (Exception ex)
{
logger.Error(ex, "General fileop exception");
}
return success;
}
Not only was this an elegant way to catch a comprehensive set of exceptions, but the resulting code was much more readable.
Of course, we could pass bigger chunks of code and this is fine in moderation. But the flip-side can mean less readable code when lambda functions (and especially lambda expressions) are used without restraint. Readability for the whole team is paramount. After all, too much syntactic sugar will rot your teeth!
The brilliant thing about C# is the mind set of “I’m sure there’s a way to do this” is often rewarded with a little research.