Start of Main Content
How to Improve Code Reusability Using C# Delegates
Delegates are a powerful, functional language feature of C# that are heavily utilized but rarely implemented by most developers who are not familiar of the advantages they give you. Similar to function pointers in C/C++, delegates in C# allow you to assign a type to a particular method signature. In addition to referencing existing methods as delegates, C# also lets you define delegates inline as anonymous methods. We can apply static typing to both the input parameters and return value of a delegate, a concept I will touch upon again later in this post. If you've been programming in C# for any significant period of time, you've almost certainly run into delegates while using LINQ. Without getting into LINQ and how awesome it is (note: it is awesome), I'm going to explain how you yourself can use delegates to make code that is vastly more robust, clean, and powerful. This post is intended for developers who have the following skills:
- Experience writing C# code.
- The ability to use delegates already. Maybe you just don’t use them often or are not sure how utilize them effectively. Knowing how to use delegates will be important as I don’t cover this. I recommend reading the MSDN documentation on them if you don’t already have experience with them.
- Intermediate programming experience. I'll be using some programming jargon that will seem foreign to novice programmers. If you're a novice but consider yourself up to the challenge, feel free to continue with the tutorial as you too will certainly get something out of it.
Why Delegates?
When you think with delegates, you will find yourself writing not just methods that do one thing, but little behavioral engines that a developer can enhance, reuse, and extend. The amount of refactoring will drop significantly, because instead of having a large number of rigid methods each doing a very specific task, you will have a smaller number of much more general methods that can be enhanced to handle a wide variety of potential scenarios. Your code will become highly reusable, and you will feel better about yourself because of it. Consider the following case where we have a list processor that takes in a list of strings and processes each item in the list, and then returns a new list of the processed strings. The current implementation replaces all instances of “a” with “b” for each string in the list.
public class ListProcessor
{
public static List ProcessList(List strings)
{
List newList = new List();
foreach (string str in strings)
{
newList.Add(ProcessItem(str));
}
return newList;
}
private string ProcessItem(string str)
{
return str.Replace("a", "b");
}
}
Cool, it does its job, but what if we want to use this for other tasks? What if we want to modify what we do to each of the strings? Let’s use delegates to solve this.
public class ListProcessor
{
public delegate string StringProcessor(string str);
public static List ProcessList(
List strings,
StringProcessor processor)
{
List newList = new List();
foreach (string str in strings)
{
newList.Add(processor(str));
}
return newList;
}
}
Awesome, now we have the ability to do custom string processing, and aren’t limited to just replacing “a” with “b”. We can do whatever we want with the string now. To make the same call as in the original, you’d type:
ListProcessor.ProcessList(
myListOfStrings,
str => str.Replace("a", "b"));
What if we’d like to filter out certain items from being processed? We could just iterate over the items beforehand and filter them out, but as good software developers, we know that it’s wise to keep our code DRY. Is there any way to avoid writing another loop, when we know that the ProcessList method has a perfectly nice one already? Yes. Let’s add a delegate that, if it returns false, doesn’t include that item in the return list, and have that be our acceptance condition for an item being retained in the returned list.
public class ListProcessor
{
public delegate bool StringAcceptor(string str);
public delegate string StringProcessor(string str);
public static List ProcessList(
List strings,
StringAcceptor acceptor,
StringProcessor processor)
{
List newList = new List();
foreach (string str in strings)
{
if (!acceptor(str))
{
continue;
}
newList.Add(processor(str));
}
return newList;
}
}
If the item passed into the StringAcceptor delegate returns false, then the item is not added to the return list, nor processed for that matter. Now, if we just want to process items who don’t have a “c” in them, and replace the instances of “a” with “b” in the ones we accept, we can do the following:
ListProcessor.ProcessList(
myListOfStrings,
str => !str.Contains("c"),
str => str.Replace("a", "b"));
Simple enough. We now have a single method call that takes a list of items, filters out items with a “c” in them, and replaces all instances of “a” with “b”. Best of all, we can swap out the acceptance condition and processing method to suit whatever needs we have.
Let’s Go Further With Generics
Delegates support the powerful generics system in C#. Let’s take our code above, and refactor it to work on any kind of item, while retaining the static typing niceness of C#. We’re going to make this list processor take in a list of Type A and return a list of Type B. Now, our Acceptor (formerly StringAcceptor) delegate will accept items of Type A, and our Processor (formerly StringProcessor) takes in objects of Type A and outputs objects of Type B (note: We can make it take in and pump out objects of the same type, if that is what we desire). Now, our ListProcessor class looks like this:
public class ListProcessor
{
// This is equivalent to the built-in Predicate delegate
// http://msdn.microsoft.com/en-us/library/bfcke1bz.aspx
public delegate bool Acceptor(TA obj);
public delegate TB Processor(TA obj);
public static List ProcessList(
List objects,
Acceptor acceptor,
Processor<ta,> processor)
{
List newList = new List();
foreach (TA obj in objects)
{
if (!acceptor(obj))
{
continue;
}
newList.Add(processor(obj));
}
return newList;
}
} </ta,>
This still works:
List resultStrings = ListProcessor.ProcessList(
myListOfStrings,
str => !str.Contains("c"),
str => str.Replace("a", "b"));
But we can also do this:
List resultUris = ListProcessor.ProcessList(
myListOfStrings,
str => !str.Contains("amikawaiiuguu.com"),
str => new Uri(str));
If you’ve used LINQ before, you’re probably saying to yourself “this looks a lot like the stuff I do in LINQ”. That’s a good thing, because LINQ makes fantastic use of the delegate system in C#, and what we’ve done here is behaviorally the same to this LINQ statement:
List resultUris = myListOfStrings.
Where(str => !str.Contains("amikawaiiuguu.com")).
Select(str => new Uri(str)).ToList();
We’re absolutely not limited to just lists though. Here’s an example of using delegates for validation on database records.
public class DatabaseRecord
{
public delegate bool RecordCallback(T record)
where T : DatabaseRecord;
// Returns true if the record is saved.
public static bool SaveRecord(DatabaseRecord dbr)
{
// Saves the record
// Assume this is implemented
}
// Returns false if the pre or post validation fail, or if the saving fails.
// Returns true otherwise.
public static bool SaveRecordWithValidation(
T record,
RecordCallback onPreSave,
RecordCallback onPostSave)
where T : DatabaseRecord
{
return onPreSave(record) &&
SaveRecord(record) &&
onPostSave(record);
}
}
Simply implement your database Record subclass and pass it to the DatabaseRecord.SaveRecordWithValidation method. Put in your pre- and post- save validations and you’ve got typesafe validations that can be dynamically set where needed. We’re just leveraging the ability to inject behavior to make our application level code more extensible and cleaner.
Case in Point
Delegates give developers the power to make methods that can have behavior injected into them, while retaining the type safety of C#. They lend themselves well to writing highly reusable and DRY code. They are very effective when used as callbacks to various system events, handling data structures that will have common code affecting it’s various parts, or if you want to write your code in a more functional style, such as utilizing advanced functional programming techniques like currying.