Лекция 1.1 Язык программирования и среда разработки

Владимир Биллиг

Основы объектного программирования на С#

(C# 3.0, Visual Studio 2008)

Часть 1
Ядро языка

 

Цели курса

 

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

В этой книге в качестве языка программирования выбран язык C# — его версия 3.0, в качестве среды разработки программных проектов – Visual Studio 2008, Professional Edition и Framework .Net в версии 3.5.

Язык C#

Язык C# является наиболее известной новинкой в области создания языков программирования. По сути это язык  программирования, созданный уже в 21-м веке. Явившись на свет в недрах Microsoft, он с первых своих шагов получил мощную поддержку.  Язык признан международным сообществом.  В июне 2006 года  Европейской ассоциацией по стандартизации принята уже четвертая версия стандарта этого языка: Standard ECMA-334 C# Language Specifications, 4-th edition –

http://www.ecma-international.org/publications/standards/Ecma-334.htm

Международной ассоциацией по стандартизации эта версия языка принята как стандарт ISO/IEC – 23270.  Заметим, что первая версия стандарта языка принята еще в 2001 году. Компиляторы Microsoft строятся в соответствии с международными стандартами языка.

Язык C# является молодым языком и продолжает интенсивно развиваться. Каждая новая версия языка включает принципиально новые свойства. Не исключением явилась и версия 3.0, рассматриваемая в данном учебном курсе.

Руководителем группы, создающей язык C#, является  сотрудник Microsoft Андреас Хейлсберг. Он стал известным в мире программистов задолго до того, как пришел в Microsoft. Хейлсберг входил в число ведущих разработчиков одной из самых популярных сред разработки – Delphi. В Microsoft он участвовал в создании версии языка Java – J++, так что опыта в написании языков и сред программирования ему не занимать. Как отмечал сам Андреас Хейлсберг, C# создавался  как язык компонентного программирования, и в этом одно из главных достоинств языка, направленное на возможность повторного использования созданных компонентов. Создаваемые компилятором компоненты являются само документируемыми, помимо кода содержат метаинформацию, описывающую компоненты, и поэтому могут выполняться на различных платформах.

Отметим следующие важные факторы:

  • C# создавался и развивается параллельно с каркасом Framework .Net и в полной мере учитывает все его возможности;
  • C# является полностью объектно-ориентированным языком;
  • C# является мощным объектным языком с возможностями наследования и универсализации;
  • C# является наследником языка C++. Общий синтаксис, общие операторы языка облегчают переход от языка С++ к C#;
  • Сохранив основные черты своего родителя, язык стал проще и надежнее;
  • Благодаря каркасу Framework .Net, ставшему надстройкой над операционной системой, программисты C#  получают преимущества работы с виртуальной машиной;
  • Framework .Net поддерживает разнообразие типов  приложений на C#;
  • Реализация, сочетающая построение надежного и эффективного кода, является немаловажным фактором, способствующим успеху C#.

В каком направлении развивается язык C#? Назовем новинки, появившиеся в версии 3.0:

  • На первое место я бы поставил возможности создания качественно новых типов проектов на C#. Конечно, новые типы проектов нельзя отнести к новинкам языка C#. Эти возможности предоставляет каркас Framework .Net 3.5 и Visual Studio 2008. Но поскольку язык, среда разработки и каркас среды тесно связаны, то с точки зрения программистов, работающих на C#, их возможности построения программных проектов на C# существенно расширились.
  • Введение в язык инструмента, получившего название LINQ (Language Integrated Query). Сегодня ни один серьезный проект на C# не обходится без обмена данными с внешними источниками данных – базами данных, Интернет и прочими хранилищами данных. В таких ситуациях приходилось использовать специальные объекты (ADO .Net  или их более ранние версии). При работе с ними нужно было использовать SQL — специальный язык запросов. Благодаря  LINQ язык запросов становится частью языка программирования C#. Тем самым реализована давняя мечта программистов – работать с данными, находящихся в различных внешних источниках, используя средства, принадлежащие языку программирования, не привлекая дополнительные инструментальные средства и языки.
  • Введение в язык инструментария, характерного для функционального стиля программирования, — лямбда-выражений, анонимных типов и функций. Андреас Хейлсберг полагает, что смесь императивного и функционального стилей программирования упрощает задачи разработчиков, поскольку функциональный стиль позволяет разработчику сказать, что нужно делать, не уточняя, как это должно делаться.
  • Новые возможности появились при реализации параллелизма в программных проектах.

Эти и другие новинки будут подробно рассмотрены в соответствующих разделах курса.

Будущее С#

Следующая версия языка С# 4.0 должна появиться параллельно с выходом новой версии Visual Studio 2010. Продолжается работа над версией C# 5.0. Можно отметить три основные тенденции в развитии языка – декларативность, динамичность и параллельность. Разработчики языка пытаются придать языку C# свойства, расширяющие традиционные возможности процедурных языков. Явно заметен тренд к функциональным языкам с их декларативным стилем. Такие свойства появились уже в C# 3.0, в следующих версиях они только расширяются.

В новой версии Visual Studio 2010 должны появиться новые динамические языки программирования – «железный змей» — Iron Python и Iron Ruby. Эти языки проще устроены, во многом из-за того, что не являются строго типизированными и потому не позволяют проводить контроль типов еще на этапе компиляции. В C# 4.0 введена возможность задания динамических переменных, аналогично тому, как это делается в динамических языках.

Параллельные вычисления в ближайшие 5-10 лет станут реальностью повседневной работы программиста. В этом направлении развивается техника. Языки программирования должны поддерживать эту тенденцию.

Компилятор как сервис, программирование на лету, — такие возможности должны появиться в C# 5.0. Можно не сомневаться, что C# программистам в ближайшие годы скучать не придется.

 

Visual Studio 2008

Как уже отмечалось, принципиальной новинкой  этой версии является возможность построения новых типов программных проектов, что обеспечивается новой версией каркаса Framework .Net 3.5. Если не считать этой важной особенности, то идейно Visual Studio 2008 подобна  предыдущим версиям Visual Studio 2005 и Visual Studio 2003.

Рассмотрим основные особенности среды разработки Visual Studio.

Открытость

Среда разработки программных проектов является открытой языковой средой. Это означает, что наряду с языками программирования, включенными в среду фирмой Microsoft – Visual C++ .Net (с управляемыми расширениями), Visual C# .Net , Visual Basic .Net, – в среду могут добавляться любые языки программирования, компиляторы которых создаются другими фирмами.

Таких расширений среды Visual Studio сделано уже достаточно много, практически они существуют для всех известных языков – Fortran и Cobol, RPG и Component Pascal, Eiffel, Oberon и Smalltalk.

Новостью является то, что Microsoft не включила в Visual Studio 2008 поддержку языка Java. Допустимые в предыдущих версиях проекты на языке J++ в Visual Studio 2008 в настоящее время создавать нельзя, ранее созданные проекты в студии не открываются.

Открытость среды не означает полной свободы. Все разработчики компиляторов при включении нового языка в среду разработки должны следовать определенным ограничениям. Главное ограничение, которое можно считать и главным достоинством, состоит в том, что все языки, включаемые в среду разработки Visual Studio .Net должны использовать единый каркас – Framework .Net. Благодаря этому достигаются многие желательные свойства: легкость использования компонентов, разработанных на различных языках; возможность разработки нескольких частей одного приложения на разных языках; возможность бесшовной отладки такого приложения; возможность написать класс на одном языке, а его потомков — на других языках. Единый каркас приводит к сближению языков программирования, позволяя вместе с тем сохранять их индивидуальность и имеющиеся у них достоинства. Преодоление языкового барьера – одна из важнейших задач современного мира. Visual Studio .Net, благодаря единому каркасу, в определенной мере решает эту задачу в мире программистов.

Framework .Net – единый каркас среды разработки приложений

В каркасе Framework .Net можно выделить два основных компонента:

  • статический – FCL (Framework Class Library) – библиотеку классов каркаса;
  • динамический – CLR (Common Language Runtime) – общеязыковую исполнительную среду.

Библиотека классов FCL – статический компонент каркаса

Понятие каркаса приложений – Framework Applications появилось достаточно давно, оно широко использовалось еще в четвертой версии Visual Studio. Библиотека классов MFC (Microsoft Foundation Classes) играла роль каркаса приложений Visual C++.

Несмотря на то, что каркас был представлен только статическим компонентом, уже тогда была очевидна его роль в построении приложений. Уже в то время важнейшее значение в библиотеке классов MFC имели классы, задающие архитектуру строящихся приложений. Когда разработчик выбирал один из возможных типов приложения, например архитектуру Document-View, то в его приложение автоматически встраивались класс Document, задающий структуру документа, и класс View, задающий его визуальное представление. Класс Form и классы, задающие элементы управления, обеспечивали единый интерфейс приложений. Выбирая тип приложения, разработчик изначально получал нужную ему функциональность, поддерживаемую классами каркаса. Библиотека классов поддерживала и традиционные для программистов классы, задающие расширенную систему типов данных, в частности, динамические типы данных – списки, деревья, коллекции, шаблоны.

За прошедшие годы роль каркаса в построении приложений существенно возросла, — прежде всего, за счет появления его динамического компонента, о котором чуть позже поговорим подробнее. Что же касается статического компонента – библиотеки классов, то здесь появился ряд важных нововведений.

Единство каркаса

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

Встроенные примитивные типы

Важной частью библиотеки FCL стали классы, задающие примитивные типы, те типы, которые считаются встроенными в язык программирования. Типы каркаса покрывают основное множество встроенных типов, встречающихся в языках программирования. Типы языка программирования проецируются на соответствующие типы каркаса. Тип, называемый в языке Visual Basic – Integer, а в языках С++ и C# — int, проецируется на один и тот же тип каркаса System.Int32. В языке программирования, наряду с «родными» для языка названиями типов, разрешается пользоваться именами типов, принятыми в каркасе. Поэтому, по сути, все языки среды разработки могут пользоваться единой системой встроенных типов, что, конечно, способствует облегчению взаимодействия компонентов, написанных на разных языках.

Структурные типы

Частью библиотеки стали не только простые встроенные типы, но и структурные типы, задающие организацию данных – строки, массивы, динамические типы данных = стеки, очереди, списки, деревья. Это также способствует унификации и реальному сближению языков программирования.

Архитектура приложений

Существенно расширился набор возможных архитектурных типов построения приложений. Помимо традиционных Windows- и консольных приложений, появилась возможность построения Web-приложений. Большое внимание уделяется возможности создания повторно используемых компонентов – разрешается строить библиотеки классов, библиотеки элементов управления и библиотеки Web-элементов управления. Популярным архитектурным типом являются Web-службы, ставшие сегодня благодаря открытому стандарту одним из основных видов повторно используемых компонентов.

Модульность

Число классов библиотеки FCL велико (несколько тысяч). Поэтому понадобился способ их структуризации. Логически классы с близкой функциональностью объединяются в группы, называемые пространством имен (Namespace). Основным пространством имен библиотеки FCL является пространство System, содержащее как классы, так и другие вложенные пространства имен. Так, уже упоминавшийся примитивный тип Int32 непосредственно вложен в пространство имен System и его полное имя, включающее имя пространства – System.Int32.

В пространство System вложен целый ряд других пространств имен. Например, в пространстве System.Collections находятся классы и интерфейсы, поддерживающие работу с коллекциями объектов – списками, очередями, словарями. В пространство System.Collections, в свою очередь, вложено пространство имен Specialized, содержащие классы со специализацией, например, коллекции, элементами которых являются только строки. Пространство System.Windows.Forms содержит классы, используемые при создании Windows-приложений. Класс Form из этого пространства задает форму – окно, заполняемое элементами управления, графикой, обеспечивающее интерактивное взаимодействие с пользователем.

По ходу курса мы будем знакомиться со многими классами библиотеки FCL.

Общеязыковая исполнительная среда CLR – динамический компонент каркаса

Важным шагом в развитии каркаса Framework .Net стало введение динамического компонента каркаса — исполнительной среды CLR. С появлением CLR процесс выполнения приложений стал принципиально другим.

Двухэтапная компиляция. Управляемый модуль и управляемый код

Компиляторы языков программирования, включенные в Visual Studio .Net, создают код на промежуточном языке IL (Intermediate Language) – ассемблерном языке.  В результате компиляции проекта, содержащего несколько файлов, создается так называемый управляемый модуль – переносимый исполняемый файл (Portable Executable или PE-файл). Этот файл содержит код на IL и метаданные – всю информацию, необходимую для CLR, чтобы под ее управлением PE-файл мог быть исполнен. Метаданные доступны и конечным пользователям. Классы, входящие в пространство имен Reflection, позволяют извлекать метаинформацию о классах, используемых в проекте. Этот процесс называется отражением. Об атрибутах классов, отображаемых в метаданные PE-файла, еще будем говорить неоднократно. В зависимости от выбранного типа проекта, PE-файл может иметь разные уточнения — exe, dll, mod или mdl.

Заметьте, PE-файл, имеющий уточнение exe, хотя и является exe-файлом, но это не обычный исполняемый Windows файл. При его запуске он распознается как PE-файл и передается CLR для обработки. Исполнительная среда начинает работать с кодом, в котором специфика исходного языка программирования исчезла. Код на IL начинает выполняться под управлением CLR (по этой причине код называется управляемым). Исполнительную среду следует рассматривать как виртуальную IL-машину. Эта машина транслирует «на лету» требуемые для исполнения участки кода в команды реального процессора, который в действительности и выполняет код.

Виртуальная машина

Отделение каркаса от студии явилось естественным шагом. Каркас Framework .Net перестал быть частью студии, а стал надстройкой над операционной системой. Теперь компиляция и создание PE модулей на IL отделено от выполнения, и эти процессы могут быть реализованы на разных платформах.

В состав CLR входят трансляторы JIT (Just In Time Compiler), которые и выполняют трансляцию IL в командный код той машины, где установлена и функционирует исполнительная среда CLR. Конечно, в первую очередь Microsoft реализовала CLR и FCL для различных версий Windows, включая Windows 98/Me/NT 4/2000, 32 и 64-разрядные версии Windows XP , Windows Vista и семейство .Net Server. Облегченная версия Framework .Net разработана для операционных систем Windows CE и Palm.

Framework .Net развивается параллельно с развитием языков программирования, среды разработки программных проектов и операционных языков. Версия языка C# 2.0 использовала версию Framework .Net  2.0. Операционная система Windows Vista включила в качестве надстройки Framework .Net 3.0. Язык C# 3.0 и Visual Studio 2008 используют версию Framework .Net 3.5.

Framework .Net является свободно распространяемой виртуальной машиной. Это существенно расширяет сферу его применения. Производители различных компиляторов и сред разработки программных продуктов предпочитают теперь также транслировать свой код в IL, создавая модули в соответствии со спецификациями CLR. Это обеспечивает возможность выполнения их кода на разных платформах.

Компилятор JIT, входящий в состав CLR, компилирует IL код с учетом особенностей текущей платформы. Благодаря этому создаются высокопроизводительные приложения. Следует отметить, что CLR, работая с IL кодом, выполняет достаточно эффективную оптимизацию и, что не менее важно, защиту кода. Зачастую нецелесообразно выполнять оптимизацию на уровне создания IL кода, она иногда может не улучшить, а ухудшить ситуацию, не давая CLR провести оптимизацию на нижнем уровне, где можно учесть особенности процессора.

Дизассемблер и ассемблер

Для проекта, построенного на C#, иногда полезно провести анализ построенного  PE-файла, его IL кода и связанных с ним метаданных. В состав Framework SDK входит дизассемблер – ildasm, выполняющий дизассемблирование PE-файла и показывающий в наглядной форме метаданные и IL код с комментариями. Мы иногда будем пользоваться результатами дизассемблирования. У меня на компьютере кнопка, вызывающая дизассемблер, находится на рабочем столе. Вот путь к папке, в которой обычно находится дизассемблер:

C:\Program Files\Microsoft Visual Studio .Net\FrameworkSDK\Bin\ildasm.exe

Профессионалы, предпочитающие работать на низком уровне, могут программировать на языке ассемблера IL. В этом случае в их распоряжении будет вся мощь библиотеки FCL и все возможности CLR. У меня на компьютере путь к папке, где находится ассемблер, следующий:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\ilasm.exe

В этой книге к ассемблеру мы обращаться не будем, упоминаю о нем для полноты картины.

Метаданные

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

Мы увидим также, что при проектировании класса программист может создавать его атрибуты, добавляемые к метаданным PE-файла. Клиенты класса могут, используя классы из пространства имен Reflection, получать эту дополнительную информацию и на ее основании принимать соответствующие решения.

Сборщик мусора – Garbage Collector и управление памятью

Еще одной важной особенностью построения CLR является то, что исполнительная среда берет на себя часть функций, традиционно входящих в ведение разработчиков трансляторов, и  облегчает тем самым их работу. Один из таких наиболее значимых компонентов CLR —  сборщик мусора (Garbage Collector). Под сборкой мусора понимается освобождение памяти, занятой объектами, которые стали бесполезными и не используются в дальнейшей работе приложения. В ряде языков программирования (классическим примером является язык C/C++) память освобождает сам программист, в явной форме отдавая команды, как на создание, так и на удаление объекта. В этом есть своя логика – я тебя породил, я тебя и убью. Однако можно и нужно освободить человека от этой работы. Неизбежные ошибки программиста при работе с памятью тяжелы по последствиям, и их крайне тяжело обнаружить. Как правило, объект удаляется в одном модуле, а необходимость в нем обнаруживается в другом далеком модуле. Обоснование того, что программист не должен заниматься удалением объектов, а сборка мусора должна стать частью исполнительной среды, дано достаточно давно. Наиболее полно оно обосновано в работах Бертрана Мейера и в его книге «Object-Oriented Construction Software», первое издание которой появилось еще в 1988 году.

В CLR эта идея реализована в полной мере. Задача сборки мусора снята не только с программистов, но и с разработчиков трансляторов; она решается в нужное время и в нужном месте – исполнительной средой, ответственной за выполнение вычислений. Здесь же решаются и многие другие вопросы, связанные с использованием памяти, в частности, проверяется и не допускается использование «чужой» памяти, не допускаются и другие нарушения.  Данные, удовлетворяющие требованиям CLR и допускающие сборку мусора, называются управляемыми данными.

Но, как же, спросите вы, быть с языком C++ и другими языками, где есть нетипизированные указатели, адресная арифметика, возможности удаления объектов программистом? Ответ следующий – CLR позволяет работать как с управляемыми, так и с неуправляемыми данными. Однако использование неуправляемых данных регламентируется и не поощряется. Так, в C# модуль, использующий неуправляемые данные (указатели, адресную арифметику), должен быть помечен как небезопасный (unsafe),  и эти данные должны быть четко зафиксированы. Об этом мы еще будем говорить при рассмотрении языка C# в последующих лекциях. Исполнительная среда, не ограничивая возможности языка и программистов, вводит определенную дисциплину в применении потенциально опасных средств языков программирования.

Исключительные ситуации

Что происходит, когда при вызове некоторой функции (процедуры) обнаруживается, что она не может нормальным образом выполнить свою работу? Возможны разные варианты обработки такой ситуации. Функция может возвращать код ошибки или специальное значение типа HResult, может выбрасывать исключение, тип которого характеризует возникшую ошибку. В CLR принято во всех таких ситуациях выбрасывать исключение. Косвенно это влияет и на язык программирования. Выбрасывание исключений наилучшим образом согласуется с исполнительной средой. В языке C# выбрасывание исключений, их дальнейший перехват и обработка – основной рекомендуемый способ обработки исключительных ситуаций.

События

У CLR есть свое видение того, что представляет собой тип. Есть формальное описание общей системы типов CTS – Common Type System. В соответствии с этим описанием, каждый тип, помимо полей, методов и свойств, может содержать и события. При возникновении событий в процессе работы с тем или иным объектом данного типа посылаются сообщения, которые могут получать другие объекты. Механизм обмена сообщениями основан на делегатах – функциональном типе. Надо ли говорить, что в язык C# встроен механизм событий, полностью согласованный с возможностями CLR. Мы подробно изучим все эти механизмы, рассматривая их на уровне языка.

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

Общие спецификации и совместимые модули

Уже говорилось, что каркас Framework .Net облегчает межъязыковое взаимодействие. Для того чтобы классы, разработанные на разных языках, мирно уживались в рамках одного приложения, для их бесшовной отладки и возможности построения разноязычных потомков, они должны удовлетворять некоторым ограничениям. Эти ограничения задаются набором общеязыковых спецификаций – CLS (Common Language Specification). Класс, удовлетворяющий спецификациям CLS, называется CLS-совместимым. Он доступен для использования в других языках, классы которых могут быть клиентами или наследниками совместимого класса.

Спецификации CLS точно определяют, каким набором встроенных типов можно пользоваться в совместимых модулях. Понятно, что эти типы должны быть общедоступными для всех языков, использующих Framework .Net. В совместимых модулях должны использоваться управляемые данные и выполняться некоторые другие ограничения. Заметьте, ограничения касаются только интерфейсной части класса, его открытых свойств и методов. Закрытая часть класса может и не удовлетворять CLS. Классы, от которых не требуется совместимость, могут использовать специфические особенности языка программирования.

Framework .Net 3.5

Рассмотрим новинки, появившиеся в последней версии Framework .Net 3.5.  Прежде всего, заметим, что практически все новинки языка C# 3.0 поддержаны нововведениями в Framework .Net 3.5.

LINQ и деревья выражений

Уже говорилось, что в C# 3.0 встроен язык запросов к данным, что существенно облегчает работу с данными, поступающими из внешних источников. Этот языковый механизм поддерживается классами библиотеки FCL Framework .Net 3.5. Пространство System.Linq содержит классы, задающие типы, интерфейсы, стандартные операторы запроса.  Пространства System.Data.Linq , System.Data.Linq.Mapping поддерживают работу с реляционными базами данных. Классы пространства System.XML.Linq поддерживают запросы к XML- данным. Новые классы DataRowComparer, DataRowExtensions, DataTableExtensions позволяют локально хранить данные, заменяя объекты DataSet ADO .Net. Классы из пространства System.Linq.Expressions позволяют работать с деревьями выражений, используемых в запросах.

Windows Presentation Foundation

В Visual Studio 2008 появились новые типы проектов, основанные на возможностях предоставляемых технологией WPF (Windows Presentation Foundation). Эта технология позволяет строить новое поколение систем презентации – с новыми графическими возможностями, связыванием данных и прочими элементами, придающими приложению принципиально новые свойства. Предполагается, что этот тип приложений постепенно будет вытеснять традиционные Windows приложения, основанные на понятии окна.

Windows Communication  Foundation (WCF) и Windows Workflow Foundation (WF)

Технологии WCF и WF позволяют строить специализированные приложения и службы (Services), позволяющие приложениям обмениваться данными, используя асинхронный ввод-вывод.

ASP   .NET

Новые возможности Framework .Net 3.5 облегчают разработку Веб- приложений, в частности построение сайтов с AJAX (Asynchronous Javascript and XML) – свойствами. Приложения с такими свойствами становятся более быстрыми и удобными, позволяя при взаимодействии с сервером не перезагружать всю страницу полностью.

Другие новинки

Трудно, да и не имеет особого смысла перечислять все нововведения, появившиеся в Framework .Net 3.5. При обсуждении новых возможностей при построении приложений на языке C# несомненно речь будет идти и о том, как эти возможности поддерживаются в CLR и FCL.

Управляемый и неуправляемый код

Как уже отмечалось результатом написанного на C# проекта и скомпилированного в Visual Studio 2008  является сборка (assembly), содержащая IL-код проекта и манифест, полностью описывающий сборку. Сборка может быть создана на одном компьютере, на одной платформе, а выполняться на другом компьютере с другим типом процессора, с другой операционной системой. Для выполнения сборки необходимо и достаточно  установки на целевом компьютере соответствующей версии Framework .Net, представляющего надстройку над операционной системой.

Когда мы говорим о сборках, то язык программирования, на котором создавался исходный код, уже не имеет значения, его особенности никак не отражаются в сборке. Сборки, созданные на VB или C++ с управляемыми расширениями, неотличимы от сборок, созданных на C# или других языках, включенных в состав Visual Studio 2008 и использующих каркас Framework .Net при компиляции управляемого кода.

С другой стороны понятно, что в состав Visual Studio 2008 могут включаться языки, не использующие Framework .Net, не создающие сборки с управляемым кодом, а использующие собственные библиотеки и собственные каркасы приложений (Framework Applications). В частности на языке С++ в рамках Visual Studio 2008 можно писать проекты, использующие библиотеки MFC и ATL, ориентированные исключительно на С++ и создающие в результате компиляции проекта, обычные exe-файлы.

Сегодня на всех компьютерах, работающих под управлением любой из версий Windows, установлена соответствующая версия Framework .Net, так что на таких компьютерах могут выполняться и сборки и обычные exe-файлы. Поскольку Framework .Net, также как и C# стандартизован и является свободно распространяемым программным продуктом, то его можно встретить и на тех компьютерах, где нет Windows.

На рис. 1_1 показана схема функционирования компьютера, позволяющего выполнять как сборки – управляемый код, так и обычные exe-файлы – неуправляемый код.

Рис. 1_1 Управляемый и неуправляемый код

Заметьте, два мира программ, выполняемые по-разному, могут взаимодействовать друг с другом – из управляемого кода возможен вызов программ с неуправляемым кодом и наоборот. В проектах, написанных на C#, можно управлять офисными приложениями – документами Word и Excel. Офисные документы – это COM-объекты, принадлежащие миру неуправляемого кода, а проекты C# — это сборки – жители страны с управляемым кодом.

Проекты C# в Visual Studio 2008

При запуске Visual Studio 2008, которая, надеюсь, уже установлена на Вашем компьютере, открывается стартовая страница. В окне «Recent Projects» стартовой страницы есть две скромные, непрезентабельного вида две ссылки – «Open:     Project…» и «Create    Project…».

Они задают две основные функции, которые может выполнять разработчик в Visual Studio 2008, – он может открывать существующие проекты и работать с ними, или создать и работать с новым проектом. В большинстве случаев после открытия стартовой страницы щелчком по одной из ссылок мы переходим к созданию или открытию проекта. Вид стартовой страницы показан на рис. 1_2.

Рис. 1_2 Вид стартовой страницы

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

Для компьютера, подключенного к интернет, стартовая страница автоматически связывается с сайтом, содержащим текущую информацию по C# и Visual Studio 2008, — по умолчанию показываются новости с сайта msdn.  Выбрав соответствующий пункт из раздела Getting Started (Давайте Начнем), можно получить информацию из центра разработчиков C#, можно подключиться к одному из трех форумов по языку C#, можно получить нужную справку в режиме «on line».

На стартовой странице, помимо вкладки «StartPage», расположена вкладка «Главная страница MSDN», позволяющая перейти к соответствующему сайту. На рис. 1_3  показана страница, открытая при выборе этой вкладки.

Рис. 1_3 Вид главной страницы MSDN

Коль скоро речь зашла о получении текущей информации и справок по языку C#, приведу несколько полезных ресурсов:

http://forums.msdn.microsoft.com/en-us/forums/  — англоязычный сайт предоставляет доступ к различным форумам, в том числе форумам по языку С#.

http://msdn.microsoft.com/ru-ru/default.aspx  — русскоязычный сайт msdn.

http://msdn.microsoft.com/en-us/vcsharp/default.aspx — англоязычный сайт по языку C# на msdn.

http://csharpfriends.com/ — англоязычный сайт, где можно найти нужную информацию, задать вопросы и получить ответы от сообщества разработчиков.

Создание проекта

Выбрав на стартовой странице ссылку «Создать проект», переходим на страницу создания нового проекта. На рис. 1_4 показан внешний вид этой страницы.

Рис. 1_4 Окно создания нового проекта

Посмотрите, какой широкий спектр возможностей предлагает Visual Studio 2008 своим разработчикам.

В окне категорий типов проекта — «Project Types» можно выбрать категорию, определяющую набор шаблонов, задающих типы проектов данной категории. Первые три категории, показанные на рисунке, задают язык программирования, на котором будут создаваться проекты. Эти категории позволяют создавать проекты на трех разных языках, включенных Microsoft в состав студии. Уже говорилось, что среда является открытой и дополнительный состав языков определяется предпочтениями разработчика. При включении нового языка в состав Visual Studio 2008 число категорий, задающих язык, будет увеличиваться.

Еще одна категория «Other Project Types» определяет шаблоны типов проектов, не связанных с языком программирования. Сюда входят проекты, предназначенные для развертывания приложений, проекты для работы с реляционными базами данных и языком запросов SQL, проекты построения Add-in, расширяющие возможности  других приложений, например приложений Office.

Последняя категория «Test Projects»включает шаблон проекта, задающего тестовый документ.

Каждая категория включает подкатегории со своими шаблонами типов проектов. Суммарное число типов  проектов, которые разработчик может построить в рамках Visual Studio 2008, достаточно велико. Я не думаю, что есть разработчики, которые используют в своей работе все типы проектов.  И в этой книге, в этом курсе  будем рассматривать только часть возможных типов проектов.

Категория проектов на языке C# включает в настоящее время 9 подкатегорий.  Наиболее часто используемыми являются первые две категории  — Windows и Web. Первая из них позволяет строить Windows-проекты, предназначенные для работы на локальном компьютере. Вторая подкатегория позволяет строить Web-приложения, работающие в сети интернет или интранет. Этим типам проектов, по крайней мере части из них будет уделено основное внимание в нашем курсе.

Новые категории проектов на C# появились в связи с упоминавшимися новинками Framework .Net 3.5. В частности появились отдельные подкатегории WCF и Workflow, позволяющие строить проекты, использующие упоминавшиеся технологии WCF и WF, включенные в новый каркас.

Рассмотрим чуть более подробно категорию Windows-проектов. Она включает на момент написания этого текста 10 типов проектов:

  • Windows Forms Application – основной вид проектов, строящихся на языке C#. Большинство примеров в рамках нашего курса будут использовать этот тип проектов. Интерфейс таких приложений строится  в визуальном стиле на основе популярных форм Windows. Приставка Visual в названии языка во многом определяется возможностями этого типа проектов.
  • Class Library – проект, позволяющий построить DLL (Dynamic Link Library) – динамически подключаемую библиотеку классов. Этот вид проектов будет столь же часто встречаться в наших примерах, как и предыдущий. Дело в том, что всегда полезно отделять содержательную часть приложения от ее интерфейса. Классы, определяющие содержание приложения, будут собираться в DLL, которая затем будет подключаться к проекту, описывающему интерфейс приложения. Такой подход в наибольшей степени соответствует повторному использованию. Один раз созданные содержательные классы, собранные в DLL, могут использоваться в разных приложениях.
  • Console Applications – этот тип проектов почти не используется в программных продуктах, для которых интерфейс играет крайне важную роль. Тем не менее, это весьма популярный у разработчиков тип проектов, используемый для внутренних целей в ходе разработки. Этот тип проектов довольно часто будет появляться в наших примерах, когда для понимания тех или иных возможностей языка C# достаточно весьма простого интерфейса – ввода и вывода данных на консоль.
  • Windows Forms Control Library  — полезный и часто используемый тип проектов. Он применяется при создании повторно используемого элемента, обладающего визуальным интерфейсом.
  • WPF Application,  WPF Browser Application, WPF User Control Library, WPF Custom Control Library – 4 типа проектов, связанных с упоминавшейся новой технологией WPF, включенной в состав каркаса Framework .Net 3.5.
  • Windows Service – проект, задающий службы (сервисы), предоставляемые удаленным компьютером.
  • Empty  — пустой проект. Все предыдущие типы проектов изначально предлагают разработчику проекта вполне определенную функциональность. Когда при создании проекта разработчик указывает его тип, то из библиотеки классов FCL, входящей в состав каркаса Framework .Net, выбираются классы, задающие архитектуру данного типа проекта. Эти классы составляют каркас проекта, построенного по умолчанию для данного типа, они и определяют функциональность, присущую данному типу проекта. Разработчику проекту остается каркас проекта дополнить плотью и кровью, добавив собственные классы и расширив функциональность классов, входящих в каркас проекта. Для пустого проекта начальная функциональность отсутствует – разработчик все должен делать сам  — ab ovo.  Мы пустыми проектами заниматься не будем.

Определение основных понятий

Эта глава носит обзорный характер.  В ней введено много новых понятий.  Полезно дать определения введенных понятий, которые будут использоваться по ходу всего курса.

Класс (Class)

Класс – это центральное понятие объектно-ориентированного программирования и языка C#. Разработчик проектов на C# использует стандартные классы из библиотеки FCL и создает собственные классы. У класса две различные роли:

Класс – это модуль – архитектурная единица построения проекта по модульному принципу. Справиться со сложностью большого проекта можно только путем деления его на модули, — сравнительно небольшие единицы,  допускающие независимую разработку и последующее объединение в большую систему.

Класс – это тип данных. Тип данных – это семантическая единица, описывающая свойства и поведение множества объектов, называемых экземплярами класса. Синтаксически класс представляет описание данных, называемых полями класса, описание методов класса и описание событий класса. Для класса, рассматриваемого как тип данных, поля определяют состояние объектов, методы – поведение объектов. События – это некоторые специальные состояния, в которых может находиться объект, и которые могут обрабатываться внешними по отношению к классу обработчиками события. Так, например, объект класса Person может иметь событие «День рождения» и каждый из обработчиков этого события может принести объекту свои поздравления по этому случаю.

Как правило, класс C# играет обе роли. Но язык C# позволяет определять классы, играющие только роль  модуля. Это так называемые статические классы, для которых невозможно создавать объекты. В ходе выполнения программной системы создается единственный экземпляр такого класса, обеспечивающий доступ к полям и методам этого модуля.

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

Объект (Object)

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

Заметьте, путаница понятий класса и объекта характерна и для опытных разработчиков. Показателен тот факт, что центральный класс в библиотеке FCL, являющийся прародителем всех классов как библиотечных, так и создаваемых разработчиком проектов C#, назван именем Object.

Пространство имен (Namespace)

Пространство имен – это оболочка, содержащая множество классов, объединенных, как правило, общей тематикой или группой разработчиков. Собственные имена классов внутри пространства имен должны быть уникальны. В разных пространствах могут существовать классы с одинаковыми именами. Полное или уточненное имя класса состоит из уникального имени пространства имен и собственного имени класса. В пространстве имен могут находиться как классы, так и пространства имен.

Пространства имен позволяют задать древесную структуру на множестве классов большого проекта. Они облегчают независимую разработку проекта большим коллективом разработчиков, каждая группа которого работает в своем пространстве имен.

Пространства имен придают структуру библиотеке FCL, содержащей большое число различных пространств имен, объединяющих классы определенной тематики. Центральным пространством имен библиотеки FCL является пространство System, содержащее другие пространства и классы, имеющие широкое употребление в различных проектах.

Проект (Project)

Проект – это единица компиляции. Результатом компиляции проекта является сборка. Каждый проект содержит одно или несколько пространств имен. Как уже говорилось, на начальном этапе создания проекта по заданному типу проекта автоматически строится каркас проекта, состоящий из классов, являющихся наследниками классов, входящих в состав библиотеки FCL. Так, если разработчик указывает, что он хочет построить проект типа «Windows  Forms Application», то в состав каркаса проекта по умолчанию войдет класс Form1 – наследник библиотечного класса Form. Разработчик проекта населит созданную форму элементами управления – объектами соответствующих классов, тем самым расширив возможности класса, построенного по умолчанию.

Каждый проект содержит всю информацию, необходимую для построения сборки. В проект входят все файлы с классами, построенные автоматически в момент создания проекта, и файлы с классами, созданные разработчиком проекта. Помимо этого проект содержит ссылки на пространства имен из библиотеки FCL, содержащие классы, используемые в ходе вычислений. Проект содержит ссылки на все подключаемые к проекту DLL, COM-объекты, другие проекты. В проект входят установки и ресурсы, требуемые для работы. Частью проекта является файл, содержащий описание сборки.

В зависимости от выбранного типа проект может быть выполняемым или невыполняемым. К выполняемым проектам относятся, например,  проекты типа Console или Windows. При построении каркаса выполняемого проекта  в него включается класс, содержащий статическую процедуру с именем Main.  В результате компиляции такого проекта создается PE-файл (Portable Executable file) выполняемый переносимый файл. Напомним, что PE-файл может выполняться только на компьютерах, где установлен Framework .Net, поскольку это файл с управляемым кодом.

К невыполняемым проектам относятся, например, проекты типа DLL. В результате компиляции такого проекта в сборку войдет файл с уточнением dll. Такие проекты (сборки) непосредственно не могут быть выполнены на компьютере. Они присоединяются к выполняемым сборкам, откуда и вызываются методы классов, размещенных в невыполняемом проекте (DLL) .

Сборка (Assembly)

Сборка – результат компиляции проекта. Сборка представляет собой коллекцию из одного или нескольких файлов, помеченных номером версии. Каждая сборка разворачивается на компьютере как единое целое. Программист работает с проектами, CLR  работает со сборками. Сборка позволяет решать вопросы безопасности, так как содержит описание требуемых ей ресурсов и права доступа к элементам сборки. Каждая сборка содержит манифест, содержащий полное описание сборки, ее элементов, требуемые ресурсы, ссылки на другие сборки, исполняемые файлы. Благодаря этому описанию CLR  не требуется никакой дополнительной информации для развертывания сборки, трансляции промежуточного кода и его выполнения. Манифест идентифицирует сборку, специфицирует файлы, требуемые для реализации сборки, специфицирует типы и ресурсы, составляющие сборку, задает зависимости, необходимые в период компиляции для связи с другими сборками, специфицирует множество разрешений, необходимых, чтобы сборка могла выполняться на данном компьютере.

Решение (Solution)

Каждый проект, создаваемый в Visual Studio 2008, помещается в некоторую оболочку, называемую Решением – Solution.  Решение может содержать несколько проектов, как правило, связанных общей темой. Например, все проекты, рассматриваемые в одной главе книги, я мог бы поместить в одно Решение.  В наших примерах зачастую Решение будет содержать три проекта – DLL  с классами, определяющими содержательную сторону приложения, и два интерфейсных проекта – консольный и Windows.

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

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

Пример

Один из принципов, которых я придерживаюсь при написании книг по программированию, состоит в том, что в таких книгах программный код должен составлять существенную часть текста. Этот код следует читать и изучать не менее внимательно, чем обычный текст. Зачастую он говорит больше чем рассуждения автора. Поэтому и данная глава заканчивается примером, иллюстрирующим основные понятия, введенные в главе. Я отказался от традиции начинать с классического приложения «Здравствуй, мир!». Для первого рассмотрения наш пример будет достаточно сложным, — мы построим Решение, содержащее три проекта – проект DLL, консольный проект и Windows-проект.

Постановка задачи

Начну с описания содержательной постановки задачи.  Вначале некоторая преамбула. В системе типов языка C# есть несколько типов, задающих различные подмножества арифметического типа данных – int, double и другие. Для значения x любого из этих типов хорошо бы уметь вычислять математические функции – sin(x), ln(x) и другие. Встраивать вычисление этих функций в каждый из классов, задающих соответствующий арифметический подтип, кажется неразумным. Поэтому в библиотеку FCL включен класс Math, методы которого позволяют вычислять по заданному аргументу нужную математическую функцию. Класс Math является примером статического класса, играющего единственную роль – роль модуля. У этого класса нет собственных данных, если не считать двух математических констант – e и π, а его методы являются сервисами, которые он предоставляет другим классам.

Построим аналог класса Math  и поместим этот класс в DLL, что позволит повторно использовать его, присоединяя при необходимости к различным проектам. В нашем примере не будем моделировать все сервисы класса Math. Рассмотрим вначале вычисление функции sin(x). Эту функцию, как и другие математические функции, можно вычислить, используя разложение в ряд Тэйлора:

(1)

Детали вычислений, использующих формулу (1), отложим на момент реализации. А пока продолжим уточнять цель нашего примера. Итак, мы хотим построить DLL – динамическую подключаемую библиотеку, содержащую аналог класса Math из библиотеки FCL. Затем мы хотим построить консольный проект, позволяющий провести тестирование корректности вычислений функций построенного нами класса. Затем мы построим Windows-проект, интерфейс которого позволит провести некоторые интересные исследования. Все три проекта будут находиться в одном Решении (Solution).

Создание DLL — проекта типа «Class Library»

Запустим Visual Studio 2008, со стартовой страницы перейдем к созданию проекта и в качестве типа проекта укажем тип  «Class Library».  В открывшемся окне создания DLL, показанном на рис. 1_5,  все поля заполнены значениями по умолчанию. Как правило, их следует переопределить, задавая собственную информацию.

Рис. 1_5 Создание проекта DLL

В поле Name задается имя строящейся DLL – MathTools в нашем случае.

В поле Location указывается путь к папке, где будет храниться Решение, содержащее проект. Для Решений этой книги создана специальная папка.

В поле Solution выбран элемент «Create New Solution», создающий новое Решение. Альтернативой является элемент списка, указывающий, что проект может быть добавлен к существующему Решению.

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

Обратите внимание и на другие установки, сделанные в этом окне  — включен флажок (по умолчанию) «Create directory for solution», в верхнем окошке из списка возможных каркасов выбран каркас Framework .Net 3.5. Задав требуемые установки и щелкнув по кнопке «OK», получим автоматически построенную заготовку проекта DLL, открытую в среде разработки проектов Visual Studio 2008 . На рис.1_6 показан внешний вид среды с построенным Решением и проектом.

Рис. 1_6 Среда Visual Studio 2008 с начальным проектом DLL

Среду разработки можно настраивать, открывая или закрывая те или иные окна, перемещая и располагая их по своему вкусу. Это делается стандартным способом, и я не буду на этом останавливаться.

В окне проектов Solution Explorer показано Решение с именем «Ch1», содержащее проект DLL  с именем «MathTools». В папке «Properties» проект содержит файл с описанием сборки – ее имя и другие характеристики. В папке «References» проект содержит ссылки на основные пространства имен библиотеки FCL, которые могут понадобиться в процессе работы DLL.

Поскольку всякая DLL  содержит один или несколько классов, то для одного класса, которому по умолчанию дано имя «Class1», заготовка построена. Класс этот, показанный в окне кода, пока что пуст – не содержит никаких элементов.

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

Мы рассмотрели подготовительную часть работы, которую Visual Studio 2008 выполнила для нас. Дальше предстоит потрудиться самим.  С чего следует начать?  С переименования! Важное правило стиля программирования говорит, что имена классов должны быть содержательными. Изменим имя «Class1» на имя «MyMath».  Как следует правильно изменять имена объектов в проектах? Никак не вручную. В окне кода проекта выделите имя изменяемого объекта, затем в главном меню выберите пункт Refactor и подпункт Rename. В открывшемся окне укажите новое имя. Тогда будут показаны все места, требующие переименования объекта. В данном случае будет только одна очевидная замена, но в общем случае замен много, так что автоматическая замена всех вхождений крайне полезна.

Следующий шаг также продиктован правилом стиля – имя класса и имя файла, хранящего класс, должны совпадать. Переименование имени файла делается непосредственно в окне проектов Solution Explorer.

И следующий шаг продиктован крайне важным правилом стиля, имеющим собственное название – правило «И не вздумайте!», которое гласит — и не вздумайте написать класс без заголовочного комментария.  Для добавления комментария достаточно в строке, предшествующей заголовку класса набрать три подряд идущих слеша (три косых черты). В результате перед заголовком класса появится заголовочный комментарий – тэг «summary», в который и следует добавить краткое, но содержательное описание сути класса. Тэги «summary», которыми следует сопровождать классы, открытые (public) методы и поля класса играют три важные роли. Они облегчают разработку и сопровождение проекта, делая его само документируемым. Клиенты класса при создании объектов класса получают интеллектуальную подсказку, поясняющую суть того, что можно делать с объектами. Специальный инструментарий позволяет построить документацию по проекту, включающую информацию из тегов «summary». В нашем случае комментарий к классу MyMath может быть достаточно простым – «Аналог класса Math  библиотеки FCL».

Поскольку мы хотим создать аналог класса Math, то в нашем классе должны быть аналогичные методы. Начнем, как уже говорилось, с метода, позволяющего вычислить функцию sin(x). Заголовок метода сделаем такой же, как и в классе аналоге. Согласно правилу стиля «И не вздумайте» зададим заголовочный комментарий к методу. В результате в тело класса добавим следующий код:

/// <summary>

/// Sin(x)

/// </summary>

/// <param name=»x»>угол в радианах — аргумент функции Sin</param>

/// <returns>Возвращает значение функции Sin для заданного угла</returns>

public static double Sin(double x)

{

}

Осталось написать реализацию вычисления функции, заданную формулой (1). Как и во всяком реальном программировании для этого требуется знание некоторых алгоритмов. Алгоритмы вычисления конечных и бесконечных сумм относятся к элементарным алгоритмам, изучаемым в самом начале программистских курсов. Хотя этот учебник я пишу в ориентации на лиц, владеющих программированием и основами алгоритмических знаний, но я хотел бы, чтобы он был доступен и для тех, для кого C# является первым языком программирования. Поэтому прежде, чем написать программный текст, несколько слов о том, как вычислять конечные и бесконечные суммы, аналогичные формуле (1), задающей вычисление функции sin(x). Искушенные читатели могут пропустить этот текст.

Вычисление конечных и бесконечных сумм

Вычисление конечных сумм и произведений – это наиболее часто встречающийся тип элементарных задач, шаблон решения которых должен быть заучен, как 2*2. Какова бы не была сложность выражений, стоящих под знаком конечной суммы с заданным числом слагаемых, задачу всегда можно записать в виде:

(2)

и применить для ее решения следующий шаблон:

S=0;

for(int k=1; k<=n; k++)

{

//Вычислить текущий член суммы ak

S+=ak;

}

Часто приходится пользоваться слегка расширенным шаблоном:

Init;

for(int k=1; k<=n; k++)

{

//Вычислить текущий член суммы ak

S+=ak;

}

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

В этой схеме основные проблемы могут быть связаны с вычислением текущего члена суммы ak. Нужно понимать, что ak – это не массив, а скаляр — простая переменная. Значения этой переменной вычисляются заново на каждом шаге цикла, задавая очередной член суммирования. Кроме того, следует заботиться об  эффективности вычислений, применяя два основных правила, позволяющие уменьшить время вычислений:

  • Чистка цикла. Все вычисления, не зависящие от k, следует вынести из цикла (в раздел Init).
  •  Рекуррентная формула. Часто можно уменьшить время вычислений ak, используя предыдущее значение ak, построив рекуррентную формулу ak+1 = f(ak). Этот прием с успехом используется как при вычислении функции sin(x) по формуле (1), так и при аналогичных вычислениях большинства других математических функций.

Покажем на примере формулы (1), как можно построить необходимые рекуррентные соотношения. Запишем соотношения для a0, ak, ak+1:

(3)

Вычислив отношение  ak+1/ak, получим требуемое рекуррентное соотношение:

 

Значение a0 задает базис вычислений, позволяя инициализировать начальное значение переменной ak, а соотношение (4) позволяет каждый раз в теле цикла вычислять новое значение этой переменной. Заметьте, введение рекуррентного соотношения позволило избавиться от вычисления факториалов и возведения в степень на каждом шаге цикла.

Иногда следует ввести несколько дополнительных переменных, хранящие вычисленные значения предыдущих членов суммы. Рекуррентная формула выражает новое значение ak через предыдущее значение и дополнительные переменные, если они требуются. Начальные значения ak и дополнительных переменных должны быть корректно установлены перед выполнением цикла в разделе Init. Заметьте, если начальное значение ak вычисляется в разделе Init до цикла, то схема слегка модифицируется, — вначале выполняется прибавление ak к S, а затем новое значение ak вычисляется по рекуррентной формуле.

А теперь поговорим о том, как справляться с бесконечными суммами, примером которых является формула (1).  Для математики бесконечность естественна. Множество целых чисел бесконечно, множество рациональных чисел бесконечно, множество вещественных чисел бесконечно. Элементы первых двух множеств можно пронумеровать – они задаются счетными множествами, множество вещественных чисел несчетно. Сколь угодно малый промежуток вещественной оси мы бы не взяли, там находится бесконечно много вещественных чисел. Число π и другие иррациональные числа задаются бесконечным числом цифр, не имеющим периода.

Мир компьютеров – это конечный мир, хотя в нем и присутствует стремление к бесконечности. Множества, с которыми приходится оперировать в мире компьютера, всегда конечны. Тип целых чисел в языках программирования – int – всегда задает конечное множество целых из некоторого фиксированного диапазона. В библиотеке FCL это наглядно подтверждается самими именами целочисленных типов System.Int16, System.Int32, System.Int64. Типы вещественных чисел – double, float – задают конечные множества. Это достигается не только тем, что диапазон задания вещественных чисел ограничен, но и ограничением числа значащих цифр, задающих вещественное число. Поэтому для вещественных чисел компьютера всегда можно указать наборы таких двух чисел, между которыми нет никаких других чисел. Иррациональности компьютер не знает, – число π всегда задается конечным числом цифр.

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

 

По определению число A является пределом числовой последовательности, если для любого сколь угодно малого числа ε существует такой номер N, зависящий от ε, что для всех n, больших N, числа an находятся в ε-окрестности числа A. Это определение дает основу для вычисления значения предела A. Понятно, что получить точное значение A во многих случаях принципиально невозможно, – его можно вычислить лишь с некоторой точностью и тоже не сколь угодно малой, поскольку существует понятие «машинного нуля» — минимального числа, все значения меньше которого воспринимаются как нуль. Когда два соседних члена последовательности – an и an+1 – начинают отличаться на величину по модулю меньшую чем δ, то можно полагать, что оба члена последовательности попали в ε-окрестность числа A и an+1 можно принять за приближенной значение числа A. Это рассуждение верно только при условии, что последовательность действительно имеет предел. В противном случае этот прием может привести к ошибочным выводам. Например, рассмотрим последовательность, элементы которой равны 1, если индекс элемента делится на 3, и равны 2, если индекс не делится на 3. Очевидно, что у этой последовательности предела нет, хотя существуют полностью совпадающие соседние члены последовательности.

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

Вернемся к  задаче вычисления функции sin(x). Вот возможный шаблон решения:

Init;

while(Abs(ak) > EPS)

{

S+=ak;

k++;

//Вычислить новое значение ak

}

При применении этого шаблона предполагается, что в разделе Init объявляются и должным образом инициализируются нужные переменные – S, ak, k. По завершению цикла переменная S содержит значение функции, вычисленное с заданной точностью.

Теперь мы готовы расширить определение класса, добавив код метода.

Код

Приведем полный код проекта DLL, построенный на данный момент:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace MathTools

{

/// <summary>

/// Аналог класса Math библиотеки FCL

/// </summary>

public class MyMath

{

//Константы класса

const double TWOPI = 2 * Math.PI;

const double EPS = 1E-9;

 

//Статические методы класса

 

/// <summary>

/// Sin(x)

/// </summary>

/// <param name=»x»>

///     угол в радианах — аргумент функции Sin

/// </param>

/// <returns>

///     Возвращает значение функции Sin для заданного угла

/// </returns>

public static double Sin(double x)

{

//Оптимизация — приведение к интервалу

x = x % TWOPI;

 

//Init

double a = x;

double res = 0;

int k = 0;

 

//Основные вычисления

while (Math.Abs(a) > EPS)

{

res += a;

a *= -x * x / ((2 * k + 2) * (2 * k + 3));

k++;

}

return res;

}

}

}

Поставленная цель достигнута, — построена DLL, содержащая класс, метод которого позволяет вычислять по заданному аргументу x функцию sin(x). Метод построен в полном соответствии с описанным алгоритмом. При его построении использованы две важные оптимизации. Во-первых, применено рекуррентное соотношение, позволяющее существенно ускорить время и точность вычисления функции – попробуйте объяснить, почему улучшаются оба эти параметра. Во-вторых, аргумент x приведен к сравнительно небольшому интервалу, что увеличивает скорость сходимости и гарантирует работоспособность метода для больших значений x. Если не делать этой оптимизации, то для больших по модулю значений метод может давать некорректные результаты, – проверьте это предположение.

Итак, все хорошо? Не совсем. Оптимизацию можно продолжить, правда, не столь уже существенную. Сейчас для вычисления значения переменной a  требуется выполнить одно деление, пять умножений, два сложения, взятие результата с обратным знаком. Попробуйте самостоятельно написать новую версию метода с улучшенными показателями, не глядя на код, который я сейчас приведу. Я добавил в класс новую версию метода, сохранив для новой версии имя метода – Sin. В классе остался и старый метод, но уже с именем SinOld. Две версии, давая один и тот же результат вычислений, позволят нам в дальнейшем провести некоторые полезные исследования.

Вот код метода с дополнительной оптимизацией:

public static double Sin(double x)

{

//Оптимизация — приведение к интервалу

x = x % TWOPI;

 

//Init

double a = x;

double res = 0;

int k = 0;

double x2 = x * x;

 

//Основные вычисления

while (Math.Abs(a) > EPS)

{

res += a;

k+=2;

a *= -x2 / (k * (k + 1));

}

return res;

}

Код метода стал элегантнее, короче, вместо пяти умножений теперь делается только два, и вместо двух сложений – одно.

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

А теперь вернемся к технической стороне дела. Построим Решение, содержащее проект, для чего в Главном меню среды выберем пункт Build | Build Solution. В результате успешной компиляции будет построен файл с уточнением dll. Поскольку построенная сборка не содержит выполняемого файла, то непосредственно запустить наш проект на выполнение не удастся. Построим консольный  проект, к которому присоединим нашу DLL, и протестируем, насколько корректно работают созданные нами методы. Заодно разберемся с тем, как строится консольный проект и как к нему подсоединяется сборка, содержащая DLL.

Консольный проект

Наша цель состоит в том, чтобы построить интерфейс, обеспечивающий конечному пользователю доступ к тем сервисам, которые предоставляет построенная DLL.  Начнем с построения простейшего интерфейса, позволяющего пользователю с консоли вводить исходную информацию – в нашем случае аргумент x. С исходными данными пользователь может провести вычисления, вызвав сервисы, предоставляемые DLL, а затем полученные результаты вывести на консоль – экран дисплея.  Для организации подобного интерфейса и служит тип проекта – Console Application.

Чтобы создать новый проект, находясь в среде разработки, вовсе не обязательно начинать со стартовой страницы. Достаточно выбрать пункт меню File|New|Project, приводящий на страницу создания нового проекта, показанную на рис. 1_5. В этом окне, как описано ранее, зададим тип строящегося проекта, дадим ему имя – ConsoleToMathTools, укажем, что проект добавляется к существующему Решению Ch1.  В результате в уже существующее Решение добавится еще один проект, что отображено на рис. 1_7

Рис. 1_7 Решение, включающее консольный проект

Как показано на рис. 1_7 в консольном проекте автоматически создается  класс с именем Program, содержащий единственный статический метод – процедуру  Main.  Если скомпилировать этот проект и запустить его на выполнение, то начнет выполняться код этой процедуры, пока отсутствующий и который предстоит нам создать.

Начало начал – точка «большого взрыва»

Основной операцией, инициирующей вычисления в объектно-ориентированных приложениях, является вызов метода F некоторого класса, имеющий вид:

x.F(arg1, arg2, …, argN);

В этом вызове x  - это некоторый  существующий объект, называемый целью вызова. Возможны три ситуации:

  • x – имя класса. Объектом в этом случае является статический объект, который всегда создается в момент трансляции кода класса. Метод F должен быть статическим методом класса, объявленным с атрибутом static, как это имеет место для точки вызова – процедуры Main;
  • x – имя объекта или объектное выражение. В этом случае F может быть обычным, не статическим методом. Иногда такой метод называют экземплярным, подчеркивая тот факт, что метод вызывается экземпляром класса – некоторым объектом;
  • x – не указывается при вызове. В отличие от двух первых случаев такой вызов называется неквалифицированным. Заметьте, неквалифицированный вызов вовсе не означает, что цель вызова отсутствует, – она просто задана по умолчанию. Целью является текущий объект, имеющий зарезервированное имя this. Применяя это имя, любой неквалифицированный вызов можно превратить в квалифицированный вызов. Иногда без этого имени просто не обойтись.

Но как появляются объекты? Как они становятся текущими? Как реализуется самый первый вызов метода, другими словами, кто и где вызывает точку входа – метод Main? С чего все начинается?

Когда Решение запускается на выполнение, то в него должна входить сборка, отмеченная как стартовый проект, содержащая класс с точкой входа – статическим методом (процедурой) Main. Некоторый объект исполнительной среды CLR и вызывает этот метод, так что первоначальный вызов метода осуществляется извне приложения. Это и есть точка «большого взрыва» – начало зарождения мира объектов и объектных вычислений. Извне создается и первый объект, задающий статический модуль с методом Main. Этот объект и становится текущим.

Дальнейший сценарий зависит от содержимого точки входа. Как правило, в процессе работы метода Main создаются один или несколько объектов других классов, они и вызывают методы и/или обработчики событий, происходящих с созданными объектами. В этих методах и обработчиках событий могут создаваться новые объекты, вызываться новые методы и новые обработчики. Так, начиная с одной точки, разворачивается целый мир объектов приложения.

Связывание с DLL

Первым делом свяжем два построенных проекта, для чего в консольный проект добавим ссылку на проект с DLL MathTools. В окне Solution Explorer подведем указатель мыши к имени консольного проекта и из контекстного меню, появляющегося при щелчке правой кнопки, выберем пункт меню «Add Reference». В открывшемся окне добавления ссылок  выберем вкладку «Projects». Поскольку проект MathTools включен в Решение, то он автоматически появится в открывшемся окне. Если ссылку нужно установить на проект, не включенный в Решение, то в окне добавления ссылок нужно задать путь к проекту. Нам проще, путь указывать не нужно, достаточно щелкнуть по появившемуся в окне имени MathTools. Ссылка на DLL  появится в папке «References» консольного проекта. Теперь проекты связаны и из консольного проекта доступны сервисы, предоставляемые DLL.

Организация консольного интерфейса

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

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace ConsoleToMathTools

{

class Program

{

/// <summary>

/// Точка входа в консольный проект

/// организация интерфейса к DLL MathTools

/// </summary>

/// <param name=»args»></param>

static void Main(string[] args)

{

//Входные данные

double x = 0;

const string INVITE =

«Введите вещественное число x» +

«- аргумент функции Sin(x)»;

const string CONTINUE =

«Продолжим? (Yes/No)»;

string answer = «yes»;

do

{

//Организация ввода данных

Console.WriteLine(INVITE);

string temp = Console.ReadLine();

x = Convert.ToDouble(temp);

 

//Вычисления и вывод результата

double res = 0;

res = Math.Sin(x);

Console.WriteLine(«Math.Sin(x) = » +

res.ToString());

 

res = MathTools.MyMath.Sin(x);

Console.WriteLine(«MathTools.MyMath.Sin(x) = » +

res.ToString());

 

res = MathTools.MyMath.SinOld(x);

Console.WriteLine(«MathTools.MyMath.SinOld(x) = » +

res.ToString());

 

//диалог с пользователем

Console.WriteLine(CONTINUE);

 

answer = Console.ReadLine();

} while (answer == «yes»);

}

}

}

Дадим краткие комментарии к этому коду:

Входные данные устроены просто – задается лишь одна переменная x типа double. Помните, что в языке C# все переменные являются объектами.

Вводу значения x предшествует, как и положено для хорошего стиля программирования, приглашение к вводу. Для ввода и вывода значений используются статические методы ReadLine и WriteLine класса Console, входящего в библиотеку FCL и предоставляющего свои сервисы пользователям консольных приложений. Для преобразования введенных данных, представляющих собой строки текста, к нужному типу (в нашем случае к типу double) используются статические методы класса Convert, сервисы которого позволяют проводить различные преобразования между типами данных.

Значение функции Sin(x) вычисляется тремя разными методами – методом стандартного класса Math и двумя методами класса MyMath, входящего в состав библиотеки MathTools.

Следуя правилу стиля “Имена константам» в коде метода используются именованные константы.

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

На рис. 1_8 показаны результаты работы консольного проекта:

Рис. 1_8 Результаты работы консольного проекта

Анализируя эти результаты можно видеть, что все три метода на всех исследуемых аргументах дают одинаковые результаты, совпадающие с точностью до 9 цифр после запятой. Точность методов в классе MyMath обеспечивается константой EPS этого класса. Достигнутая точность вполне достаточна для большинства практических задач. Остается понять, насколько написанные нами методы проигрывают методу стандартного класса по времени. Это исследование оставим для следующего проекта – Windows проекта, обеспечивающего интерфейс, дающий пользователю больше возможностей.

Windows проект

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

Рис. 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 показан интерфейс спроектированной формы:

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

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

В классе MyMath для вычисления функции sin(x) построены два метода, для одного из которых проведена дополнительная оптимизация. Оценить ее эффективность не так просто, поскольку при оценке времени работы возможны погрешности, измеряемые десятками миллисекунд, что сравнимо с выигрышем, полученным в результате оптимизации.

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

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

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

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

Некоторый недостаток этого подхода состоит в том, что рабочий код нужно дополнять операторами, нужными только для проведения исследований. Поэтому хочется иметь более удобный инструментарий, и я покажу возможный образец построения подобного инструмента. В созданной  нами библиотеке 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 не возвращает в точности текущее время в момент ее вызова. Это связано с механизмами операционной системы, когда в реальности на компьютере работают несколько процессов и система обработки прерываний имеет некоторый фиксированный квант времени при переключении процессов. Поэтому, запуская измерение времени вычислений на одних и тех же данных можно получать различные данные с точностью, определяемой характеристиками системы прерываний. Это не существенно влияет в целом на получение  временных характеристик, но не позволяет сравнивать методы, время выполнения которых сравнимо с погрешностью временных оценок.

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

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

В класс MyMath добавлены методы, вычисляющие и некоторые другие математические функции. В Решение Ch1добавлен еще один Windows проект, но я уже не буду останавливаться на их подробном описании.

Итоги

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

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

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

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

 Скачать лекцию 1.1