I'm not sure about the community stance on passing around delegates as arguments but personally I love it. I think there is something magical about the way you can provide instructions on how how to do something and then pass it into another method and let it do its job without needing to think about it. It is almost like writing a code template and then filling in the blanks with the specifics of what you need.
Using delegates as arguments can simplify code and in some cases reduce it. This post goes over a sample scenario where you might use delegates as arguments instead of writing multiple functions to perform different tasks.
Simple Resource Loader
Let’s pretend we have a class that is responsible for loading and saving files from an external resource. We could write a class like the one below:
public static class ResourceLoader {
public static byte[] LoadBytes(string resource) {
string path = ResourceLoader._GetResourcePath(resource);
return File.ReadAllBytes(path);
}
public static XDocument LoadXmlDocument(string resource) {
string path = ResourceLoader._GetResourcePath(resource);
return XDocument.Load(path);
}
public static Bitmap LoadBitmap(string resource) {
string path = ResourceLoader._GetResourcePath(resource);
return Bitmap.FromFile(path) as Bitmap;
}
public static string LoadText(string resource) {
string path = ResourceLoader._GetResourcePath(resource);
return File.ReadAllText(path);
}
private static string _GetResourcePath(string file) {
return Path.Combine(@"c:\path\to\files\", file);
}
}
Nothing is really wrong with this code. If we need to add a new type to this class, then we simply create a new method and plug it in. Additionally, each of the methods could use some sort of exception handling in case the conversion doesn't go so well. As you can imagine, the more try catch
blocks we add, the larger this class starts to get.
However, if you think about it, all of these resources could be handled roughly the same way. They all need a way to convert bytes to whatever type you're wanting.
Using A Delegate To Fill In The Blank
So instead, let's address this problem using a delegate to handle the conversion of bytes to the type that we need.
public static class ResourceLoader {
public static T Load<T>(string resource, Func<byte[], T> convert) {
string path = Path.Combine(@"c:\path\to\files\", resource);
byte[] bytes = File.ReadAllBytes(path);
try {
return convert(bytes);
}
catch (Exception ex) {
throw new FormatException(
string.Format("Could not load resource '{0}' as a type {1}",
resource, typeof(T).Name),
ex
);
}
}
}
Great — Now we can provide any method we want to format the bytes and then return the type we're looking for. So for example, instead of calling ResourceHandler.LoadBitmap
we can use our new method as shown below:
Bitmap bitmap = ResourceLoader.Load("test.jpg", (bytes) => {
using (MemoryStream stream = new MemoryStream(bytes)) {
return Bitmap.FromStream(stream) as Bitmap;
}
});
Pretty slick, right? …oh, wait… this code is longer and more difficult to use… This can't be right!
Bringing The Concept Together
Clearly, the example above isn't improving anything for us. The code is longer and requires that we write the same functionality for reading a resource in multiple places. Even though this code is more flexible, it also requires more work. So instead, let's write some definitions of common conversions as part of our class.
public static class ResourceLoader {
public static readonly Func<byte[], Bitmap> AsBitmap = (bytes) => {
using (MemoryStream stream = new MemoryStream(bytes)) {
return Bitmap.FromStream(stream) as Bitmap;
}
};
public static readonly Func<byte[], XDocument> AsXml = (bytes) => {
using (MemoryStream stream = new MemoryStream(bytes)) {
string xml = Encoding.UTF8.GetString(bytes);
return XDocument.Parse(xml);
}
};
public static readonly Func<byte[], string> AsText = (bytes) => {
return Encoding.UTF8.GetString(bytes);
};
public static readonly Func<byte[], byte[]> AsBytes = (bytes) => bytes;
}
Now, instead of needing to manually create a delegate to handle the conversion process, we can write code like the example below:
Bitmap bitmap = ResourceLoader.Load("test.jpg", ResourceLoader.AsBitmap);
By using delegates as an argument, we allow our loading function to be flexible enough to accept other methods for performing the conversion (that we can create as we need them) or the common conversion methods that we added as part of the class.
You'll also notice that we don't need to declare the generic type we're wanting to have returned since it can be inferred from the return type of the delegate we pass in! (so cool… to me at least)
Of course, there are about a hundred different ways that can already load resources in .NET but you could apply this same concept in many other areas of applications you develop.
How could you use delegates to create “templates” for code?