Тверской государственный университет
Опубликован: 02.12.2009 | Доступ: свободный | Студентов: 3204 / 514 | Оценка: 4.41 / 4.23 | Длительность: 11:54:00
ISBN: 978-5-9963-0259-8
Лекция 1:

Язык программирования и среда разработки. Цели курса

Лекция 1: 12345678 || Лекция 2 >

Windows-проект

Добавим в Решение новый проект, аналогично тому, как был добавлен консольный проект. В качестве типа проекта выберем "Windows Forms Application", дадим проекту имя "WindowsFormsToMathTools". Результат этой работы показан на рис. 1.9.

Решение, содержащее три проекта - Class Library, Console, Windows

увеличить изображение
Рис. 1.9. Решение, содержащее три проекта - Class Library, Console, Windows

При создании проекта DLL автоматически создавался в проекте один пустой класс, в консольном проекте создавался класс, содержащий метод Main с пустым кодом метода. В Windows-проекте автоматически создаются два класса - класс с именем Form1 и класс с именем Program.

Первый из этих классов является наследником класса Form из библиотеки FCL и наследует все свойства и поведение (методы и события) родительского класса. Класс Form поддерживает организацию интерфейса пользователя в визуальном стиле. Форма является контейнером для размещения визуальных элементов управления - кнопок (Button), текстовых полей (TextBox), списков (ListBox) и более экзотичных элементов - таблиц (DataGridView), деревьев (TreeView) и многих других элементов. С некоторыми элементами управления мы познакомимся уже в этом примере, другие будут встречаться в соответствующих местах нашего курса.

Классы в C# синтаксически не являются неделимыми и могут состоять из нескольких частей, каждая из которых начинается с ключевого слова "partial" (частичный). Таковым является и построенный автоматически класс Form1. Возможность разбиения описания одного класса на части появилась еще в версии языка C# 2.0, что облегчает работу над большим классом. Каждая часть класса хранится в отдельном файле со своим именем. Одна часть класса Form1 лежит в файле с именем "Form1.Designer.cs". Эта часть класса заполняется автоматически инструментарием, называемым Дизайнером формы. Когда мы занимаемся визуальным проектированием формы и размещаем на ней различные элементы управления, меняем их свойства, придаем форме нужный вид, задаем обработчиков событий для элементов управления, то Дизайнер формы транслирует наши действия в действия над объектами соответствующих классов, создает соответствующий код и вставляет его в нужное место класса Form1. Предполагается, что разработчик проекта не вмешивается в работу Дизайнера и не корректирует часть класса Form1, созданную Дизайнером. Тем не менее, понимать код, созданный Дизайнером, необходимо, а иногда полезно и корректировать его. Другая часть класса Form1, хранящаяся в файле "Form1.cs", предназначена для разработчика - именно в ней располагаются автоматически создаваемые обработчики событий, происходящих с элементами управления, код которых создается самим разработчиком. Такая технология программирования, основанная на работе с формами, называется визуальной, событийно управляемой технологией программирования.

Класс Program, автоматически создаваемый в Windows-проекте, содержит точку входа - статический метод Main, о важной роли которого мы уже говорили. В отличие от консольного проекта, где тело процедуры Main изначально было пустым и должно было заполняться разработчиком проекта, в Windows-проектах процедура Main уже готова и, как правило, разработчиком не изменяется. Что же делает автоматически созданная процедура Main, текст которой можно видеть на рис. 1.9? Она работает с классом Application библиотеки FCL, вызывая поочередно три статических метода этого класса - EnableVisualStyles, SetCompatibleTextRenderingDefault, Run. О назначении первых двух методов можно судить по их содержательным именам. Основную работу выполняет метод Run - в процессе его вызова создается объект класса Form1 и открывается форма - визуальный образ объекта, с которой может работать конечный пользователь проекта. Если, как положено, форма спроектирована и заполнена элементами управления, то конечному пользователю остается вводить собственные данные в поля формы, нажимать на кнопки, вообще быть инициатором возникновения различных событий в мире объектов формы. В ответ на возникающие события начинают работать обработчики событий, что приводит к желаемым (или не желанным) изменениям мира объектов. Типичной ситуацией является проведение вычислений по данным, введенным пользователем, и отображение результатов этих вычислений в полях формы, предназначенных для этих целей.

