ARTICLE

A Strategy for Using Delegates in C#

Posted by Matthew Cochran Articles | Coding Best Practices June 15, 2007
In a recent article comment I was asked for a sample of how to use a delegate and so I wanted to put together a sample of how we can use delegates in order to implement a strategy pattern in a way that requires much less code that is easier to maintain than when approaching the same solution deriving sub classes using an OOP implementation.
Reader Level:
Download Files:
 

Let's look at two ways of implementing a basic strategy pattern for a Calculator class that performs a calculation implemented as a strategy. I'll attempt to make the surface areas of each class the same so we can look at the different requirements needed to implement the pattern through traditional OOP and then by using delegates.

To start, we will be using a common enumerator to define the different methods our Calculator class can perform:

    public enum CaluclationType
    {
        Add = 0,
        Subtract = 1,
        Multiply = 2,
        Divide = 3
    }

When we implement, we want to code that looks like the following where we set the calculation strategy using the previously CaluclationType  enum:

            Calculator calc = new Calculator(CaluclationType.Add);
            Console.WriteLine(calc.GetValue(1.2, 3.4));

            calc.CalculationType = CaluclationType.Subtract;
            Console.WriteLine(calc.GetValue(1.2, 3.4));

            calc.CalculationType = CaluclationType.Multiply;
            Console.WriteLine(calc.GetValue(1.2, 3.4));

            calc.CalculationType = CaluclationType.Divide;
            Console.WriteLine(calc.GetValue(1.2, 3.4));

Part I. OOP Implementation

To get started with the OOP implementation of our solution we'll need an interface that defines the strategy we are implementing:

    public interface ICalculate
    {
        double Calculate(double inputA, double inputB);
    }

And then use this interface in all the different calculations we'll be needing:

    public class Adder : ICalculate
    {
        public double Calculate(double inputA, double inputB)
        {
            return inputA + inputB;
        }
    }

    public class Subtractor : ICalculate
    {
        public double Calculate(double inputA, double inputB)
        {
            return inputA - inputB;
        }
    }

    public class Multiplier : ICalculate
    {
        public double Calculate(double inputA, double inputB)
        {
            return inputA * inputB;
        }
    }

    public class Divider : ICalculate
    {
        public double Calculate(double inputA, double inputB)
        {
            return inputA / inputB;
        }
    }

Next, we'll define our OopCalculator class that executes the strategy.  The constructor is going to be passed the CaluclationType  enum.  We need to keep member variables for the strategy and the enumerator and we'll have a helper method to instantiate the correct class implementing our ICalculate strategy.

    public class OopCalculator
    {
        public OopCalculator (CaluclationType type)
        {
            m_calculator = GetCalculation(type);
        }

        private ICalculate m_calculator;
        private CaluclationType m_calculationType;

        public CaluclationType CalculationType
        {
            get { return m_calculationType; }
            set
            {
                m_calculationType = value;
                m_calculator = GetCalculation(value);
            }
        }

        private ICalculate GetCalculation(CaluclationType type)
        {
            switch (type)
            {
                case CaluclationType.Add: return new Adder();
                case CaluclationType.Subtract:return new Subtractor();
                case CaluclationType.Multiply: return new Multiplier();
                case CaluclationType.Divide: return new Divider();
                default: throw new ArgumentException("Unexpected CalculationType");
            }
        }

        public double GetValue(double inputA, double inputB)
        {
            return m_calculator.Calculate(inputA, inputB);
        }
    }

Ok, so in the end we are left with one main class, one interface (which could have been an abstract base class as well), four strategy classes and our main class all in approximately 100 lines of code.

Part II. Delegate implementation

