Friday, 13 April 2018

Understanding Delegates in C#

Background

Delegates are a very important part of C# programming language. A delegate in C# holds a reference to a method. Think of it as a pointer to the method. This reference or pointer can change at runtime. A delegate can also reference multiple methods and can invoke them all together. Think about event listeners for a Button in UI. You can add as many event listeners as you want and when the button is clicked all methods get the callback - much like the Observer Design Pattern. In this post well see how delegates work with an example.

Understanding Delegates in C#

Syntac of a delegate declaration is -
  • delegate <return type> <delegate-name> <parameter list>
For eg. consider -
  • public delegate void PriceChangeDelegate(int oldPrice, int newPrice); 

Now this delegate can point to any method that has return type void and parameters list (int, int). We will see a demo in a while. But the declaration syntax should be clear now.

So for this demo idea is to define an Item Object and with an attribute call price and we want to provide our own callback to receive updates whenever price of the item changes. We will do this using a delegate.

So let's first define our Item class -

namespace HelloWorld
{
    class Item
    {
        private int price;
        public PriceChangeDelegate PriceChanged;
        public int Price {
            get { return price; }
            set {
                if(value != 0)
                {
                    if(value != price)
                    {
                        PriceChanged(price, value);
                    }
                    price = value;
                }
            }
        }
    }
}



Notice how have created a private variable price (lower case by convention) and then a public property called Price (uppercase by convention) to encapsulate the price. Set method of this property is where our logic is. We are calling the delegate  PriceChanged when the old value price is different than current value. Also, notice how we have declared a public delegate member in class -
  • public PriceChangeDelegate PriceChanged;
 However, we have not yet declared our delegate type PriceChangeDelegate anywhere. Let's do that now -

namespace HelloWorld
{
    public delegate void PriceChangeDelegate(int oldPrice, int newPrice);
}


And there we go. Now our delegate instance can point to any methods of return type void and parameters (int, int) like we saw before. Now let's see all of this in action.


namespace HelloWorld
{
    class Program
    {
        public static void Main(string[] args)
        {
            Item item = new Item();
            item.PriceChanged = new PriceChangeDelegate(ItemPriceChangeCallback);
            item.Price = 100;
            Console.ReadLine();
        }
        public static void ItemPriceChangeCallback(int oldPrice, int newPrice) {
            Console.WriteLine("Item price changed from " + oldPrice + " to " + newPrice);
        }
    }
}


Here we are creating an instance of Item and setting a value for price to 100. Note the default value of primitive int is 0. So now when we set it to 100 we are essentially changing the price of the item. Also before that, we are assigning the delegate to the method we have locally called ItemPriceChangeCallback. So when items price will be changed ItemPriceChangeCallback will be called and our statement should get printed. Let's give it a try.




So that worked! Now like I said before a delegate can reference multiple methods as long as return type and paramters are same. Let's see how we can do that. Let's change our previous code a bit.


namespace HelloWorld
{
    class Program
    {
        public static void Main(string[] args)
        {
            Item item = new Item();
            item.PriceChanged += new PriceChangeDelegate(ItemPriceChangeCallback1);
            item.PriceChanged += new PriceChangeDelegate(ItemPriceChangeCallback2);
            item.Price = 100;
            Console.ReadLine();
        }
        public static void ItemPriceChangeCallback1(int oldPrice, int newPrice) {
            Console.WriteLine("ItemPriceChangeCallback1 :: Item price changed from " + oldPrice + " to " + newPrice);
        }
        public static void ItemPriceChangeCallback2(int oldPrice, int newPrice)
        {
            Console.WriteLine("ItemPriceChangeCallback2 : Item price changed from " + oldPrice + " to " + newPrice);
        }
    }
}


and the output is -


Notice our syntax for assigning delegate has changed. Instead of assigning we are adding it now -

  • item.PriceChanged += new PriceChangeDelegate(ItemPriceChangeCallback1);
  • item.PriceChanged += new PriceChangeDelegate(ItemPriceChangeCallback2);
instead of

  • item.PriceChanged = new PriceChangeDelegate(ItemPriceChangeCallback);

 We have defined two methods ItemPriceChangeCallback1 and ItemPriceChangeCallback2 to show this behavior. You can have as many as you wish as long as delegate contract in honoured - same return type and parameters type and number.

Also note you need not use new keyword again and again. C# compiler is intellegent enough to do this itself. You can simply do.

  • item.PriceChanged += ItemPriceChangeCallback1;
  • item.PriceChanged += ItemPriceChangeCallback2;
 It would give the same result.

Lastly you can remove all references by setting delegate as null -
  • item.PriceChanged = null;
 You would get a Null reference exception since are using delegate without instantiating our delegate and it is null by default. But this is not a desired behavior. To avoid this we can define our delegate as event. For this go back to our Item class and change -
  • public PriceChangeDelegate PriceChanged;
to
  • public event PriceChangeDelegate PriceChanged;
 and you can see that assigning null to PriceChanged is no longer possible.



 And that's all for delegates. Post your questions in comments below. Thanks.



No comments:

Post a Comment

t> UA-39527780-1 back to top