I was reading a domain drive design PHP repository created by CodelyTv that is a model to create new solutions using this methodology when I found something interesting to perform in futures scenarios. I read the next piece of code:
use function Lambdish\Phunctional\repeat;
final class Repeater
{
public static function repeat(callable $function, $quantity): array
{
return repeat($function, $quantity);
}
public static function repeatLessThan(callable $function, $max): array
{
return self::repeat($function, IntegerMother::lessThan($max));
}
public static function random(callable $function): array
{
return self::repeat($function, IntegerMother::lessThan(5));
}
}
Firstly, this code is a helper class that is used in unit test cases. The Repeater class creates a random objects list using a callable function as a parameter and results are added into the list N times.
Therebefore we can execute random methods as follow:
Repeater::random(CourseIdMother::creator());
Repeater::random(IntegerMother::random());
In the first line, the result is a list with N elements resulting of CourseIdMother::creator()
and in the second line, there is a list with N elements of int type. It also exists another method called repeatLessThan
with which we can specify the max amount of objects that we want to store into the list.
Coming back to my introduction, I was reading this code when I thought that it is a good scenario to explain how we can use delegates or callable methods.
Delegate methods - Introduction.
A delegate method is a reference type variable that holds the reference to a method and it can be changed at runtime. It is especially used for implementing events and call-back methods.
There are three steps in defining and using delegates:
- Declaration of our delegate.
- Instantiation, creating the delegateβs object.
- Invocation, where we call a referenced method.
//Declaration
public delegate void WriterDelegate(string text);
class Program
{
public static void Write(string text)
{
Console.WriteLine(text);
}
static void Main(string[] args)
{
//Instantiation
WriterDelegate writerDelegate = new WriterDelegate(Write);
//Invocation
writerDelegate("Some example text.");
}
}
Also, C# have two built-in delegates: Func<T>
and Action<T>
Action is a kind of delegates method that it has up to sixteen parameters and it does not return any result. So, we can assign to this delegate only methods with the void return type.
Otherwise, Func has up to sixteen parameters and it returns a result. In other words, we use the Func delegate only with a method wich result type is other than void.
I do not want to extend this article with theory, but I recommend to check this documentation in order to understand how we can use Action and Func methods in a simple way.
Delegate methods - Example.
Helping by Generics in C#, we can create a similar random method, as it is shown in the first part of this article, using the Func sending as a parameter.
private static IEnumerable<T> Repeat(Func<T> method, int quantity)
{
for (int i = 0; i < quantity; i++)
{
yield return method();
}
}
With Repeat(Func<T> method, int quantity)
we can execute N times a Func and the result store it into an IEnumerable<T>
. The entire class Repeater<T>
results in:
using System;
using System.Collections.Generic;
using System.Linq;
public class Repeater<T>
{
private static IEnumerable<T> Repeat(Func<T> method, int quantity)
{
for (int i = 0; i < quantity; i++)
{
yield return method();
}
}
public static IEnumerable<T> RepeateLessThan(Func<T> method, int max)
{
return Repeat(method, IntegerMother.LessThan(max));
}
public static IEnumerable<T> Random(Func<T> method)
{
return Repeat(method, IntegerMother.LessThan(5));
}
}
It looks very similar than the original code. Finally, we can use the helper Repeater<T>
class in the following ways:
Repeater<CourseId>.Random(CourseIdMother.Random);
Repeater<int>.Random(IntegerMother.Random);
Repeater<string>.RepeateLessThan(StringMother.Random,20);
As we can see, the method generates a objects list executing a Func<T>
. It always returns an object of the same type indicated in the contract (CourseId in the first example).
Another example of delegate commonly used is the repository pattern with Expressions. We can create a Get method that executes a where sentence defined by another method and it returns an IQueryable object.
public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class
{
protected RepositoryContext RepositoryContext { get; set; }
public RepositoryBase(RepositoryContext repositoryContext)
{
this.RepositoryContext = repositoryContext;
}
public IQueryable<T> FindAll()
{
return this.RepositoryContext.Set<T>();
}
public IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression)
{
return this.RepositoryContext.Set<T>()
.Where(expression);
}
}
Using this approach we could send a where sentence created by LinQ expression as a parameter, as follow:
await FindByCondition(o => o.Id.Equals(ownerId))
.SingleAsync();
await FindByCondition(o => o.Age > 10 && o.Name.Contains(ownerName))
.SingleAsync();
There are many uses and potential scenarios for delegate methods in C# but this article shows a simple example to understand how we can use this kind of tools that the language provided.
Recommended resources.
Microsoft - Using Delegates
Why Delegates
Code Maze - Delegates
Top comments (2)
Firstly, you are not limited to Action and Func. Those are built in types in .NET, but you can declare your own delegates. Your article seems to sugguest Action and Func are the only options.
Secondly, you are NOT calling your func multiple times. It is calling it once and Enumerable is duplicating that first return value multiple times.
If you want to call the func multiple times, you should do something like:
Thirdly, your code doesn't offer anything practical. It is just an unnecessary wrapper around an Enumerable function.
I actually rarely write more than 20 lines of code before using delegates. Here are some practical examples of where I use delegates if you are curious...
You can use them to cache runtime compilation:
You can use them to abstract functionality to write generic and reusable code as I am doing in graph path finding algorithms here:
github.com/ZacharyPatten/Towel/blo...
You can use them to cache reflection:
gist.github.com/ZacharyPatten/16be...
I even use them in data structures. For example, I use a comparison delegate on my Heap data structure so that you can control how the heap compares it's items (source code available in same GitHub project).
Even "Array.Sort()" has an overload that takes a delegate. I use that one all the time.
Hello Zachary.
Thanks for your comment and feedback, it's very useful.
I've edited the post in order to fix the two first points that you mention.
Otherwise, this article doesn't show the best way to use delegates, instead of that it shows a very simple example that I saw in a public repository.