In our delegate implementation, we'll be using a delegate in place of the strategy interface so let's first define a really generic delegates as follows:

    public delegate TOutput Calculate<TOutput, TInput, TInput2>(TInput input1, TInput2 input2);

       And the only other thing we'll be needing is the implementing class.  It will have the same constructor as our OopCalculator and be implemented exactly the same. We'll have a switch statement just like in the OOPCalculator to get the correct strategy.  The main difference is that all the methods containing the calculations will be internal to the class which we'll be wiring up with delegates:

    public class Calculator
    {
        public Calculator(CaluclationType cType)
        {
            m_calculationType = cType;
            m_calculator = GetCalculation(cType);
        }

        private CaluclationType m_calculationType;
        private Calculate<double, double, double> m_calculator;

        public CaluclationType CalculationType
        {
            get { return m_calculationType; }
            set
            {
                m_calculationType = value;
                m_calculator = GetCalculation(value);
            }
        }

        public double GetValue(double inputA, double inputB)
        {
            return m_calculator(inputA, inputB);
        }

        private Calculate<double, double, double> GetCalculation(CaluclationType value)
        {
            switch (value)
            {
                case CaluclationType.Add: return AddMethod;
                case CaluclationType.Subtract: return SubtractMethod;
                case CaluclationType.Multiply: return MultiplyMethod;
                case CaluclationType.Divide: return DivideMethod;
                default: throw new ArgumentException("Unexpected CalculationType");
            }
        }

        #region Methods

        private static double AddMethod(double input1, double input2)
        {
            return input1 + input2;
        }

        private static double SubtractMethod(double input1, double input2)
        {
            return input1 - input2;
        }

        private static double MultiplyMethod(double input1, double input2)
        {
            return input1 * input2;
        }

        private static double DivideMethod(double input1, double input2)
        {
            return input1 / input2;
        }

        #endregion

    }

So in the end using the delegate approach we are left with one main class, one delegate (which can be reused because it's so generic) and around 60 lines of code.  That's significantly less code, classes and resulting complexity to keep track of because we no longer have to have an individual object representing each strategy.

Hopefully this gives you some insight as to the power and usefulness of delegates.

Until next time,
            Happy coding

Login to add your contents and source code to this article
post comment
     

HI I am very new for c#, but I just want to know what if i want to do same coding in Window Application instead of console ...by using all the classes and methods...please help me,..i really need to know. thankyou!

Posted by chanda Dec 15, 2009

Huy, I wanted to know whether, when executing inside a delegate (i.e. inside the method that has been invoked by the delegate method), does it block C# events from being fetched by the program, until the delegate completes it's execution..?

Posted by Bathiya Lakmal Feb 11, 2009

I definately agree that duplicate code smells bad. However, in this case we would have to make a decision on performance vs. maintainability. There are three main ways I can think of to handle this...

1) as is -- least maintainable
2) use a Set() method to consolidate the repeated code into one method. The is the most maintainable way, but is also the least performant.
3) use the object's property to consolidate the repeated code. I agree that this feels wrong but it consolidates the code making it more maintainable. There is something I don't like about accessing a property of an object that is partially built in memory. It seems unsafe and there are situations where it could backfire on more complex objects. In this case, I could live with it, but would not particularly like it.

below is some code to run perf tests.

Calculator -- directly setting member variable
Calculator2 -- using object's property setter
Calculator3 -- using common Set() method for code consolidation

Here are my test timer results:

Calculator constructor: 709
Calculator2 constructor: 817
Calculator3 constructor: 830
Calculator property accessor: 560
Calculator2 property accessor: 560
Calculator property accessor: 748

Directly accessing the member variable is the fastest (but then we have possible maintenance issues). Using the object's property setter is the next fastest. Consolidating using the common set method is the slowest (because two calls go on the stack when the object's setter property is called).

So my general thought is that "it depends". Generally I'd favor maintainability but if it is a high-throughput app, we'd have to go with the fastest version.

--------------------------------


class Program
{
private const int LOOP_COUNT = 10 * 1000 * 1000;

static void Main(string[] args)
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();

#region Instantiation Tests

Calculator c;

watch.Start();

for(int i=0; i {
c = new Calculator(CaluclationType.Add);
}

watch.Stop();

Console.WriteLine("Calculator constructor: " + watch.ElapsedMilliseconds);

watch.Reset();

Calculator2 c2;

watch.Start();

for (int i = 0; i < LOOP_COUNT; i++)
{
c2 = new Calculator2(CaluclationType.Add);
}

watch.Stop();

Console.WriteLine("Calculator2 constructor: " + watch.ElapsedMilliseconds);

watch.Reset();

Calculator3 c3;

watch.Start();

for (int i = 0; i < LOOP_COUNT; i++)
{
c3 = new Calculator3(CaluclationType.Add);
}

watch.Stop();

Console.WriteLine("Calculator3 constructor: " + watch.ElapsedMilliseconds);

watch.Reset();


#endregion

#region Property Accessors

watch.Reset();

c = new Calculator(CaluclationType.Add);

watch.Start();

for (int i = 0; i < LOOP_COUNT; i++)
c.CalculationType = CaluclationType.Add;

watch.Stop();

Console.WriteLine("Calculator property accessor: " + watch.ElapsedMilliseconds);

watch.Reset();

c2 = new Calculator2(CaluclationType.Add);

watch.Start();

for (int i = 0; i < LOOP_COUNT; i++)
c2.CalculationType = CaluclationType.Add;

watch.Stop();

Console.WriteLine("Calculator2 property accessor: " + watch.ElapsedMilliseconds);

watch.Reset();

c3 = new Calculator3(CaluclationType.Add);

watch.Start();

for (int i = 0; i < LOOP_COUNT; i++)
c3.CalculationType = CaluclationType.Add;

watch.Stop();

Console.WriteLine("Calculator property accessor: " + watch.ElapsedMilliseconds);


#endregion


Console.ReadLine();

}
}

public class Calculator
{
public Calculator(CaluclationType cType)
{
m_calculationType = cType;
m_calculator = GetCalculation(cType);
}

private CaluclationType m_calculationType;
private Calculate m_calculator;

public CaluclationType CalculationType
{
get { return m_calculationType; }
set
{
m_calculationType = value;
m_calculator = GetCalculation(value);
}
}

public double GetValue(double inputA, double inputB)
{
return m_calculator(inputA, inputB);
}

private Calculate GetCalculation(CaluclationType value)
{
switch (value)
{
case CaluclationType.Add: return AddMethod;
case CaluclationType.Subtract: return SubtractMethod;
case CaluclationType.Multiply: return MultiplyMethod;
case CaluclationType.Divide: return DivideMethod;
default: throw new ArgumentException("Unexpected CalculationType");
}
}

#region Methods

private static double AddMethod(double input1, double input2)
{
return input1 + input2;
}

private static double SubtractMethod(double input1, double input2)
{
return input1 - input2;
}

private static double MultiplyMethod(double input1, double input2)
{
return input1 * input2;
}

private static double DivideMethod(double input1, double input2)
{
return input1 / input2;
}

#endregion

}

public class Calculator2
{
public Calculator2(CaluclationType cType)
{
CalculationType = cType;
}

private CaluclationType m_calculationType;
private Calculate m_calculator;

public CaluclationType CalculationType
{
get { return m_calculationType; }
set
{
m_calculationType = value;
m_calculator = GetCalculation(value);
}
}

public double GetValue(double inputA, double inputB)
{
return m_calculator(inputA, inputB);
}

private Calculate GetCalculation(CaluclationType value)
{
switch (value)
{
case CaluclationType.Add: return AddMethod;
case CaluclationType.Subtract: return SubtractMethod;
case CaluclationType.Multiply: return MultiplyMethod;
case CaluclationType.Divide: return DivideMethod;
default: throw new ArgumentException("Unexpected CalculationType");
}
}

#region Methods

private static double AddMethod(double input1, double input2)
{
return input1 + input2;
}

private static double SubtractMethod(double input1, double input2)
{
return input1 - input2;
}

private static double MultiplyMethod(double input1, double input2)
{
return input1 * input2;
}

private static double DivideMethod(double input1, double input2)
{
return input1 / input2;
}

#endregion

}

