Monday, February 21, 2011

Еще пример про делегаты

Задача такая: по имени функции (математической) получить результат ее вычисления. Самый простой вариант такой:
public class CallFunc1
{
    /// <summary>
    /// Вызываем функцию по имени
    /// </summary>
    public double GetFuncValue(string funcName, double arg)
    {
        switch (funcName.ToUpper())
        {
            case "SIN": return Math.Sin(arg);
            case "COS": return Math.Cos(arg);
            case "TAN": return Math.Tan(arg);
            case "SQRT": return Math.Sqrt(arg);
        }

        throw new ArgumentException("Такой функции нет в списке зарегистрированных функций", funcName);
    }
}

Но тут мы вспоминаем, что квадратный корень нельзя вычислять от отрицательного значения и добавляем еще код:
case "SQRT":
    if (arg < 0)
        throw new ArgumentException("Попытка вычислить квадратный корень от отрицательного числа", "arg");
    return Math.Sqrt(arg);

Потом появляются еще функции и свои проверки… В результате метод GetFuncValue напоминает сборную солянку. А хотелось бы как-то попроще и покрасивее сделать.
С помощью делегатов можно сделать так. Описываем делегат, имеющий сигнатуру метода с одним параметром:
        // Делегат- математический метод
        public delegate double MathFunction(double arg);

А теперь описываем словарь соответствия названий и делегатов таких методов:
        // Список функций
        private Dictionary<string, MathFunction> funcList;

Инициализировать его нужно будет в конструкторе:
    /// <summary>
    /// Конструктор
    /// </summary>
    public CallFunc1()
    {
        // Создаем список функций
        funcList = new Dictionary<string, MathFunction>();
        funcList.Add("SIN", delegate(double arg) {
            return Math.Sin(arg);
        });
        funcList.Add("COS", delegate(double arg)
        {
            return Math.Cos(arg);
        });
        funcList.Add("TAN", delegate(double arg)
        {
            return Math.Tan(arg);
        });
        funcList.Add("SQRT", delegate(double arg)
        {
            if (arg < 0)
                throw new ArgumentException("Попытка вычислить квадратный корень от отрицательного числа", "arg");
            return Math.Sqrt(arg);
        });
    }

Обратите внимание, что реализация делегата сделана прямо “по месту” (это называется анонимными методами – методами без имени).
А с помощью LINQ вызов станет совсем простым делом:
    public double GetFuncValue(string funcName, double arg)
    {
        // Ищем нужную функцию в списке
        var func = funcList.Where(f => f.Key == funcName).Select(f => f.Value).FirstOrDefault();
        // Не нашли - генерируем исключение
        if (func == null)
            throw new ArgumentException("Такой функции нет в списке зарегистрированных функций", funcName);
        // Нашли - вызываем эту функцию
        return func(arg);
    }

Теперь каждое имя функции связанно со своей реализацией и своими ограничениями.

1 comment:

Pavel Agurov said...

Для доступа к словарю тут LINQ не нужен. Можно просто через []. Это просто для примера.