To understand Double Dispatch, understanding of Overloading and Overriding is must. I already talked about Overloading and Overriding in post Overloading & Overriding.

One level of virtual dispatching

derived types override a base types: as shown below

        class Farms
        {
            public virtual void Irrigation()
            {
                Console.WriteLine("Farm Type");
            }
        }

        class WheatFarm : Farms
        {
            public override void Irrigation()
            {
                Console.WriteLine("WheatFarm");
            }
        }

        class RicaFarm : WheatFarm
        {
            public override void Irrigation()
            {
                Console.WriteLine("RicaFarm");
            }
        }


        static void Main(string[] args)
        {
            var a = new Farms();
            var b = new WheatFarm();
            var c = new RicaFarm();
            a.Irrigation();
            b.Irrigation();
            c.Irrigation();
        }

        //output
        Farm Type
        WheatFarm
        RicaFarm

Two level of virtual dispatching (Double Dispatch)

This concept is used in Visitor Design Pattern.

use polymorphic static binding technique to ensure that proper overload is called

        class Crop
        {
            public virtual void CropName(CropWatering obj)
            {
                obj.WaterSupply(this);
            }
        }

        class Wheat : Crop
        {
            public override void CropName(CropWatering obj)
            {
                obj.WaterSupply(this);
            }
        }

        /* An example of overloading (Method with same name but different parameter type)
         * */
        class CropWatering
        {
            public  virtual void WaterSupply(Crop crop)
            {
                Console.WriteLine("CropWatering working on type Crop");
            }

            public  virtual void WaterSupply(Wheat wheat)
            {
                Console.WriteLine("CropWatering working on type Wheat");
            }
        }

        class CropWatringSpring : CropWatering
        {
            public override void WaterSupply(Crop crop)
            {
                Console.WriteLine("CropWatringSpring working on type Crop");
            }

            public override void WaterSupply(Wheat wheat)
            {
                Console.WriteLine("CropWatringSpring working on type Wheat");
            }
        }


        static void Main(string[] args)
        {
            Crop crop = new Crop();
            Wheat wheat = new Wheat();
            CropWatering cropWatering = new CropWatering();
            crop.CropName(cropWatering);
            wheat.CropName(cropWatering);

            CropWatringSpring cropWatringSpring = new CropWatringSpring();
            crop.CropName(cropWatringSpring);
            wheat.CropName(cropWatringSpring);
        }

        //output
        CropWatering working on type Crop
        CropWatering working on type Wheat
        CropWatringSpring working on type Crop
        CropWatringSpring working on type Wheat

Reference