public class Calculator3
{
public Calculator3(CaluclationType cType)
{
SetCalculationType(cType);
}

private CaluclationType m_calculationType;
private Calculate m_calculator;

public CaluclationType CalculationType
{
get { return m_calculationType; }
set{ SetCalculationType(value); }
}

private void SetCalculationType(CaluclationType cType)
{
m_calculationType = cType;
m_calculator = GetCalculation(cType);
}

public double GetValue(double inputA, double inputB)
{
return m_calculator(inputA, inputB);
}

private Calculate GetCalculation(CaluclationType value)
{
switch (value)
{
case CaluclationType.Add: return AddMethod;
case CaluclationType.Subtract: return SubtractMethod;
case CaluclationType.Multiply: return MultiplyMethod;
case CaluclationType.Divide: return DivideMethod;
default: throw new ArgumentException("Unexpected CalculationType");
}
}

#region Methods

private static double AddMethod(double input1, double input2)
{
return input1 + input2;
}

private static double SubtractMethod(double input1, double input2)
{
return input1 - input2;
}

private static double MultiplyMethod(double input1, double input2)
{
return input1 * input2;
}

private static double DivideMethod(double input1, double input2)
{
return input1 / input2;
}

#endregion

}

Posted by Matthew Cochran Jul 20, 2007

I noticed in your delegate implementation in the constructor you use:
m_calculationType = cType;
m_calculator = GetCalculation(cType);

If you use:
CalculationType = cType;

You achieve the same thing with the added benefit that if you change the setter for CalculationType you don't have to remember to change your constructor as well. I often run into this same situation and don't know whether or not to access the property or always use the private field of that property when in a constructor.

Using the property itself (primarily for ease of maintainability) just feels "wrong" since you access the property of an object during the object's construction. But at times when the setter does several manipulations it also feels wrong to duplicate these line for line in the constructor. Additionally one must typically remember to duplicate any changes to the setter back to the constructor.

What are your thoughts?

Posted by Bart Jul 12, 2007

I think the best programmers really love what they do and have the drive and interest to keep learning and keep up with changing technologies.  The amount of time a person has been coding is a big factor in their skill level because they have had the opportunity to make lots of mistakes and fix and learn from them.

Knowing a lower level language like C where you have to deal with memory management really helps understand how/why things behave the way they do in higher level languages like C#. 

Knowing basic data structures and sorting/searching algorithms is also crucial to being a good developer.  I would suggest learning how to implement linked lists, doubly linked lists, stacks, queues, binary trees and basic sorting algorithms in C.

After getting the basics down, understanding the object oriented programming model is pretty important (inheritance, abstract classes, etc) and then knowing what design patterns are, when to use them, and more importantly knowing when not to.

There are tons of smart people teaching and writing out there to learn from as well. A formal education really helps and picking up books/periodicals to learn from is just as important.

Having talent helps, but I'm in my experience it seems that passion and drive win out over talent almost every time because without struggle you don't get better.  Sometimes talented people never learned to struggle and thus develop their skills. I've seen many talented people stagnate in their growth and get passed up by people who just have more more drive.  Of course, there are the rare few who are both talented and driven and they are the ones that usually are tryly outstanding in their field.

I think anyone who loves doing something and is persistant and dedicated will end up being good, maybe even great.

Posted by Matthew Cochran Jun 23, 2007
COMMENT USING
PREMIUM SPONSORS
DynamicPDF™ product line allows you to dynamically generate PDF documents, merge PDF documents and add new content to existing PDF documents from within your applications.
Get Career Advice from Experts
SPONSORED BY
  • PDF reports have never been easier to create. With our included WYSIWYG Designer, you can layout your reports, set up your data source and let DynamicPDF ReportWriter do the rest.
Join a Chapter