Introduction
In our day-to-day programming with the latest .Net frameworks (3.5 & above) we use delegates extensively in one or the other way. It might be in the form of events or with LINQ or with Task/Threads or with MVC Rzaor helpers, the list goes on. Since the introduction of lambda expression & LINQ the delegate became so natural to us.
Most of us use delegates by providing anonymous method implementation for the framework methods, but when it comes to our own business logic or some code the delegate just doesn't come in our mind. Many people use delegates just for events, but by observing the base class libraries we can easily make out that how useful the delegates are! This article shows what are the different problems that we can solve using delegates and how they make our life simpler. So, next time when you are ready to solve some problem, give a thought for delegate as well :)
Background
I assume that you already knew what delegates and lambda expressions are, how they are related, what are the different in-built delegates provided by the framework and how to use them. If not, here is a a good link to learn. Here is another link which talks about anonymous function and events as well.
Using the code
1. Avoid class library dependency using delegates
Lets say that you have 3 class library projects namely Repositoy
, Common
and Service
. As the name suggests Repository
is intended to hold various repositories which contains data access logic. Similarly Service
is a project which is intended to hold business logic, and the Common
project is used to hold some common logic across your solution.
Assume that, there is a requirement in which you need a common logic which depends on one of the Repositoy, but you don't want your Common
assembly to have a dependency on that. In the situations as such, people generally solve it either by creating a helper service or by introducing one more abstraction, in the Service
assembly itself. But this solution has an arguable downside, that is - any other assembly that needs this logic has to use the service abstraction, which is not required otherwise. And this is arguable because, the moment you have a dependency on Repo, most probably the logic is best suited for the Service
assembly than the Common
assembly and the solution is following the proper design principles/guidelines. The other side of the argument is reusability which our solution lacks here. Sometimes the real life business requirement is so complex that it contradicts with the design principles. The argument goes on but keeping these things apart (as it is out of the scope of this article), I will concentrate on the delegate implementation part.
If you are confused with all these, don't worry! you will understand once you look into the code sample. Here is the code snippet which I am talking about:
The Repository
Assembly -
class Repo
{
public int GetValue()
{
return 100;
}
}
The Common
Assembly -
class Common
{
static Repo repo = new Repo();
public static int OperateOnRepoValue()
{
return repo.GetValue() * 2;
}
}
The Service
Assembly -
class Service
{
Repo repo = new Repo();
public void Operate()
{
int result = Common.OperateOnRepoValue();
}
}
As you can see in the above code, the Common
class is using GetValue
method of Repo
creating dependency on Repository
assembly. As discussed earlier, one way of solving this problem is by introducing an abstraction on the service layer or by introducing a new service just for the purpose of operating on the Repository
value in a re-usable manner. However, I will not give the code sample for this as it is out of the scope of this article; instead, I will show how to solve this using delegate.
Solution using a Function delegate
-
class Common
{
public static int OperateOnRepoValue(Func<int> func)
{
return func() * 2; }
}
The above code removes dependency on the Repository
by accepting a function delegate as parameter. Now, the caller can provide implementation for that delegate in the way it wish.
Here is our updated Service
class -
class Service
{
Repo repo = new Repo();
public void Operate()
{
int result = Common.OperateOnRepoValue(() => repo.GetValue());
}
}
2. Measuring various function/method execution using StopWatch
(An Using pattern with delegate)
At times, we need to measure the elapsed time of a function/method, and it is recommended that you always use StopWatch
for maximum accuracy. In case when we have to measure multiple functions, we always end-up with multiple StopWatch wrappers around our function under test. Here is an example:
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
myMethod_1();
stopWatch.Stop();
Console.WriteLine(string.Format("{0}-Elapsed: {1}", "My Method 1", stopWatch.Elapsed));
stopWatch.Restart();
myMethod_2();
stopWatch.Stop();
Console.WriteLine(string.Format("{0}-Elapsed: {1}", "My Method 2", stopWatch.Elapsed));
We can avoid this cluttering code and always reuse the measuring logic this way -
public static void Measure(Action action, string actionName)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
action();
stopWatch.Stop();
Console.WriteLine(string.Format("{0}-Elapsed: {1}", actionName, stopWatch.Elapsed));
}
The above method takes an Action
delegate and wraps it under the StopWatch
. You can use it like this:
static void Main(string[] args)
{
Measure(() => MyMethod_1(), "My method 1");
Console.ReadKey();
}
public static void MyMethod_1()
{
Thread.Sleep(1000 * 2);
}
In case, if your function under test is having a return value, use the generic function delegate
instead. Here is the code sample -
public static T Measure<T>(Func<T> func, string actionName)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
T result = func();
stopWatch.Stop();
Console.WriteLine(string.Format("{0}-Elapsed: {1}", actionName, stopWatch.Elapsed));
return result;
}
Now in the main method -
var result = Measure(() => MyMethod_1(), "My method 1");
3. Factory pattern using delegate (not factory method)
Sounds crazy?!.....not really, it looks natural once you have it in place.
Note: When I say factory, I am talking about a simple object creation, don't confuse it with Factory method pattern which involves inheritance.
Lets say that you have an Asp.Net MVC application which has an action method in a controller which accepts a parameter. Based on this parameter you may need to instantiate different Services
. You might be using a dependency container to resolve the service (or any) dependencies. If the dependency is known at compile time, then we are good and can easily register the dependencies at application startup/config file. But in our case the resolution should happen at runtime based on the provided parameter, but you may not want to scatter the dependency resolution all over the place. You can easily solve this by using a delegate. Here is the steps -
Create a class similar to this:
public static class ServiceFactory
{
public static Func<MyOptions, IService> Create { get; set; }
}
Where, MyOptions is an enum
enum MyOptions
{
Option1,
Option2,
Option3
}
Now in your application startup, on the bootstrap/component registration method you can resolve the dependencies like this:
ServiceFactory.Create = (myOptions) =>
{
switch (myOptions)
{
case MyOptions.Option1: return container.Resolve<Service1>();
case MyOptions.Option2: return container.Resolve<Service2>();
case MyOptions.Option3: return container.Resolve<Service3>();
default: return container.Resolve<Service1>();
}
};
So, in the action method you just need to rely on the factory to resolve the dependency:
public ActionResult Index(string id, MyOptions myOptions)
{
IService service = ServiceFactory.Create(myOptions);
}
4. Changing behavior of a property/method at runtime (An Using pattern with delegate)
Lets say that you are using entity framework
for data access and you created generic repository to deal with the entities. Now for some reason (usually performance) you want to schedule multiple repo calls simultaneously using parallel execution capabilities of .Net framework. Unfortunately, the generic repository pattern will not allow you to do this out-of-the-box as the DB Context
allows execution of only one thread at a time. In this case, the obvious solution is to create each repo instance
in its own new DB Context
.
In your service class you might have lot of methods and we are un-sure that at what time which methods will execute in parallel. Also, the methods which participates in parallel execution may change over time based on the business requirement and performance measures. In the scenario's like this you can always take help of delegates and solve it in a more maintainable manner. Here is the code sample -
public abstract class BaseService<Entity> where Entity: class
{
private bool isConcurrencyEnabled = false;
private IRepository<Entity> _repo = null;
protected IRepository<Entity> Repo
{
get
{
if (this.isConcurrencyEnabled)
{
return RepoFactory.GetRepo<Entity>();
}
else
{
return this._repo;
}
}
}
public BaseService(IDbContext dbContext)
{
this._repo = dbContext.GetRepository<Entity>();
}
protected T ExecuteInParallel<T>(Func<T> action)
{
try
{
this.isConcurrencyEnabled = true;
return action();
}
finally
{
this.isConcurrencyEnabled = false;
}
}
}
The concrete service class can use it like this -
public class MyService: BaseService<MyData>
{
public MyService(IDbContext dbContext)
: base(dbContext)
{ }
public List<MyData> GetMyData(List<int> ids)
{
if(ids == null)
{
throw new ArgumentNullException("The ids cannot be null");
}
if(ids.Count > 5)
{
throw new ArgumentException("The number of elements should not exceed 5");
}
return this.ExecuteInParallel(() =>
{
ConcurrentBag<MyData> myData = new ConcurrentBag<MyData>();
ids.AsParallel().ForAll(id => myData.Add(this.GetMyDataById(id)));
return myData.ToList();
});
}
public MyData GetMyDataById(int id)
{
return this.Repo.Find(x => x.Id == id).FirstOrDefault();
}
}
The consumer of MyService
can call GetMyDataById
multiple times with various ids one by one (which re-uses the context) or can make use of GetMyData
by providing a list of ids which executes parallelly (creates new context for each repo call). This particular example does not make much sense but this is just to give an idea of how we can achieve Using
pattern with delegate. In reality it can be implemented in a greater extent and it is very powerful! possibly I will write a dedicated article on this topic.
Note: The exception is not handled in the above code for brevity but in real projects you must handle it.
Points of Interest
Here we have seen how we can solve various problems using delegates. At the same time, it warns you that these are not some of the out-of-box solution. The extensive usage of delegates makes your code more difficult to debug and understand. You need to apply them thoughtfully. The key thing to notice here is that, we are applying delegate to those part of the code which varies. If you keep this in mind, you will simply know where and when to apply it.
History
August 24, 2015 - Initial Version