Thursday, March 17, 2011

Снова про code style...

Еще раз хочу про стиль кода написать. Замучился доказывать, что писать надо в едином стиле. Вот если кино начинается комедией, а заканчивается детективом, все плюются… Если в часть главы в книге написано от первого лица, а потом резко так, без перехода от третьего – странно все это выглядит. Возникает вопрос что курил автор. А если код так писать, почему-то нормально.
Второй вопрос – заглавные буквы. Заглавная буква это начало мысли, начало чего-то большого, обозначение идеи. Название городов пишут с большой буквы, имена людей и т.д. Но если взять и Просто так начать Писать часть слов Не понятно зачем писать с Большой буквы, то читать это сложно. Ну и про автора такого текста мысли какие-то бродят.. Так почему нужно писать имена переменных именно так:
  private int MyTempVar;
Почему с большой буквы-то? Имя метода (имя человека, города) – это понятно. А тут-то зачем? И читать такой код сложно, хотя можно конечно.
Вообще загадочно, почему то что в обычной жизни никто не делает, в программировании считается нормальным и до хрипоты спорят что можно и так.

Friday, March 4, 2011

Диалоги

Индус (и) разговаривает с боссом (б) в европейской фирме:
б: Is it done?
и: Yes, but not yet.

б: so did you have chance to complete it?
и: yes
б: could you please send it to me
и: I will send it to you tomorrow after it will be finally completed

- Дим, ты все сделал по этой задаче?
- Да, все
- Совсем все или еще что-то осталось?
- Ну тут еще немного...

События


События (event) это почти делегаты, только используется ключевое слово event, что позволяет редактору Visual Studio правильно распределять их по закладкам окна свойств.

Описание класса, использующего события:
namespace EventTest
{
    /// <summary>
    /// Тип метода
    /// </summary>
    public delegate void Progress(int progress);

    public class Parser
    {
        /// <summary>
        /// Описываем событие
        /// </summary>
        public event Progress ProgressHandler;

        /// <summary>
        /// Основной метод
        /// </summary>
        public void Calc()
        {
            for (int i = 0; i < 100; i++)
            {
                // Если обработчик задан - вызываем его
                if (ProgressHandler != null)
                    ProgressHandler(i+1);
            }
        }
    }
}

Использование этого класса:
namespace EventTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // Создаем экземпляр
            Parser parser = new Parser();
            // Вешаем обработчик
            parser.ProgressHandler += new Progress(parser_ProgressHandler);
            // Можно и два метода обработки одного события
            parser.ProgressHandler+=new Progress(parser_ProgressHandlerSecond);
            // Можно описывать обработчики "по месту", хотя это не всегда удобно
            parser.ProgressHandler+= delegate(int progress)
            {
                Console.WriteLine("inline:" + progress);
            };

            // Вызываем метод
            parser.Calc();
        }

        /// <summary>
        /// Этот метод будет вызываться при обработке события
        /// </summary>
        static void parser_ProgressHandler(int progress)
        {
            Console.WriteLine("first:" + progress);
        }

        /// <summary>
        /// Можно вешать и два метода
        /// </summary>
        static void parser_ProgressHandlerSecond(int progress)
        {
            Console.WriteLine("second:" + progress);
        }
    }
}

Saturday, February 26, 2011

Подклассы или типы

В пятницу на очередном занятии возник вопрос - когда лучше делать подклассы, а когда можно обойтись просто свойством (например, сделать тип сущности)? Я так думаю, что если отличий в разных типах не много, то мутить с подклассами не стоит.

Например, если кирпичи в игре Арканоид отличаются только цветом и числом попаданий до уничтожения, то вполне можно обойтись либо типом и тогда возвращать соответсвующие свойства в зависимости от типа:

public Color Foreground()
{
   swith (type)
   {
     case StoneType.One: return Colors.Red;
   ............
}

Либо вообще вынести эти параметры в конструктор и типы не заводить:

public Stone(Color foreground, int countStrike)
{
  .....
}

А вот если отличий будет очень много, то класс будет замусорен кучей if или switch. Вот тогда имеет смысл делать подклассы и разносить логику и код. Разумеется, если какой-то из "типов" имеет дополнительные свойства, то это однозначно нужно выностить в подкласс.

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);
    }

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

Делегаты

В C# нет указателей (про unsafe я молчу). И это хорошо и правильно – указатель он по сути своей не типизирован. Передали в метод ссылку, а как ее интерпретировать – на совести принимающей стороны. Можем передать int, а использовать как float. И т.д. Тоже самое касается и методов – передали указатель на метод с одной сигнатурой, а используем совсем по-другому…
Зато в C# есть делегаты. Я бы назвал делегаты “типизированные указатели на методы”. Покажу на примере как это работает.
Сначала нужно описать тип делегата, т.е. сигнатуру метода. Описание можно не привязывать к конкретному классу:
namespace DelegateTest
{
    public delegate decimal GetVarValue(string name);

Здесь метод имеет один параметр типа string и возвращает decimal.
Теперь этот делегат мы можем использовать при описании параметров и переменных:
public class Parser
{
    /// <summary>
    /// Описание переменной, сохращающей делегат
    /// </summary>
    private GetVarValue getVarValueHandler;

    /// <summary>
    /// Передаем делегат в конструктор
    /// </summary>
    /// <param name="getVarValueHandler"></param>
    public Parser(GetVarValue getVarValueHandler)
    {
        // Сохраняем делегат, т.к. хотим использовать его
        // в другом методе этого класса
        this.getVarValueHandler = getVarValueHandler;
    }

    /// <summary>
    /// Метод расчета
    /// </summary>
    /// <returns></returns>
    public decimal Calc()
    {
        decimal result = 0;

        // Вызываем делегат с параметром var1
        result += getVarValueHandler("var1");
        // Вызываем делегат с параметром var2
        result += getVarValueHandler("var2");

        return result;
    }

Описание переменной типа делегат выглядит совершенно так же как и обычной переменной. Переменная getVarValueHandler имеет тип GetVarValue, т.е. тип делегата с нашей сигнатурой. Отличие от переменной в том, что мы можем вызывать этот метод. Разумеется, вызывать его можно только так, как он описан, т.е. с одним параметром типа string:
                 getVarValueHandler("var1");
Прелесть  в том, что никак иначе вызвать метод по этой ссылке (а передали мы фактически ссылку на метод) нельзя. Только так, как метод описан в описании делегата.
Использовать этот класс тоже очень просто. Во-первых, нам потребуется метод, который мы будем передавать в конструктор нашего класса. Наверное, излишне говорить, что сигнатура этого метода должна совпадать с сигнатурой делегата и передать в конструктор что-то другое нельзя:
class Program
{
    static void Main(string[] args)
    {
        // Передаем в конструктор делегат, т.е. ссылку
        // на наш метод
        Parser parser = new Parser(GetVarValueMethod);
        // Вызываем метод Calc
        Console.WriteLine(parser.Calc());
    }

    /// <summary>
    /// Делегат. Имя у него может быть любое, главное
    /// чтобы сигнатура была той как описана в типе
    /// делегата
    /// </summary>
    static decimal GetVarValueMethod(string name)
    {
        Console.WriteLine("Value of {0}:", name);
        return decimal.Parse(Console.ReadLine());
    }

}

Имя метода может быть может быть любое, главное чтобы сигнатура совпадала с сигнатурой делегата.
Вот и все. Теперь при вызове метода Calc будет два раза вызван метод GetVarValueMethod.

Цитата

студенты: а что вы обычно говорите своим выпускникам, когда встречаете их?
преподаватель: картошку фри и большую колу пожалуйста