Построение интерфейса формы

Прежде чем заняться построением интерфейса формы, переименуем класс Form1, дав ему, как положено, содержательное имя - FormResearchSinus. Заметьте, переименование объектов класса хотя и можно делать вручную, но это далеко не лучший способ, к тому же,чреватый ошибками. Для этих целей следует использовать возможности, предоставляемые меню Refactor|Rename. Параллельно с переименованием класса следует переименовать и файл (файлы) с описанием класса.

Займемся теперь построением интерфейса - размещением в форме элементов управления. Классическим примером интерфейса, поддерживающего сервисы стандартного класса Math, является инженерный калькулятор. В нашем классе реализована пока только одна функция - \sin(x), так что можем построить пока калькулятор одной функции. Но и цели у нас другие - мы занимаемся исследованием того, насколько корректно и точно предложенные алгоритмы позволяют вычислить эту функцию.

Проведем еще одно важное исследование - оценим время, затрачиваемое на вычисление функции. Временные оценки работы проекта и его отдельных частей - крайне важная часть работы разработчика проекта. Во многих случаях требуется построить временной профиль работы проекта, выявить его наиболее узкие места, на выполнение которых уходит основное время работы, что позволит целенаправленно заниматься оптимизацией проекта, направленной на уменьшение времени работы. Следует помнить, что интерактивный стиль работы современных приложений требует быстрой реакции системы на действия пользователя. Пользователь имеет право задумываться при выборе своих действий, но от системы в большинстве случаев ждет немедленного ответа. Так что поставим цель - получить время, затрачиваемое компьютером на вычисление функции как стандартным методом класса Math, так и методами класса MyMath из библиотеки MathTools.

На рис. 1.10 показан интерфейс спроектированной формы.

Интерфейс формы класса FormResearchSinus

Рис. 1.10. Интерфейс формы класса FormResearchSinus

Для наших целей достаточен скромный интерфейс. В форму включено текстовое поле для ввода значения аргумента x, три текстовых поля предназначены для отображения результата вычислений функции \sin(x) тремя различными методами. В форме есть отдельный контейнер для оценки временных характеристик. В контейнер помещены три текстовых поля, в которых будет отображаться время, затрачиваемое на вычисление функции каждым из анализируемых методов. Поскольку компьютеры быстрые, замерить время, требуемое на однократное вычисление функции, просто невозможно. Замеряется время, затрачиваемое на многократное выполнение метода (отдельного участка кода). В контейнере размещено окно, позволяющее задать число повторов вычисления функции при измерении времени работы. Все текстовые поля снабжены метками, проясняющими смысл каждого поля. Для входных текстовых полей (аргумент функции и число повторов) заданы значения по умолчанию. В форме находится командная кнопка, щелчок по которой приводит к возникновению события Click этого объекта, а обработчик этого события запускает вычисление значений функции, получение оценок времени вычисления и вывод результатов в соответствующие текстовые поля. Каков сценарий работы пользователя? Когда при запуске проекта открывается форма, пользователь может в соответствующих полях задать значение аргумента функции и число повторов, после чего нажать кнопку с надписью "Вычислить sin(x)". В выходных текстовых полях появятся результаты вычислений. Меняя входные данные, можно наблюдать, как меняются результаты вычислений. Можно будет убедиться, что при всех задаваемых значениях аргумента функции значения функции, вычисленные тремя разными методами, совпадают с точностью до 9 знаков после запятой, а время вычислений метода, встроенного в стандартный класс Math, примерно в два раза меньше, чем время спроектированных нами методов, что ,впрочем, не удивительно и вполне ожидаемо. Реализация вычисления стандартных математических функций реализована на аппаратном уровне, поэтому практически невозможно написать собственный код, работающий эффективнее.

Что дает оптимизация метода, рассмотренная нами в классе MyMath? Оценить это не так просто, поскольку при оценке времени работы возможны погрешности, измеряемые десятками миллисекунд, что сравнимо с выигрышем, полученным в результате оптимизации. Об этом мы еще поговорим чуть позже.

Как оценить время работы метода

