C# Callbacks with Interfaces and Delegates

10/6/2009

This article gives an example of a callback implemented two ways: with a delegate and with an interface. First I quickly describe interface polymorphism and a typical example of it's use in software design. This will give you some background on the interface implementation of the callback. I have another article on delegates which you might read before continuing here.

C# Interface Polymorphism

When you're beginning to learn about interfaces in C#, you might read about how they're C#'s answer to multiple inheritance or how they're a way to form a contract between objects related by interface inheritance. Really, the most interesting thing about interfaces is the polymorphism that they allow, an aspect heavily used by various design patterns in efforts to achieve low coupling between objects. I suppose beginning books leave this out because they they don't want to get too deep into design concepts.

Interfaces are appropriately named, as they allow you to plug in different implementations for a certain task. This short console app demonstrates how you can switch between LINQ and SQL to save data, let's say a person object, just by changing one line of code. That is, once you've written the interface and all the LINQ and SQL code.

In the code below, we change the instantiaion of the repository object, leaving the call to SavePerson, repository.SavePerson(person), untouched. The key to all this is that an object of a class can be instantiated using not only the class type but also the type of any interface it implements. So, in this example, because both the Linq and Sql classes implement the same IRepository interface, the repository object will be of type IRepository, whether it's an object of the Linq OR the SQL class. The repository object is polymorphic because it can refer to the Linq code or the SQL code, depending on how it's created. And remember I mentioned earlier about a contract? Any class that implements an interface must include all the methods that are in that interface. So, in this case, IRepository has a SavePerson method and so must both SQLRepository and LinqRepository. When you swap one out for the other, you know that you'll still have a SavePerson method so you won't have to change that code as well. (This assumes the SavePerson do the same thing, just in different ways.)

You might be complaining, "But where's the benefit, the loose coupling? I still have to edit Main(), change the instantiation of repository." That's exactly right. In the real world, the repository object would be injected into the method, Main() in this example, so you wouldn't be changing Main at all. Main() would be instead, Main(IRepository repository). Here all I wanted to do was show interface polymorphism. There are several ways to inject an object into a method allowing you to achieve loose coupling between your business/domain code and your data access code, (and make testing easier), but that's another story. You might look up test driven design, dependency injection and IoC containers. For dependecny injection, and more, Martin Fowler's site is a don't miss: http://martinfowler.com/articles/injection.html.

using System;
 
namespace RepositoryInterface
{
    class Program
    {
        static void Main(string[] args)
        {
            IRepository repository = new SQLRepository();
            //IRepository repository = new LinqRepository();
            Person person = new Person();
            repository.SavePerson(person);
            Console.ReadKey();
        }
    }
 
    public interface IRepository
    {
        void SavePerson(Person person);
    }
 
    public class SQLRepository : IRepository
    {
        public void SavePerson(Person person)
        {
            Console.WriteLine("Saving Person using SQL");
        }
    }
 
    public class LinqRepository : IRepository
    {
        public void SavePerson(Person person)
        {
            Console.WriteLine("Saving Person using LINQ");
        }
    }
 
    public class Person
    {
        string Name { get; set; }
    }
}

Callbacks

Now I'll show another example of interface polymorphism with the use of a callback. Below is windows forms app consisting of two forms and a static class, DoWork. Each form calls DoWork, sending it a reference to itself as a parameter, which DoWork uses to callback to the calling form to perform methods located there. DoWork uses the same line of code to callback to both forms; it uses the same reference parameter name for both callbacks. This ability is not so mysterious when you understand that the reference parameter is an address of a method back in the calling form. The parameter name in DoWork is just a function pointer, or in C# lingo, a delegate. (The name pointer was much better than delegate, but I guess it was already taken by C). Slightly more mysterious is the same callback design implemented using interface polymorphism, brought to us by the C# compiler. I show both in this example.

I should stop and talk a bit about why a callback design makes sense, though as I'm just learning all this stuff, I'm stepping out of my league a bit here. Far as I can tell, callbacks are mostly used to uncouple parts of an application. In this case the caller is uncoupled from the callee.

