Продумывая архитектуру очередного класса вы понимаете, что вам очень бы пригодилась возможность передать в качестве аргумента кусок исполняемого кода. Это позволило бы вам избежать веретеницы if-ов и case-ов и сделало бы ваш код более элегантным Девушки восхищенно бы охали и непременно оставляли бы вам свой телефончик в комментах. Кхм… что-то я увлекся.
Итак как это делается в C#? Например вы пишете калькулятор и у вас есть простейшая логика:
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");
}
}
Это простое и изящное решение имеет право на жизнь, но у него есть некоторые проблемы:
- Софт изменчив. Завтра вам понадобится добавить взятие по модулю и тогда придется перекомпилировать класс. На определенных стадиях проекта это недешевое удовольствие для потребителей вашего класса.
- Код в текущем виде не имеет никаких проверок входных данных. Если их добавить, то switch неприлично разрастется.
Ма лаасот, как говорят мои израильские друзья.
Во первых надо инкапсулировать код в функции:
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; }
Во вторых надо вообще избавиться от свитча:
private delegate double OperationDelegate(double x, double y);
private Dictionary<string, OperationDelegate> _operations;
public Calculator()
{
_operations =
new Dictionary<string, OperationDelegate>
{
{ "+", 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);
}
Что мы сделали? Мы вынесли определение операций из кода в данные — из свитча в словарь.
private delegate double OperationDelegate(double x, double y);
private Dictionary<string, OperationDelegate> _operations;
Делегат это обьект указывающий на функцию. Вызывая делегат, мы вызываем функцию на которую он указывает. В данном случае мы создаем делегат на функцию принимающую два double параметра и возращающую double. Во второй строке мы создаем маппинг между символом операции (+-*/) и её функцией.
Таким образом мы разрешили первый недостаток: список операций можно изменять по своему усмотрению.
К сожалению мы поимели лишний делегат, да и запись вида
{ "+", this.DoAddition }
не так понятна как
case "+": return x + y;
Начиная с C# 2.0 мы можем разрулить эту проблему внедрением анонимных методов:
{ "+", delegate(double x, double y) { return x + y; } },
{ "-", delegate(double x, double y) { return x - y; } },
{ "*", this.DoMultiplication },
{ "/", this.DoDivision },
Здесь для сложения и вычитания я использую анонимные методы, а для умножения и деления полноценные методы. Но всё равно слишком много воды...
На помощь приходит C# 4.0 с лямбдами:
private Dictionary<string, Func<double, double, double>> _operations =
new Dictionary<string, Func<double, double, double>>
{
{ "+", (x, y) => x + y },
{ "-", (x, y) => x - y },
{ "*", this.DoMultiplication },
{ "/", this.DoDivision },
};
Во-о-т, уже гораздо лучше — девушки уже строчат комменты!
Func<double, double, double> эквивалентно delegate double Delegate(double x, double y)
Сигнатура фанка читается как Func<тип первого аргумента, тип второго аргумента, тип результата>. Сам по себе Func это тот же делегат, но с генериками. Помимо удобства записи, Func принимает как лямбды, так и анонимные методы, так и обычные методы и всё под одним обьявлением. Разве это не удивительно удобно?
Таким образом в C# можно писать изящные, типизированные конструкции практически без лишней воды.
Уголок любителя динамических языков
А вот в JavaScript я всегда мог написать
var operations = { "+": function(x, y) { return x + y; } };
Нафига спрашивается всякие фанки-шманки?
Отвечаю: C# это строго-типизированный язык, который строго следит за тем чтобы типы совпадали и не падали в рантайме. За попытку присвоения неправильных типов, он бьет по рукам при компиляции. Поэтому ему требуется формальное указание о всех фигурирующих типах.
Автор: rroyter