Давайте подумаем, как можно оценить время работы метода класса или отдельного фрагмента кода. Во-первых, можно провести теоретическую оценку. Например, для функции \sin(x), как мы видели, на одном шаге цикла требуется около 10 операций (не учитывая разную сложность операций). Число итераций зависит от значения аргумента. Максимальное значение аргумента по модулю не превышает 2\pi, но и в этом случае 15 итераций достаточно, чтобы текущий член суммы по модулю стал меньше 10^{-9}. Современному компьютеру средней мощности с частотой 1,6 GHz потребуется менее 1 секунды для вычисления функции при числе повторов 10^6.

Чем считать операции, зачастую проще непосредственно измерить реальное время вычислений. В библиотеке CLR для этих целей создан класс DateTime, позволяющий работать с датами и временами. У этого класса есть замечательный статический метод Now, вызов которого возвращает в качестве результата объект класса DateTime, задающий текущую дату и текущее время (по часам компьютера). Многочисленные свойства этого объекта - Year, Month, Hour, Second и многие другие позволяют получить все характеристики даты и текущего времени. Текущее время можно также измерять и в единицах, называемых "тиками", где один тик равен 100 наносекунд или, что то же, 10^{-7} секунды.

Имея в своем арсенале такой класс, не составит большого труда измерить время, требуемое на выполнение некоторого участка кода. Достаточно иметь две переменные с именами, например, start и finish класса DateTime. Переменой start присвоим значение, возвращаемое функцией Now перед началом измеряемого участка кода, а переменной finish - в конце участка кода. Разность времен даст нам требуемую оценку длительности выполнения кода.

Некоторый недостаток этого подхода состоит в том, что рабочий код нужно дополнять операторами, задействованными только для проведения исследований. Поэтому хочется иметь более удобный инструментарий. Покажу возможный образец построения подобного инструмента. Поместив его в созданную нами библиотеку классов DLL MathTools, обеспечим тем самым возможность повторного использования.

Добавим в эту библиотеку новый класс. Вот описание этого класса, пока пустого, но содержащего заголовочный комментарий:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MathTools
{
    /// <summary>
    /// Класс спроектирован для получения оценок времени 
    /// выполнения различных методов.
    /// Встроенные делегаты определяют сигнатуры методов
    /// </summary>
    public class TimeValue
    {
		}
}

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

Предварительные сведения о делегатах - функциональном типе данных

Уже говорилось, что одна из главных ролей класса состоит в том, чтобы задать описание типа данных. Каждый тип данных характеризует некоторое множество объектов - экземпляров класса. Класс, позволяющий описать некоторое множество объектов, каждый из которых является функцией, называется функциональным типом. В языке C# для описания функциональных типов используются классы, называемые делегатами, описание которых начинается с ключевого слова - delegate. Делегаты играют важную роль в языке C#, и их описанию будет уделено достойное внимание. Пока что нам достаточно знать, как выглядит описание делегата и как оно используется во многих задачах. Описание делегата представляет описание сигнатуры функций, принадлежащих одному функциональному типу. Под сигнатурой функции понимается описание числа, порядка и типов аргументов функции и типа возвращаемого значения. В языках программирования заголовок функции определяет ее сигнатуру. Пусть задан делегат

public delegate double DToD(double arg1);

Этот делегат задает описание класса с именем DToD (Double To Double), которому принадлежат все функции с одним аргументом типа double и возвращающие результат типа double. Функция \sin(x), как и многие другие математические функции, соответствует этой сигнатуре и, следовательно, является объектом этого класса.

Если задан делегат, то появляется возможность объявлять объекты этого класса, в частности, формальный аргумент метода может принадлежать такому классу, а в качестве фактического аргумента в момент вызова можно передавать имя конкретной функции, принадлежащей данному функциональному типу. Пример, иллюстрирующий эту возможность, сейчас будет продемонстрирован. Но прежде одно важное замечание о методах, процедурах и функциях.

Формально у классов языка C# есть только методы и нет ключевых слов для таких понятий, как процедуры и функции. Фактически же любой метод представляет собой либо процедуру, либо функцию. Существуют синтаксические и содержательные различия в описании методов, представляющих процедуры и функции, в способах их вызова и применения. Подробнее об этом поговорим в соответствующем разделе курса, сейчас же заметим только, что в зависимости от контекста будем использовать как термин "метод", так и термины "процедура" и "функция".

Класс TimeValue

