Delegate with a real-life example

delegate in c sharp

What is delegate?

Delegate is a function passed as a parameter to another function. When I say I am delegating work to someone else means I am forwarding my responsibility to others. This meaning stands corrected for delegates in programming as well. In C#, the caller passes the logic to callee and callee executes that logic.

Delegate Example:

The complete code of this example can be downloaded from https://github.com/Imadoddin/delegate-example

To understand this we will take an example of search functionality that searches posts based on different criteria. This functionality will be used by different clients where they can pass any search logic they want. Since we need to give clients the flexibility to pass any logic, we must use delegates.

  1. Create a class Post. Post class will have some properties like title, content, author and publish date.
  2. Create a class PostRepository to store posts.
  3. Create a class SearchService that have a method Search. Search method will take function as a parameter, basically a delegate and execute it.
  4. Create different classes that will call Search method from SearchService and pass different logic. We can call these classes as Clients because they will be using the service.

Post class:

Post class defines the structure of the post. Technically it contains different properties.

using System;

namespace SearchService
{
    class Post
    {
        public string Title { get; set; }
        public string Content { get; set; }
        public string Author { get; set; }
        public DateTime PublishDate { get; set; }
    }
}

Post repository class:

This class will have all the posts. In a real-life project, it will fetch data from some database or web service. For the sake of brevity, I have added a few static posts. You can add any number of posts to play around with this example.

using System;
using System.Collections.Generic;

namespace SearchService
{
    class PostRepository
    {
        public List<Post> Posts
        {
            get
            {
                return new List<Post> {
                    new Post {
                        Title = "Introduction to C#",
                        Content = "A programming language that can do so and so...",
                        Author = "Kurt",
                        PublishDate = new DateTime(2020, 11, 23)
                    },
                    new Post {
                        Title = "Sitecore CMS",
                        Content = "A content management system that can do so and so...",
                        Author = "Xavier",
                        PublishDate = new DateTime(2018, 03, 11)
                    },
                    new Post {
                        Title = "Introduction to Javascript",
                        Content = "A programming language for web...",
                        Author = "Donelle",
                        PublishDate = new DateTime(2020, 04, 27)
                    },
                    new Post {
                        Title = "CSS",
                        Content = "A stylesheet that makes html look beautiful...",
                        Author = "Kaelee",
                        PublishDate = new DateTime(2019, 07, 18)
                    }
                };
            }
        }
    }
}

Search Service:

This is the most important class. This class contains a method, Search, that will accept a delegate and perform logic based on that.

using System;
using System.Collections.Generic;

namespace SearchService
{
    class SearchService
    {
        public List<Post> Search(Func<Post, bool> searchCriteria)
        {
            var posts = new PostRepository().Posts;
            var filteredPosts = new List<Post>();

            foreach (var post in posts)
            {
                if(searchCriteria(post))
                {
                    filteredPosts.Add(post);
                }
            }

            return filteredPosts;
        }
    }
}

The Search method accepts a delegate parameter in the form of Func. Func is a way to define a delegate. There are other ways too like Action and Predicate. Coming back to the example, the Search method has a reference to PostRepository to get the list of posts. It will iterate over them and return only posts that pass the criteria provided by the caller and finally return them to the caller.

Please note that this class or method does not even know what logic it will be. It will execute whatever the caller provides. It just knows that method must have an input parameter of type Post and return boolean.

The last generic parameter to Func represents the return type. To know more about these generic delegates, please visit Action, Func, and Predicate.

Client implementation:

Now we have set all the required classes. It’s time to create some client classes. These classes will pass delegates (logic) to the SearchService and get back the result. To do this I will create an interface. This interface will have a method that will be implemented by client classes. This enforces some consistency in the project.

using System.Collections.Generic;

namespace SearchService
{
    interface IPostSearch
    {
        List<Post> SearchPosts(string searchKeyword);
    }
}

After we have defined the interface, we will create 3 classes that will implement it and call SearchService with the logic they want.

QuickSearch class:

Let’s first create a class, QuickSearch. QuickSearch class will search the posts based on title only. Since the title field is small and so results will be very few, so I named it QuickSearch.

using System.Collections.Generic;

namespace SearchService.Clients
{
    class QuickSearch : IPostSearch
    {
        string _searchKeyword;
        public List<Post> SearchPosts(string searchKeyword)
        {
            _searchKeyword = searchKeyword;
            return new SearchService().Search(SearchByTitle);
        }

        bool SearchByTitle(Post post)
        {
            return post.Title.ToLower().Contains(_searchKeyword.ToLower());
        }
    }
}

This class implements the SearchPosts method of the IPostSearch interface. This method calls the Search method of the SearchService class by providing a method as a parameter. Please note that the method that is passed as a parameter has the same input and return type as the delegate.

The method passed as a parameter must have the same input and return types as delegates. That makes delegates strongly typed.

When this code executes, SearchService will provide all the results that have search-keyword in their title field.

There is a better way of passing a parameter. You can pass the method body directly as a parameter. This will save us from creating another method. Let’s make slight modifications in the above class to pass delegate as a method body. Also known as the lambda expression.

using System.Collections.Generic;

namespace SearchService.Clients
{
    class QuickSearch : IPostSearch
    {
        public List<Post> SearchPosts(string searchKeyword)
        {
            return new SearchService().Search(
                (post) => {
                    return post.Title.ToLower().Contains(searchKeyword.ToLower());
                });
        }
    }
}

I personally like to pass delegates this way because it looks cleaner to me. I use a special method only when it should be re-used.