Let's say that the two forms had some code in them that was almost identical. We'll pull it out of the forms and put it in the DoWork class. (So we're getting some code reuse benefit here too.) But I said almost identical. There's one little thing that's peculiar to each of the forms. In this case it might make sense for DoWork to call back to it's calling class to perform that peculiar bit of work. This way, DoWork doesn't need some ugly conditional statements like if form1 called me do this else if form2 called me do that. This way too, another form that's similar to forms 1 and 2 can be added to the mix and DoWork doesn't need to be changed. Also, if one of the forms changes, and the code particular to the form is updated, DoWork doesn't need to be touched.

C# Delegates

I talk about Delegates in another page, but I thought it was appropriate to include them in a callback article, even if it may muck up the interface waters slightly. And I talk about events in yet another. These three articles are closely related, each emphasizing different aspects of the C# delegate world.

As you'll see in my example below, the use of a delegate for a callback is actually easier and maybe cleaner. But if the specific stuff needed in each caller class is extensive, say there are multiple methods that the helper calls back to, an interface helps keep it more organized. Also, with an interface, one reference parameter takes care of multiple specific methods back in the caller class. With delegates you need to have a separate parameter for each method to be called back to. (You could pass multiple delegate params or a list of them, but still, that seems cumbersome.) Also, an interface is nice because it forces a contract between DoWork and the specialized stuff back in the calling classes but like I said before, to me, it's the polymorphism that's interesting and particularly useful.

Enough yammering, here's the example. A quick overview of the example:

You might create a Windows Forms app with 2 forms an interface and a class and copy in the code below. Then compile and have at it; tracing and changing stuff like making the DoWork class an instance class, just so get a good feel for it.

Form1 Class

using System;
using System.Windows.Forms;
 
namespace Callbacks
{
    // Notice that Form1 implements the ISpecializedStuff Interface.
    public partial class Form1 : Form, ISpecializedStuff
    {
        public Form1()
        {
            InitializeComponent();
        }
       
        // Exercise the delegate implementation of a callback
        private void button1_Click(object sender, EventArgs e)
        {
            //If DoWork were an instance class, not static.
            //DoWork doWork = new DoWork();  
            //doWork.DoWorkWithDelegate(DoSpecializedWork);
            //In this example, DoWork is static
            DoWork.DoWorkWithDelegate(DoSpecializedWork);
        }
        
        // Below this point is the code for the interface implementation.
        private void button2_Click(object sender, EventArgs e)
        {
            DoWork.DoWorkWithInterface(this);            
        }
 
        // Implementation of the DoSpecializedWork method of the 
        // ISpecializedStuff interface
        public void DoSpecializedWork()   
        {
            MessageBox.Show("Doing Stuff Specific to form1.");
        }
 
        // Implementation of the DoMoreSpecializedWork method of the 
        // ISpecializedStuff interface
        public void DoMoreSpecializedWork()   
        {
            MessageBox.Show("Doing More Stuff Specific to form1.");
        }
 
        //Bring up Form2
        private void button3_Click(object sender, EventArgs e)
        {            
            Form2 form2 = new Form2();
            form2.ShowDialog();
        }        
    }
}

Form2 Class

using System;
using System.Windows.Forms;
 
namespace Callbacks
{
    public partial class Form2 : Form, ISpecializedStuff
    {
        public Form2()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {            
            DoWork.DoWorkWithDelegate(DoSpecializedWork);
        }
 
        // Below this point is the code for the interface implementation.
        private void button2_Click(object sender, EventArgs e)
        {
            DoWork.DoWorkWithInterface(this);
        }
 
        //Implement the Interface methods
        public void DoSpecializedWork()   
        {
            MessageBox.Show("Doing Stuff Specific to form2.");
        }
        public void DoMoreSpecializedWork()   
        {
            MessageBox.Show("Doing More Stuff Specific to form2.");
        }
    }
}

Interface

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Callbacks
{
    // Here's the interface, ISpecializedStuff, which will contain
    // 2 method declarations, representing the 2 behaviors that
    // are different in each of the calling classes, form1 and form2.    
    public interface ISpecializedStuff
    {
        void DoSpecializedWork();
        void DoMoreSpecializedWork();
    }
}

DoWork Class

using System;
using System.Windows.Forms;
 
namespace Callbacks
{
    public static class DoWork
    {
        ////////////////////////////////////////////////////////////////////////
        // Here at the top is the delegate implementation of a callback.
        ////////////////////////////////////////////////////////////////////////
 
        // Declare a delegate object that can be used to reference any method
        // with a signature that returns void and accepts no parameters.
        public delegate void MyCallbackDelegate();
 
        // DoWorkWithDelegate does the common stuff. It gets a reference to a method back
        // in the calling class that does stuff specific to that particular class.
        public static void DoWorkWithDelegate(MyCallbackDelegate doSpecializedWork)
        {
            MessageBox.Show("Doing Common Stuff in DoWorkWithDelegate");
            doSpecializedWork();
            MessageBox.Show("Doing More Common Stuff in DoWorkWithDelegate");
        }
 
        ////////////////////////////////////////////////////////////////////////
        // Below this point is the code for the interface 
        // implementation of a callback.
        ////////////////////////////////////////////////////////////////////////
 
      
        // Here's where polymorphism comes in. Both forms call this method, each
        // passing in a reference to itself.  DoWorkWithInterface can declare the 
        // form reference of type ISpecializedStuff because both forms implement
        // ISpecializedStuff.  So, we only need one method to deal with both forms,
        // even though the forms differ in behavior.
        public static void DoWorkWithInterface(ISpecializedStuff callingForm)
        {
            MessageBox.Show("Doing Common Stuff in DoWorkWithInterface");
            callingForm.DoSpecializedWork();
            MessageBox.Show("Doing More Common Stuff in DoWorkWithInterface");
            callingForm.DoMoreSpecializedWork();
            MessageBox.Show("Doing Still More Common Stuff in DoWorkWithInterface");
        }
    }
}