Теперь уже можно привести код класса TimeValue со встроенным делегатом DToD, предоставляющий своим клиентам такой сервис, как оценка времени работы любого метода клиента, сигнатура которого согласована с делегатом. При необходимости этот класс всегда можно расширить, добавив соответствующие сервисы и новые делегаты. Вот этот код:

public class TimeValue
    {
        public delegate double DToD(double arg1);

        /// <summary>
        /// Возвращает время в секундах, 
        /// затраченное на вычисление count раз
        /// метода fun с сигнатурой, которая удовлетворяет 
        /// делегату DToD (double to double)
        /// </summary>
        /// <param name="count">число повторений</param>
        /// <param name="fun">имя функции</param>
        /// <param name="x">аргумент</param>
        /// <returns>время в милисекундах или тиках</returns>
        public static double EvalTimeDToD(int count, DToD fun, double x)
        {
            DateTime start, finish;
            double res = 0;
            start = DateTime.Now;
                for (int i = 1; i < count; i++)
                    fun(x);
            finish = DateTime.Now;
            //res = (finish- start).Ticks;
            res = (finish - start).Milliseconds;
            return res;
        }       
    }

Время можно измерять в разных единицах, например, в тиках или миллисекундах. Статический метод EvalTimeDToD, реализующий сервис класса, устроен достаточно просто. Две переменные start и finish класса DateTime вызывают свойство Now, окаймляя цикл по числу повторов вызовов метода, функциональный тип которого задан делегатом, а имя передается в качестве фактического параметра при вызове метода EvalTimeDToD.

Еще одно важное замечание стоит сделать по поводу точности оценок, получаемых при использовании механизма объектов DateTime. Следует учитывать, что свойство Now не возвращает в точности текущее время в момент ее вызова. Это связано с механизмами операционной системы, когда в реальности на компьютере работают несколько процессов и система обработки прерываний имеет некоторый фиксированный квант времени при переключении процессов. Поэтому, запуская измерение времени вычислений на одних и тех же данных, можно получать различные данные с точностью, определяемой характеристиками системы прерываний. Это существенно не влияет в целом на получение временных характеристик, но не позволяет сравнивать методы, время выполнения которых сравнимо с погрешностью временных оценок.

В заключение приведем результаты вычислений и временных оценок, полученных для нашего примера.

Сравнительные результаты точности и времени вычисления функции sin(x)

Рис. 1.11. Сравнительные результаты точности и времени вычисления функции sin(x)

Итоги

Эта лекция носит обзорный характер. В ней вводится много понятий из широкого круга областей, связанных как с языком программирования, так и средой разработки, операционной системой. По этой причине она может быть трудна для восприятия тех, кто только постигает начала программирования. Пусть Вас не смущает, если при ее чтении остались непонятные вещи. Надеюсь, что некоторое общее впечатление о процессе создания и выполнения проектов, написанных на языке C# и создаваемых в среде Visual Studio 2008, она все же дает. Хорошо было бы вернуться к чтению этой лекции уже после прохождения основного курса.

Еще большие сложности могут возникнуть при разборе примера, в котором я позволил себе на начальном этапе применять достаточно продвинутые и разнообразные средства языка C# и Visual Studio 2008. Тем не менее и в этом случае хотелось бы, чтобы Вы повторили все действия, которые связаны с построением Решения, включающего три проекта. Моя цель - продемонстрировать уже с первых шагов возможности языка C# по построению кода, отвечающего требованиям промышленного продукта.

В утешение скажу, что дальнейшее изложение материала, надеюсь, будет более систематическим и последовательным. Рассказать хочется о многом, а размеры текста ограничены.

На этом я закончу обзорное рассмотрение Visual Studio .Net и ее каркаса Framework .Net. Одной из лучших книг, подробно освещающих эту тему, является книга Джеффри Рихтера, переведенная на русский язык: "Программирование на платформе.Net Framework". Крайне интересно, что для Рихтера языки являются лишь надстройкой над каркасом, поэтому он говорит о программировании, использующем возможности исполнительной среды CLR и библиотеки FCL.

Лекция 1: 12345678 || Лекция 2 >
Дарья Федотова
Дарья Федотова
Надежда Савина
Надежда Савина

Записалась на платный курс Программирование на С# http://www.intuit.ru/studies/professional_retraining/951/video_courses/356/info. Оплату пока не производила.

Могу ли я скачать/прочитать текст прослушиваемых лекций? Особенно интересуют задания