We are done with the first client, that wants SearchSerivce to provide results based on the title.

DetailedSearch class:

Let’s create another client that asks SearchSerivce to provide results if searchKeyword is present in the title or content.

using System.Collections.Generic;

namespace SearchService.Clients
{
    class DetailedSearch : IPostSearch
    {
        public List<Post> SearchPosts(string searchKeyword)
        {
            return new SearchService().Search(
                (post) => {
                    return post.Title.ToLower().Contains(searchKeyword.ToLower()) ||
                    post.Content.ToLower().Contains(searchKeyword.ToLower());
                });
        }
    }
}

PublishYearSearch class:

Now, let’s create a third client that wants to get results based on the published year of the post.

using System;
using System.Collections.Generic;

namespace SearchService.Clients
{
    class PublishYearSearch : IPostSearch
    {
        public List<Post> SearchPosts(string searchKeyword)
        {
            return new SearchService().Search(
                (post) => {
                    return post.PublishDate.Year == Convert.ToInt32(searchKeyword);
                });
        }
    }
}

Well, we are not done with creating the search service and its client.

Calling all of them together:

Let’s check what the results look like when we use all of them together. To do this, call them from the Main method of our console application.

using SearchService.Clients;
using System;
using System.Collections.Generic;

namespace SearchService
{
    class Program
    {
        static void Main(string[] args)
        {
            IPostSearch search;
            List<Post> posts;

            //Filter posts by using QuickSearch
            Console.Write("Enter search keyword for quick search: ");
            search = new QuickSearch();
            posts = search.SearchPosts(Console.ReadLine());
            DisplayPostDetails(posts);
            Console.Write("");

            //Filter posts by using DetailedSearch
            Console.Write("Enter search keyword for detailed search: ");
            search = new DetailedSearch();
            posts = search.SearchPosts(Console.ReadLine());
            DisplayPostDetails(posts);
            Console.Write("");

            //Filter posts by using PublishYearSearch
            Console.Write("Enter year to search the post: ");
            search = new PublishYearSearch();
            posts = search.SearchPosts(Console.ReadLine());
            DisplayPostDetails(posts);
            Console.Write("");

            Console.ReadLine();
        }

        private static void DisplayPostDetails(List<Post> posts)
        {
            if(posts.Count == 0)
            {
                Console.Write("No posts found");
                Console.Write("n-----------------------------------------------n");
                return;
            }

            foreach (var post in posts)
            {
                Console.WriteLine($"Post title: {post.Title}");
                Console.WriteLine($"Post content: {post.Content}");
                Console.WriteLine($"Post author: {post.Author}");
                Console.WriteLine($"Post publish date: {post.PublishDate.ToString("dd/MM/yyyy")}");
                Console.Write("-----------------------------------------------n");
            }
        }
    }
}

Here is what the results look like

delegates example in c-sharp

Types of delegate:

Delegates can be divided into 3 types.

  1. Simple delegate.
  2. Multi-cast delegates.

Simple delegate:

Simple delegate references only one method. We have used a simple delegate inside SearchService in the above example which points to a single method in different post search classes.

Multi-cast delegates:

Multi-case delegates reference one or more methods. It means, if you want to attach more functionality to a delegate at runtime then these delegates can be your option.

Let’s consider a simple example where we attach multiple behaviors to the same delegate. To do this, we will define a delegate in an old-school way. Remember, in the main example we used Func, which is a generic delegate. In this case, we have to use the exact delegate. Check out the below example.

public delegate void DoCoding();
static void Main(string[] args)
{
    DoCoding coding = TurnOnComputer;

    coding += OpenVS;
    coding += OpenProject;
    coding += StartCoding;

    ExecuteDailyRoutine(coding);
    Console.ReadLine();
}

static void TurnOnComputer()
{
    Console.WriteLine("turn on computer");
}

static void OpenVS()
{
    Console.WriteLine("Open visual studio");
}

static void OpenProject()
{
    Console.WriteLine("open project");
}

static void StartCoding()
{
    Console.WriteLine("start coding");
}

static void ExecuteDailyRoutine(DoCoding doCoding)
{
    doCoding();
}

This is a very basic example. Here we have created a delegate and an instance of it. We kept adding multiple behaviors to it and passed them as a parameter.

Here is the output.

turn on computer
Open visual studio
open project
start coding

Please note, we can add methods to the delegate using the “+” operator. Similarly, when we want to remove it, we use the “-” operator.

If methods have a return type, the output of the last invoked method will be returned by multi-cast delegates.

Please see the example.

public delegate int ReturnAndOut();
static void Main(string[] args)
{
    ReturnAndOut returnAndOut = Ret1;

    returnAndOut += Ret2;

    ExecuteDel(returnAndOut);
    Console.ReadLine();
}

static int Ret1()
{
    return 1;
}

static int Ret2()
{
    return 2;
}

static void ExecuteDel(ReturnAndOut returnAndOut)
{
    Console.WriteLine("Final value is:" + returnAndOut());
}

Here is the output

Final value is:2

Generic delegates:

Generic delegates are a special type of delegates. I have already explained it here.

Summary:

  1. Delegate provides a way to pass logic as a parameter to other methods.
  2. Delegates are strongly typed as they have strongly typed input and return that. User of these delegates must respect these types.
  3. A caller can pass parameters in 2 ways, as a separate method or as inline code block.
  4. Delegates are be chained together.
CategoriesC#

Leave a Reply

Your email address will not be published.

Coding Crest Back To The Top