Why do I need delegates in C #?

    image
    Thinking over the architecture of the next class, you understand that it would be very useful for you to pass a piece of executable code as an argument. This would allow you to avoid a string of ifs and cases and make your code more elegant. Girls would delightedly groan and certainly leave you their phone number in the comments. Ahem ... something I got carried away.

    So how is this done in C #? For example, you write a calculator and you have the simplest logic:
    public double PerformOperation(string op, double x, double y)
    {
    	switch (op)
    	{
    		case "+": return x + y;
    		case "-": return x - y;
    		case "*": return x * y;
    		case "/": return x / y;
    		default: throw new ArgumentException(string.Format("Operation {0} is invalid", op), "op");
    	}
    }
    


    This simple and elegant solution has the right to life, but it has some problems:
    • Software is changeable. Tomorrow you will need to add a take modulo and then you have to recompile the class. At certain stages of the project, this is an expensive pleasure for consumers of your class.
    • The code in its current form does not have any input checks. If you add them, then the switch indecently grow.



    What do my Israeli friends ma laasot say?

    First, you need to encapsulate the code in the function:
    switch (op)
    {
    	case "+": return this.DoAddition(x, y);
    	case "-": return this.DoSubtraction(x, y);
    	case "*": return this.DoMultiplication(x, y);
    	case "/": return this.DoDivision(x, y);
    	default: throw new ArgumentException(string.Format("Operation {0} is invalid", op), "op");
    }
    ...
    private double DoDivision(double x, double y) { return x / y; }
    private double DoMultiplication(double x, double y) { return x * y; }
    private double DoSubtraction(double x, double y) { return x - y; }
    private double DoAddition(double x, double y) { return x + y; }
    

    Secondly, you need to completely get rid of the switch:
    private delegate double OperationDelegate(double x, double y);
    private Dictionary _operations;
    public Calculator()
    {
    	_operations =
    		new Dictionary
    		{
    			{ "+", this.DoAddition },
    			{ "-", this.DoSubtraction },
    			{ "*", this.DoMultiplication },
    			{ "/", this.DoDivision },
    		};
    }
    public double PerformOperation(string op, double x, double y)
    {
    	if (!_operations.ContainsKey(op))
    		throw new ArgumentException(string.Format("Operation {0} is invalid", op), "op");
    	return _operations[op](x, y);
    }
    


    What have we done? We removed the definition of operations from the code to the data - from the switch to the dictionary.
    private delegate double OperationDelegate(double x, double y);
    private Dictionary _operations;
    


    A delegate is an object indicating a function. By calling the delegate, we call the function that it points to. In this case, we create a delegate to a function that takes two double parameters and returns a double. In the second line, we create a mapping between the operation symbol (+ - * /) and its function.
    Thus, we solved the first drawback: the list of operations can be changed at your discretion.

    Unfortunately we had an extra delegate, and a record of the form
    { "+", this.DoAddition }
    not as clear as
    case "+": return x + y;

    Starting with C # 2.0, we can resolve this problem by introducing anonymous methods:
    { "+", delegate(double x, double y) { return x + y; } },
    { "-", delegate(double x, double y) { return x - y; } },
    { "*", this.DoMultiplication },
    { "/", this.DoDivision },
    

    Here, I use anonymous methods for addition and subtraction, and complete methods for multiplication and division. But still too much water ...

    C # 3.0 with lambdas comes to the rescue:
    private Dictionary> _operations =
    	new Dictionary>
    	{
    		{ "+", (x, y) => x + y },
    		{ "-", (x, y) => x - y },
    		{ "*", this.DoMultiplication },
    		{ "/", this.DoDivision },
    	};
    

    Well, it’s already much better - the girls are already scribbling comments!

    Func эквивалентно delegate double Delegate(double x, double y)
    

    The funk signature reads as Func <type of the first argument, type of the second argument, type of result> . Func itself is the same delegate, but with generics. In addition to the convenience of writing, Func accepts both lambdas and anonymous methods, as well as ordinary methods and all under one announcement. Isn't it surprisingly comfortable?

    Summary
    What have we achieved? The key PerformOperation method has been reduced to a reasonable length.
    public double PerformOperation(string op, double x, double y)
    {
    	if (!_operations.ContainsKey(op))
    		throw new ArgumentException(string.Format("Operation {0} is invalid", op), "op");
    	return _operations[op](x, y);
    }
    


    The operations dictionary can be extended as you wish. If you wish, you can create it from an xml-file, from a factory of calculators, and even from the text entered by the user. If only operations were reduced to type . Thus, in C # you can write elegant, typed constructions with virtually no excess water. The corner of the fan of dynamic languages But in JavaScript I could always writeFunc





    var operations = { "+": function(x, y) { return x + y; } };
    

    What for all sorts of funky shanks are asked?

    My answer is: C # is a strongly-typed language that makes sure that the types match and don't fall in runtime. For attempting to assign the wrong types, it shakes hands at compilation. Therefore, he needs a formal indication of all the types involved.

    Update, where I break the covers.
    By rewriting our PerformOperation to use delegates, we can extend the functionality of the calculator. Add the DefineOperation method to the Calculator class:
    public void DefineOperation(string op, Func body)
    {
    	if (_operations.ContainsKey(op))
    		throw new ArgumentException(string.Format("Operation {0} already exists", op), "op");
    	_operations.Add(op, body);
    }
    

    Now for clarity, we add the operation of taking modulo:
    var calc = new Calculator();
    calc.DefineOperation("mod", (x, y) => x % y);
    var mod = calc.PerformOperation("mod", 3.0, 2.0);
    Assert.AreEqual(1.0, mod);
    

    It is impossible to do the same trick without changing the calculator code if PerformOperation uses a switch.

    Also popular now: