Depending on your entry point to delegates, the documentation might look a tad confusing at first. For me, it was The delegate type section of the C# language reference. It throws around terms like Action, Func, Events, custom delegate types. Predicate is also related to this topic, and this was what I was looking for.
Let me briefly explain what all those words mean and how they relate to
delegate
. Then I will explain why I was looking into this.
Short teaser: “Named Predicate”, like a Hibernate Named Query.
What is a Delegate?
Let me quote the language reference I have linked earlier because it is a good short summary.
“A delegate is a reference type that can be used to encapsulate a named or an anonymous method.”
It is a C# type with a specific signature, like any C# method, and you can assign anonymous or named methods to the delegate that match the signature. C/C++ programmers of yore would call it a function pointer. A delegate is the central concept behind Action, Func, and Predicate.
public delegate string Concatenate(int numberOne, int numberTwo);
In this example, the delegate’s “type name” is Concatenate, and it expects two int parameters and returns a string. There exist several ways to use it:
- Explicitly create an instance with
new Concatenate()
and pass the function that shall be executed as the constructor parameter. - Create an instance by assigning an anonymous method.
- Create an instance by assigning a lambda.
- Create an instance by assigning a (static) method.
Here is one of the options, a lambda.
Concatenate delegateLambda = (first, second) => $"{first}{second}"
string theAnswer = delegateLambda(4, 2);
For a long explanation containing all the rules, consult the C# programming guide.
How do Action, Func, Predicate fit into this?
A delegate only works when it was defined first. You cannot use it inline like this.
public void MethodWithDelegateParam(delegate string Concatenate(int numberOne, int numberTwo))
{
...
}
This is required instead.
class Delegator
{
public delegate string Concatenate(int numberOne, int numberTwo);
public void MethodWithDelegateParam(Concatenate concatDelegate)
{
concatDelegate(4, 2);
}
}
This is where Action, Func, and Predicate come in. All are of type delegate and serve a specific purpose. You can use these types inline to define any method signature you want, as method parameters and as member or local variables.
-
Action()
: A function without a return type and zero to sixteen parameters. -
Func<out TResult>()
: A function with a return type and zero to sixteen parameters. -
Predicate<in T>()
: A “special Func” that always returns bool and accepts one parameter.A predicate is mainly (maybe even exclusively) used in “find” methods or LINQ, where lambdas are used to reduce the result set to what is wanted.
That is all there is to it. Like my custom Concatenate delegate, Action, Func, and Predicate are predefined delegates that come with the .NET SDK. After I had figured that all out, everything made sense and was clear to me.
What I wanted to do
I was having a discussion with my sister about how to refactor some code she was working on. A big class was handling data, and the specific part in question dealt with reducing a result set to a single entry, either by id or name – almost like your run-of-the-mill generic tutorial sample using a Person class.
There was some data source, and it supported querying by using a predicate. My GitHub example emulates this with the HeroRepository class and a static list of Hero objects.
The question we were pondering was how to implement the two queries that are
required. Define specific methods that use the data source, use the data source
where the data is needed, or try to come up with a generic approach that
requires only one “GetPerson” method and accepts a Predicate
? Can such a
predicate be defined as a “Named Predicate” used multiple times with different
query parameters, like a Hibernate Named Query?
And this is why I started perusing the C# documentation about delegates and predicates and stumbled across Action and Func in the process.
The initial naïve idea was to define two static readonly Predicate<Hero>
instances at the class level.
First, a little bit of setup.
private HeroRepository repository;
private Hero GetHero(Predicate<Hero> predicate)
{
return repository.GetBy(predicate);
}
In a random function, I want to do something like this.
public void DoSomething()
{
// Stuff
var hero = GetHero(NamePredicate, "Batman");
// More stuff
}
How can I get there? Not with a predicate because the Predicate
contains the
complete logic, including the value required to find a specific hero. Let me
rewrite the above method call with a lambda, which should make the dilemma more
clear.
var hero = GetHero((hero) => hero.Name == "Batman");
How can I define this at the class level to reuse it multiple times with
different values for “Name”? This is where Func
comes in. And this is also
where it gets complicated.
static readonly Func<string, Predicate<Hero>> NameFuncPredicate =
(name) => (hero) => hero.Name == name;
// Usage in DoSomething
var hero = GetHero(NameFuncPredicate("Batman"));
I have defined a Func
delegate that accepts a parameter, the hero’s name, and
returns the Predicate<Hero>
for use in GetHero()
. While this looks cool and
awesome and makes me seem bright, it is also a very complicated way of writing a
simple method.
private Predicate<Hero> NamePredicate(string name)
{
return (hero) => hero.Name == name;
}
And this can be compressed into a one-liner, too, only less complicated.
private Predicate<Hero> NamePredicate(string name) =>
(hero) => hero.Name == name;
The end result is that a method is basically always required if I want to give a
name to a simple query Predicate
that is reusable. To take this finding
further, a generic GetHero()
method that requires defining two additional
methods, one to query-by-id and one to query-by-name, should be simplified to
just the two query methods.
Instead of doing this…
private Hero GetHero(Predicate<Hero> predicate)
{
return repository.GetBy(predicate);
}
private Predicate<Hero> NamePredicate(string name)
{
return (hero) => hero.Name == name;
}
private Predicate<Hero> IdPredicate(int id)
{
return (hero) => hero.Id == id;
}
… it makes more sense to do this.
private Predicate<Hero> GetHeroByName(string name)
{
return repository.GetBy((hero) => hero.Name == name);
}
private Predicate<Hero> GetHeroById(int id)
{
return repository.GetBy((hero) => hero.Id == id);
}
I hope you found this helpful, and I thank you for reading.