C# Delegate, Action, Func, Predicate Explained

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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.