Компьютерные технологии (программирование на C#) - Учебное пособие

бесплатно 0
4.5 88
Изучение особенностей программирования на платформе .NET. Описание библиотеки классов. Конфликт имен и пространство имен. Статический конструктор и класс. Методы Equals и ReferenceEquals. Способы new и virtual, override переопределния членов класса.

Скачать работу Скачать уникальную работу

Чтобы скачать работу, Вы должны пройти проверку:


Аннотация к работе
При компьютерном моделировании физических процессов в научных или образовательных целях большую помощь могут оказать навыки программирования в современных средах с использованием современных языков и библиотек базовых классов.

В настоящее время на смену прежней библиотеке функций и структур Win32, созданной в начале 90-ых годов и составляющей основу OS Windows, пришла новая библиотека .NET (читается «дот-нет»; можно перевести как «сетевое расширение»).

Библиотека .NET создана в начале 2000-ых годов и является библиотекой классов. В настоящее время библиотека .NET составляет основу операционной системы нового поколения. Настоящий курс знакомит с библиотекой .NET и языком программирования C# (читается «си-шарп»; можно перевести как «на пол тона выше си», или «си-диез»), лежащим в ее основе.

Неформальное введение в C#. Интерфейс IINTEGRATOR

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

Формулировка задачи Коши выглядит довольно просто.

Дана система N обыкновенных дифференциальных уравнений 1-ого порядка вида dyi/dt = fi (t, y1, y2,…, YN) с N неизвестными функциями yi(t).

Даны начальные условия - значения неизвестных функций yi0 в некоторой точке t = t0.

Необходимо определить значения функций yi в любой точке t.

Правые части дифференциальных уравнений определены задачей. В каждой точке с координатами t, y1, y2,…, YN функции fi образуют вектор относительно линейных преобразований переменных dyi. Эти вектора fi (t, y1, y2,…, YN), заданные в каждой точке, образуют, как говорят, векторное поле.

При составлении алгоритма необходимо предусмотреть, чтобы класс интегратора имел доступ к следующим данным

Число уравнений N;

Метод, позволяющий вычислять векторное поле fi (t, y1, y2,…, YN) в каждой точке;

Начальные условия;

Погрешность, с которой проводится интегрирование;

Метод, реализующий алгоритм интегрирования до заданной точки;

Наконец, метод, который предоставит пользователю возможность отладки и управления кодом в ходе процесса интегрирования. Это так называемая «функция обратного вызова» (callback function), или «обработчик события».

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

Язык C# позволяет уже на этом, столь общем этапе программирования задачи, написать классы, которые позволят ограничить рамки задачи и кода, требуемого для ее решения. На основании сформулированных требований можно составить так называемый «интерфейс» - совершенно абстрактный класс, состоящий лишь из заголовков методов, которые должны быть реализованы в любом классе, решающем поставленную задачу.

Назовем наш интерфейс IINTEGRATOR. В языке C# все имена интерфейсов принято начинать с буквы I. Синтаксис описания интерфейса выглядит следующим образом public interface IINTEGRATOR

{

// Здесь описываются члены интерфейса

}

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

Описание так называемых членов интерфейса должно размещаться внутри фигурных скобок, которые ограничивают описание интерфейса от остальных классов, описываемых в том же модуле (библиотеке). Любой комментарий к коду, который не должен влиять на его трансляцию, может быть помещен после двойного знака // (double slash) и до конца строки.

В интерфейсе IINTEGRATOR должно быть реализовано свойство - число уравнений N. Этот факт мы опишем в качестве одного из членов интерфейса - так называемого свойства (property) ushort N { get;}

Так в C# выглядит описание абстрактного (не реализованного) свойства.

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

Свойства в C# - это пара методов доступа set и get. Методы доступа устанавливают (set) и возвращают (get) объект того типа, который указан в описании свойства. Метод установки set имеет неявный параметр, обозначаемый служебным словом value. Параметр value является объектом типа свойства. Он содержит в себе значение, которое передается свойству оператором присваивания, если имя свойства находится в правой части оператора. В языке C# оператор присваивания имеет вид простого знака равенства =. Поэтому запись N = 5, могла бы означать в коде C#, что некоторому свойству с именем присваивается значение 5.

Сразу отметим, что в

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

В настоящее время на смену прежней библиотеке функций и структур Win32, созданной в начале 90-ых годов и составляющей основу OS Windows, пришла новая библиотека .NET (читается «дот-нет»; можно перевести как «сетевое расширение»).

Библиотека .NET создана в начале 2000-ых годов и является библиотекой классов. В настоящее время библиотека .NET составляет основу операционной системы нового поколения. Настоящий курс знакомит с библиотекой .NET и языком программирования C# (читается «си-шарп»; можно перевести как «на пол тона выше си», или «си-диез»), лежащим в ее основе.

Неформальное введение в C#. Интерфейс IINTEGRATOR

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

Формулировка задачи Коши выглядит довольно просто.

Дана система N обыкновенных дифференциальных уравнений 1-ого порядка вида dyi/dt = fi (t, y1, y2,…, YN) с N неизвестными функциями yi(t).

Даны начальные условия - значения неизвестных функций yi0 в некоторой точке t = t0.

Необходимо определить значения функций yi в любой точке t.

Правые части дифференциальных уравнений определены задачей. В каждой точке с координатами t, y1, y2,…, YN функции fi образуют вектор относительно линейных преобразований переменных dyi. Эти вектора fi (t, y1, y2,…, YN), заданные в каждой точке, образуют, как говорят, векторное поле.

При составлении алгоритма необходимо предусмотреть, чтобы класс интегратора имел доступ к следующим данным

Число уравнений N;

Метод, позволяющий вычислять векторное поле fi (t, y1, y2,…, YN) в каждой точке;

Начальные условия;

Погрешность, с которой проводится интегрирование;

Метод, реализующий алгоритм интегрирования до заданной точки;

Наконец, метод, который предоставит пользователю возможность отладки и управления кодом в ходе процесса интегрирования. Это так называемая «функция обратного вызова» (callback function), или «обработчик события».

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

Язык C# позволяет уже на этом, столь общем этапе программирования задачи, написать классы, которые позволят ограничить рамки задачи и кода, требуемого для ее решения. На основании сформулированных требований можно составить так называемый «интерфейс» - совершенно абстрактный класс, состоящий лишь из заголовков методов, которые должны быть реализованы в любом классе, решающем поставленную задачу.

Назовем наш интерфейс IINTEGRATOR. В языке C# все имена интерфейсов принято начинать с буквы I. Синтаксис описания интерфейса выглядит следующим образом public interface IINTEGRATOR

{

// Здесь описываются члены интерфейса

}

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

Описание так называемых членов интерфейса должно размещаться внутри фигурных скобок, которые ограничивают описание интерфейса от остальных классов, описываемых в том же модуле (библиотеке). Любой комментарий к коду, который не должен влиять на его трансляцию, может быть помещен после двойного знака // (double slash) и до конца строки.

В интерфейсе IINTEGRATOR должно быть реализовано свойство - число уравнений N. Этот факт мы опишем в качестве одного из членов интерфейса - так называемого свойства (property) ushort N { get;}

Так в C# выглядит описание абстрактного (не реализованного) свойства.

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

Свойства в C# - это пара методов доступа set и get. Методы доступа устанавливают (set) и возвращают (get) объект того типа, который указан в описании свойства. Метод установки set имеет неявный параметр, обозначаемый служебным словом value. Параметр value является объектом типа свойства. Он содержит в себе значение, которое передается свойству оператором присваивания, если имя свойства находится в правой части оператора. В языке C# оператор присваивания имеет вид простого знака равенства =. Поэтому запись N = 5, могла бы означать в коде C#, что некоторому свойству с именем присваивается значение 5.

Сразу отметим, что в нашем случае свойства N такое присваивание оказалось бы невозможным. В описании свойства N отсутствует метод set. А это означает, что интерфейсом IINTEGRATOR предполагается реализация свойства N только по чтению. Метод get свойства возвращает объект типа свойства, то есть «работает» в операторе присваивания, где свойство находится в правой части. Например, CURN = N, где CURN предположительно есть некоторое текущее (current) значение свойства N.

Тип ushort описанного свойства N означает, что свойство N должно возвращать любое целое неотрицательное число, меньшее 65536.

Приведенное описание свойства числа уравнений N в форме «свойства только для чтения» предполагает, что объекты классов, основанных на интерфейсе IINTEGRATOR (говорят, «классы, наследующие интерфейс»), уже при своем конструировании будут иметь заданное число уравнений, для решения которых они создаются. Поэтому необходимости в изменении значения числа уравнений на протяжении жизненного цикла таких объектов не будет возникать.

То же замечание будет относиться и к следующему свойству интерфейса IINTEGRATOR

TEQUATIONS Equations { get;}

Предполагается, что свойство Equations будет возвращать ссылку на метод, который готовит локальный вектор уравнений. Тип этого метода имеет имя TEQUATIONS и должен быть описан где-либо вне скобок, содержащих члены интерфейса IINTEGRATOR.

Описание типа TEQUATIONS имеет вид public delegate void TEQUATIONS

( double t, //независимая переменная double [ ] y, //массив текущих значений неизвестных функций double [ ] LOCALVECTOR //массив правых частей уравнений

);

Уже знакомый нам модификатор доступа public указывает на то, что тип TEQUATIONS доступен всем приложениям, ссылающимся на готовящуюся библиотеку.

Служебное слово delegate означает, что тип TEQUATIONS принадлежит к специальным классам (делегатам), полями которых являются ссылки на методы заданной сигнатуры. Термин сигнатура означает совокупность параметров метода, их типы и тип возвращаемого методом результата. При описании класса делегата сигнатура указывается в самом описании. Так, в данном случае, полями делегата типа могут быть ссылки на любой метод, который ничего не возвращает. На это указывает термин void. Метод, на который ссылается делегат, должен иметь три параметра. Первый параметр t должен быть типа double, а два других y и LOCALVECTOR должны быть одномерными массивами объектов типа double произвольной длины. На это указывают скобки [ ]. Объекты типа double хранят положительные и отрицательные числа с плавающей запятой, модуль которых лежит в интервале [5.0Ч10?324; 1.7Ч10308].

Из комментария ясен смысл этих параметров. Отметим, что параметр типа double передается методу по значению. Другими словами передаются поле структуры, описанной в библиотеке .NET под именем Double, содержащее значение параметра t и длиной 8 байт. В то же время параметры типа массива double [ ] передаются в метод по ссылке, то есть передается адрес области памяти, в которой расположены элементы массива. Длина адреса в системе с 32-битовыми адресами 4 байта.

Итак, свойства N и Equations описывают систему дифференциальных уравнений. Предполагается, что эти свойства будут возвращать определенные значения после создания объекта реализующего (наследующего) интерфейс IINTEGRATOR. При этом свойство N будет возвращать именно значение, содержимое 2-байтового поля структуры UINT16 библиотеки .NET (ushort - псевдоним этого типа), а свойство Equations - 4-ех байтовую ссылку на экземпляр класса делегата типа TEQUATIONS.

Интерфейс IINTEGRATOR может предложить для реализации свойства, которые используются для задания начальных условий интегрирования и чтения неизвестных функций при текущем значении аргумента после, или в процессе интегрирования уравнений. Все эти свойства можно объединить в одно свойство, снабженное индексом. Такое свойство называется индексатором. По правилам C# описание индексатора как члена интерфейса имеет вид double this [ushort index] { get; set;}

Служебное слово this необходимо при описании индексатора. Оно означает, что имя свойства будет совпадать с именем объекта, свойством которого является. Конкретное значение параметра передается в качестве дополнительного параметра методу при установке значения интегратора, и методу при получении этого значения. Пусть, например, описан объект типа IINTEGRATOR (или класса-наследника от IINTEGRATOR) с именем integrator. Тогда оператор присваивания integrator[2] = -0.5;

вызовет метод set индексатора this, передав ему в качестве параметра index значение 2, а значение -0.5 в качестве параметра value. Так можно установить начальное значение независимой переменной, сопоставив ей индекс 0, и начальные значения неизвестных функций, сопоставив им индексы от 1 до N.

Обратно, если необходимо копировать значение, скажем, третьей независимой функции в некую переменную y3 типа double, можно применить оператор присваивания вида y3 = integrator[3];

В общем случае тип параметра index индексатора (в нашем случае ushort), как и тип возвращаемого объекта (в нашем примере double) могут быть любыми. Параметр index является не индексом элемента массива, а параметром методов set и get индексатора.

Интегрирование уравнений всегда производится с ограниченной погрешностью. Поэтому интерфейс IINTEGRATOR предлагает для реализации член типа double Tolerance { get; set;}

Класс-наследник должен реализовать оба метода доступа set и get свойства Tolerance (погрешность).

Следующее поле

CALLBACKEVENTHANDLER CALLBACK { get; set;} позволит пользователю вмешиваться в процесс интегрирования в точках, которые будут выделены классом, наследующим интерфейс IINTEGRATOR.

Дело в том, что метод, реализующий алгоритм интегрирования дифференциальных уравнений, может требовать выполнения довольно большого цикла вычислений. Представьте себе, что хотя бы одна из неизвестных функций yi(t) имеет период существенного изменения значительно меньший интервала между начальной и конечной точкой интегрирования. Тогда, для поддержки необходимой погрешности, цикл вычислений будет состоять из большого количества шагов. Возможно, пользователю будет необходимо выводить текущую информацию где-либо внутри цикла интегрирования, или даже на каждом его шаге. В этом случае класс, реализующий метод интегрирования, может предусмотреть вызов пользователем так называемой функции обратного вызова (callback function). Поставив вызов этой функции в некоторые точки цикла вычислений (например, в конце каждого шага цикла), можно обеспечить пользователя доступом к этому событию (например, окончания шага цикла). Свойство CALLBACK устанавливает и возвращает ссылку на функцию обратного вызова. Эта функция является объектом класса-делегата, названного здесь CALLBACKEVENTHANDLER - обработчик события обратного вызова.

Описание класса CALLBACKEVENTHANDLER следует поместить вне скобок с членами интерфейса IINTEGRATOR. public delegate void

CALLBACKEVENTHANDLER (Object Sender, CALLBACKEVENTARGS e);

Как и в случае класса-делегата TEQUATIONS в этом описании указана сигнатура методов, которые могут быть делегированы типом CALLBACKEVENTHANDLER. Согласно описанию у метода (или методов), который может делегировать пользователь в качестве обработчика рассматриваемого события, должно быть два параметра типа Object и CALLBACKEVENTARGS и этот метод не должен возвращать какой-либо объект (void). Такой набор параметров является стандартным в модели обработки событий платформы .NET, хотя и не обязательным.

Тип Object является библиотечным классом - предком всех других классов библиотеки .NET. Поэтому на место параметра Sender (посыльный) может подставляться объект любого типа. Посыльным является объект, порождающий событие, с которым связан вызов метода-делегата, или обработчика события. Таким объектом может быть integrator, описанный выше. Этот объект вызывает обработчик внутри своего цикла и передает ему в качестве параметра Sender ссылку на себя (this). Ниже мы увидим этот фрагмент кода.

Второй параметр обработчика, названный e (от слова event - событие), имеет тип CALLBACKEVENTARGS (аргументы события обратного вызова).

Описание класса CALLBACKEVENTARGS, которое мы разместили в той же библиотеке, что и весь предыдущий код, имеет вид public class CALLBACKEVENTARGS : EVENTARGS

{ public bool Stop;

}

Это пример описания класса, который имеет предка. Имя класса-предка указывается после имени класса-наследника через двоеточие.

Здесь предком является класс EVENTARGS, взятый нами из библиотеки .NET. Объекты класса EVENTARGS и его наследников являются стандартными параметрами обработчиков событий библиотеки .NET. Эти классы содержат данные о событии. Класс-предок пуст в том смысле, что его объекты не имеют полей - абстрактное событие не имеет данных. Но классы-наследники могут содержать поля, переносящие данные о событии.

В нашем случае наследник CALLBACKEVENTARGS имеет одно поле Stop типа bool. Поле Stop может принимать два значения true и false. По умолчанию после создания объекта класса CALLBACKEVENTARGS значение поля Stop равно false. Но пользователь, написав собственный код обработчика, может изменить это значение на true. В реализации интерфейса IINTEGRATOR значение true может служить сигналом к прерыванию цикла интегрирования. В этом случае пользователь сможет управлять результатом события, меняя поле stop параметра e метода, делегированного в качестве обработчика событий.

На этом моменте мы еще раз остановимся при обсуждении кода реализации интерфейса. Здесь отметим, что в сигнатуре метода, который может быть делегирован в качестве обработчика событий делегату CALLBACKEVENTHANDLER, типом второго параметра может быть не только класс CALLBACKEVENTARGS, но и его предок - класс EVENTARGS. Это допускается синтаксисом и называется свойством контравариантности делегата. Правда в этом случае пользователь лишит себя возможности использовать поле stop, которое отсутствует у класса EVENTARGS.

Возвратимся к описанию членов интерфейса IINTEGRATOR. Последним его членом должен быть метод, который берет на себя работу по численному интегрированию системы дифференциальных уравнений с заданными начальными условиями. Заголовок этого метода мы написали в виде bool INTEGRATETO (double TEND);

Метод INTEGRATETO проводит интегрирование до точки TEND и возвращает true или false в зависимости от того, остановлено ли его выполнение обработчиком (false), или метод завершен естественным путем (true) и найдены значения неизвестных функций в точке TEND.

Посмотрите прокомментированный код описанных классов на файле IINTEGRATOR.txt.

В описании кода присутствует строка using System;

Это ссылка на то, что в файле будут использованы классы из пространства имен System. Это пространство имен одной из стандартных библиотек .NET. В частности, в пространстве имен System описан использованный нами класс EVENTARGS.

Пространства имен используются при написании кода для того, чтобы логически разделить участки кода. Например, отделить имена классов одной библиотеки от имен классов другой. В частности, весь написанный нами код помещен в пространство имен namespace Integrators. Это имя, которое мы дали создаваемой нами библиотеки интеграторов. Для внешнего пользователя имя интерфейса IINTEGRATOR выглядит как Integrators.IINTEGRATOR. Оператор точка соединяет в полное имя идентификатора класса IINTEGRATOR и имя пространства имен Integrators. Если пользователь в каком-либо файле кода будет ссылаться на классы нашей библиотеки, то в начале файла он сможет набрать строку using Integrators;

А затем использовать краткие имена наших классов IINTEGRATOR, CALLBACKEVENTHANDLER и т. д.

Наконец, обратим внимание на особенности комментирования кода, приведенного в файле. Кроме обычного комментария, выделенного двойным знаком slash //, в коде, как мы видим, присутствует комментарий, выделенный тройным знаком slash и имеющий дополнительные идентификаторы, окруженные угловыми скобками.

Например, ///

/// Класс аргументов обработчика события, наступающего внутри

/// цикла интегрирования (например, после завершения оптимального шага)

///

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

Идентификаторы, окруженные угловыми скобками, называются «тэгами». Эти тэги являются составной частью универсального языка общения, названного XML (EXTENSIBLE Markup Language - «расширяемый язык разметок»). Язык XML широко используются средой для обмена данными между ее отдельными компонентами. В отличие от известного языка HTML, построенного на той же идее разметок-тэгов, язык XML использует любые тэги, заданные его схемой. Кроме того, тэги языка XML используется не для указания формата изображаемых объектов, а для определения содержательной части - данных. Так в нашем примере «открывающий» и «закрывающий» тэги говорят о том, что между ними находится краткая справка о классе, описанном ниже. Везде в коде при ссылке на класс, обладающий таким комментарием, среда будет выводить справку на экран.

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

///

/// Хранит флаг прерывания интегрирования

///

///

/// Служит для прерывания интегрирования, если установлено в true.

///

Еще один пример

///

/// Интегрирование от текущей точки до TEND

///

///

/// Значение независимой переменной, /// в котором интегрирование должно быть прервано

///

///

/// Обычное завершение - возвращает true, /// завершение, прерванное обработчиком внутреннего события - false

/// показывает, как описание метода может сопровождаться справкой о его параметрах (тэг ).

Реализация интегратора абстрактным классом

Рассмотрим первый этап реализации интерфейса IINTEGRATOR, спроектировав пока что абстрактный класс TINTEGRATOR, наследующий этот интерфейс.

Описание класса синтаксически похоже на описание интерфейса. Класс, как и интерфейс, имеет заголовок с именем и модификатором доступа, и все члены класса, как и члены интерфейса, заключены в фигурные скобки public abstract class TINTEGRATOR : IINTEGRATOR

{

}

В заголовке класса обязательно указывается служебное слово class. В данном случае указан дополнительный модификатор abstract, который означает, что класс TINTEGRATOR будет содержать не реализованные методы. Вспомним, что у интерфейса все методы не реализованы. Абстрактный класс, как и интерфейс, не может создавать объекты. Но, в отличие от интерфейса, абстрактный класс может иметь поля и реализовывать любое количество методов и свойств. В заголовке класса TINTEGRATOR указано так же, что он наследует от интерфейса IINTEGRATOR. Это означает, что класс TINTEGRATOR берет на себя обязательство реализовать все методы и свойства, описанные в интерфейсе IINTEGRATOR. Другими словами, если наш класс TINTEGRATOR не реализует (хотя бы в абстрактной форме) какой-либо из членов интерфейса IINTEGRATOR, транслятор не пропустит такой код.

Рассмотрим подробнее члены класса TINTEGRATOR.

В начале опишем ряд постоянных полей, которые потребуются для реализации некоторых методов класса TINTEGRATOR и его наследников. public const double MINTOLERANCE = 1e-15;

public const double MAXTOLERANCE = 0.01;

public const double DEFTOLERANCE = 1e-6;

protected internal const double epsilon = 1e-16;

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

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

Эти три постоянных поля имеют максимально высокий уровень доступа public. Они видны не только методам класса TINTEGRATOR и его наследников, но и всем приложениям, использующим библиотеку, в которой описан класс TINTEGRATOR. В любой части кода можно написать, в частности, выражение double d = TINTEGRATOR.MAXTOLERANCE;

Выполнение этого кода поместит значение постоянного поля MAXTOLERANCE в объект d.

Однако запись вида

TINTEGRATOR.MAXTOLERANCE = 0.1;

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

Постоянное поле epsilon имеет смысл минимальной по сравнению с единицей величины, которая используется в некоторых методах класса TINTEGRATOR и его наследников. Поле epsilon имеет уровень доступа protected internal. Уровень доступа protected internal позволяет использовать поле epsilon только в самом классе TINTEGRATOR и его наследниках, описанных в той же библиотеке, что класс TINTEGRATOR.

Далее будем описывать переменные поля и свойства класса TINTEGRATOR.

Следующим кодом класс TINTEGRATOR реализует два свойства N и Equations интерфейса IINTEGRATOR. protected internal ushort n;

public ushort N { get { return n; } } protected internal TEQUATIONS equations;

public TEQUATIONS Equations { get { return equations; } }

Как мы видим, поля n и equations, хранящие текущие значения числа уравнений и ссылку на метод, возвращающий уравнений, имеют модификатор доступа protected internal, более низкий, чем свойства. Причем свойства N и Equations позволяют лишь читать значения этих полей, но не менять их. Такая практика программирования препятствует без нужды рисковать значениями важных полей в период эксплуатации объектов.

Метод доступа get в том и другом свойстве содержит характерный оператор return. Оператор return возвращает значение метода-функции, в данном случае метода get, и прерывает выполнение метода.

Далее можно описать поля protected internal double [ ] CURRY;

protected internal double currt;

Они предназначены для хранения текущего (current) вектора неизвестных функций CURRY и текущего значения независимой переменной currt. Поле CURRY описано как ссылка на массив объектов типа double. На это указывают квадратные скобки. Длина массива не указана. Она устанавливается после задания числа уравнений и будет равна числу уравнений n. Нумерация элементов массива начинается индексом 0, поэтому фактически поле CURRY будет содержаться ссылка на массив с элементами CURRY[0],…, CURRY[n - 1].

Опишем еще два поля и соответствующие им свойства protected internal double STEPSIZE;

public double STEPSIZE { get { return STEPSIZE; } } protected internal ulong EQUATIONSCALLNMB;

public ulong EQUATIONSCALLNMB { get { return EQUATIONSCALLNMB; } }

Поле STEPSIZE хранит текущий шаг интегрирования. Оно доступно только наследникам класса TINTEGRATOR, описанным в той же библиотеке классов (модификатор доступа protected internal говорит об этом). Его свойство STEPSIZE доступно всем (имеет модификатор доступа public). Но свойство STEPSIZE лишь возвращает значение поля STEPSIZE, и не может его изменить.

Поле EQUATIONSCALLNMB хранит показания счетчика вызова уравнений, который может потребоваться пользователю для оценки эффективности того или иного метода интегрирования. Тип ulong определяет объекты длинных целых чисел без знака, занимающих 8 байт памяти. Пределы значений таких чисел от 0 до 18,446,744,073,709,551,615. Доступное всем свойство EQUATIONSCALLNMB возвращает пользователю значение поля EQUATIONSCALLNMB.

Теперь опишем метод, возвращающий интегратор в состояние начала интегрирования protected internal virtual void Reset ()

{

STEPSIZE = Double.NAN; EQUATIONSCALLNMB = 0;

}

Модификатор virtual указывает на то, что метод Reset является виртуальным. Виртуальные методы могут быть перекрыты (изменены) в классах-наследниках. Служебное слово void (пустой тип) перед именем метода Reset означает, что метод после своего выполнения не возвращает объект какого-либо типа. Такой тип метода в C# аналогичен процедуре в Паскале. Обратите внимание и на то, что хотя метод Reset не имеет параметров, синтаксис языка C# требует написания круглых скобок после имени метода.

В теле метода находится два простых оператора присваивания. Первый оператор присваивает полю STEPSIZE значение Double.NAN. NAN это постоянное поле класса Double (служебное слово double является псевдонимом класса Double), которое указывает на особое значение Not a Number объекта типа double. Фактически это неопределенное значение вещественного числа типа double, или представление числа «не имеющего значения». До начала интегрирования размер шага STEPSIZE не определен, так как шаг зависит от начальных условий и самих дифференциальных уравнений.

Таким образом, метод Reset делает размер шага STEPSIZE не определенным и обнуляет значение счетчика обращений к уравнениям EQUATIONSCALLNMB.

Теперь реализуем индексатор this, описанный в интерфейсе IINTEGRATOR. public double this [ushort index]

{ get

{ return index == 0 ? currt : CURRY [index - 1];

} set

{ if ( index == 0 ) currt = value;

else CURRY [index - 1] = value;

// Любое внешнее изменение полей currt или CURRY

// предполагает, что задаются начальные условия.

// Это должно приводить к вызову метода Reset, // "стирающего" оптимальный шаг интегрирования if ( !Double.ISNAN (STEPSIZE) ) Reset ();

}

}

Выражение index == 0 ? currt : CURRY [index - 1] из метода get называется условным. В нем находится оператор сравнения ==, означающий «равен ли объект index нулю»? Оператор сравнения возвращает логическое значение true или false. Если это true, то условное выражение возвращает то, что следует за знаком вопроса ? (в данном случае currt). В противном случае условное выражение возвращает то, что следует за знаком двоеточия : (в данном случае элемент массива CURRY [index - 1]). Таким алгоритмом индексатор класса возвращает при нулевом индексе текущее значение независимой переменной currt, а при других значениях текущее значение соответствующей неизвестной функции.

На практике такое описание индексатора приведет к следующему эффекту. Пусть описан объект integrator класса TINTEGRATOR (вернее, наследника класса TINTEGRATOR, так как сам класс TINTEGRATOR абстрактный и не может создавать объектов). Тогда использование индексатора может иметь вид оператора double d = integrator[5];

Это означает, что объекту d будет передано значение, возвращаемое методом get индексатора, отвечающее значению 5 параметра index. Согласно приведенному выше описанию индексатора, это должен быть 4-ый элемент массива неизвестных функций CURRY.

Но что произойдет, если у интегратора задано всего 4 уравнения и массив CURRY имеет границы от 0 до 3? Произойдет останов по ошибке. Возникнет, как говорят, исключительная ситуация, связанная с тем, что совершена попытка выбрать элемент массива вне зоны его границ.

Как вариант, можно изменить содержание метода get. Усилим условие, накладываемое на значение параметра index, подставив во вторую часть условного выражения еще одно условное выражение вида index<=n ? CURRY [index - 1]: Double.NAN

Полный оператор метода get теперь будет иметь вид return index == 0 ? currt : index<=n ? CURRY [index - 1]: Double.NAN;

Теперь значение, которое будет возвращать индексатор при любом индексе, превышающем число уравнений, будет неопределенным (Double.NAN). Исключительная ситуация не возникнет.

Перейдем к методу set индексатора this.

Метод set содержит условные операторы типа if…else и типа if. В операторе if условие формулируется в круглых скобках. В данном случае в первом операторе if это условие равенства нулю индекса index == 0. Во втором операторе if условие !Double.ISNAN (STEPSIZE) выполняется, если поле STEPSIZE является определенным, то есть не равно NAN. Метод ISNAN класса Double возвращает логическое значение true или false в зависимости от того, имеет ли параметр этого метода значение NAN или нет. Унарный оператор отрицания ! превращает true в false и наоборот.

При выполнении условия index == 0 метод set передает полю текущего значения независимой переменной currt значение своего параметра value. В противном случае, когда индекс не равен нулю, выполняется оператор, следующий за словом else. Здесь значение параметра value метода set передается соответствующему элементу массива текущих значений неизвестных функций CURRY [index - 1].

В пользовательском классе метод set индексатора будет вызываться в операторе вида (это пример!) integrator[5] = 0.1;

Вызов этого оператора фактически сводится к вызову метода set индексатора с параметром value, равным 0.1, и параметром index, равным 5.

Если индексы массива неизвестных функций CURRY ограничены, как и в предыдущем примере, значениями 0…3 (система содержит четыре уравнения), то вызов метода set приводит к возникновению исключительной ситуации.

Для того, чтобы избежать исключительной ситуации, можно усилить условие, дополнительно потребовав, чтобы значение индекса не превышало число уравнений n. Это усложнит условный оператор if…else, придав ему вид if ( index == 0 ) currt = value;

else if ( index <= n ) CURRY [index - 1] = value;

Здесь, как видно, при условии index > n метод set вообще не выполняет никаких действий. Поэтому в новом варианте кода оператор integrator[5] = 0.1 не выполнит никаких действий, если число уравнений меньше пяти.

Тем и хороши свойства, что они могут оберегать поля от неразумного использования.

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

В последнем операторе if метода set вызывается метод Reset, если поле STEPSIZE имеет осмысленное значение (не NAN). Отличие STEPSIZE от NAN фактически означает, что метод Reset еще не вызывался. Тем самым метод Reset вызывается лишь один раз при попытке изменить значение независимой переменной, либо одной из неизвестных функций. При любом таком изменении интегратор предполагает, что пользователь этим изменением задает новые начальные условия интегрирования.

Продолжим рассмотрение других членов класса TINTEGRATOR. protected internal double tolerance;

public virtual double Tolerance

{ get { return tolerance; } set

{ tolerance = value > MAXTOLERANCE ? MAXTOLERANCE : value < MINTOLERANCE ? MINTOLERANCE : value;

}

}

Так описывается поле tolerance, хранящее текущую погрешность интегрирования, и соответствующее ему свойство Tolerance.

Свойство Tolerance скрытого поля tolerance доступно всем. Оно реализует абстрактное описание свойства Tolerance интерфейса IINTEGRATOR. Модификатор virtual в описании свойства означает, что оно является виртуальным и его методы set и get могут быть перекрыты, изменены в классах - наследниках класса TINTEGRATOR.

Обратим внимание на содержание метода set свойства Tolerance. Если пользователь задает погрешность, выходящую за границы, определенные постоянными полями MAXTOLERANCE и MINTOLERANCE, то поле tolerance принимает значение той границы, за которую выходит значение параметра value.

Возможен другой вариант кода, предусматривающий в этом случае возбуждение исключительной ситуации. Тогда пользователь предупреждается о нарушении границ погрешности. В этом варианте тело метода set может выглядеть следующим образом if ( value > MAXTOLERANCE || value < MINTOLERANCE )

{ throw (new APPLICATIONEXCEPTION (

"Погрешность выходит за допустимые границы."));

} tolerance = value;

Теперь выполняется полная проверка значения параметра value, с которым пользователь передает предполагаемую погрешность. Если это значение больше максимально допустимого MAXTOLERANCE или (оператор || означает «или» в C#) меньше минимально допустимого MINTOLERANCE, то стандартный оператор throw создает («вбрасывает») исключительную ситуацию. Параметром оператора throw является объект стандартного класса APPLICATIONEXCEPTION. Этот объект создается вызовом оператора new и конструктора класса APPLICATIONEXCEPTION с параметром-строкой "Погрешность выходит за допустимые границы.". Эта строка будет выведена как комментарий, или сообщение об ошибке, отвечающей возникшей исключительной ситуации. После оператора throw выполнение метода set прерывается, и программа ищет обработчик исключительной ситуации, готовый принять созданный объект с его сообщением. Обработчик устанавливается пользователем в расчете на возможную исключительную ситуацию. С технологией обработки исключительных ситуаций мы познакомимся несколько позже.

Опишем еще одно поле и еще один метод класса TINTEGRATOR protected internal double [ ] STARTLOCALVECTOR;

protected internal virtual void Initialize ()

{

// Создается экземпляр объекта CURRY и // резервируется память под массив текущих значений неизвестных функций

CURRY = new double [n];

// Элементы массива CURRY делаются неопределенными (NAN, Not a Number) for ( ushort i = 0; i < n; i ) CURRY [i] = Double.NAN;

// Независимая переменная делается неопределенной NAN currt = Double.NAN;

// Создается экземпляр объекта STARTLOCALVECTOR и // резервируется память под массив правых частей уравнений

STARTLOCALVECTOR = new double [n];

// Погрешность принимает значение «по умолчанию»

Tolerance = DEFTOLERANCE;

}

Поле-массив STARTLOCALVECTOR используется в некоторых методах класса TINTEGRATOR и его наследников. Этот массив хранит начальное значение вектора правой части дифференциальных уравнений, необходимый для оценки начального шага интегрирования.

В виртуальном методе Initialize с ограниченным доступом объявленные выше поля CURRY, currt, STARTLOCALVECTOR, и tolerance принимают начальные значения. Предполагается, что к моменту вызова метода Initialize число уравнений n установлено. Поля-ссылки на массивы CURRY и STARTLOCALVECTOR инициализируются с помощью оператора new и конструктора double [n]. Оператор выделяет память под хранение необходимого числа элементов массива и передает ссылку на эту память указанному полю. Конструктор сообщает всю необходимую информацию о создаваемом объекте оператору new.

Обратите внимание на оператор цикла for (;;). Вслед за ним находится один или несколько операторов, объединенных фигурными скобками. В нашем коде это один оператор присваивания CURRY [i] = Double.NAN. Этот оператор называют «оператором, которым управляет цикл», или, короче, «телом цикла».

Синтаксис языка C# требует, чтобы оператор цикла типа for (;;) имел в круглых скобках три выражения, разделенных точкой с запятой. В первом выражении параметр цикла принимает начальное значение и может быть описан (как в нашем примере). Второе выражение является условием выполнения тела цикла (в нашем случае условие выполнения тела i < n). Наконец, в третьем выражении параметр цикла изменяется. В нашем операторе изменение параметра определяется выражением i . В языке C# операция i означает «добавление к i единицы и сохранение нового значения в объекте i».

Оператор for (;;) работает следующим образом: сразу после установки начального значения проверяется условие выполнения тела. Если оно соблюдается, то тело выполняется, если нет, то тело цикла не выполняется, а совершается «выход из цикла», то есть переход к оператору, следующему за оператором цикла вместе с его телом.

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

Затем вновь проверяется условие выполнения тела цикла, и т.д.

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

Продолжим описание членов класса TINTEGRATOR protected internal sbyte direction;

public sbyte Direction { get { return direction; } } protected internal CALLBACKEVENTHANDLER CALLBACK;

public virtual CALLBACKEVENTHANDLER CALLBACK

{ get { return CALLBACK; } set { CALLBACK = value; } }

Поле direction хранит направление изменения независимой переменной (плюс один при росте и минус один при уменьшении). Соответствующее свойство Direction доступно всем. Оно возвращает значение поля direction. Тип sbyte означает, что объект этого типа хранит целое число со знаком объемом в один байт. Пределы значений объектов типа sbyte [-128; 127].

Поле CALLBACK хранит ссылку на делегата типа CALLBACKEVENTHANDLER, передающего от пользователя обработчики, или функции обратного вызова. Об этом шла речь в описании интерфейса IINTEGRATOR. Свойство CALLBACK реализует свойство интерфейса IINTEGRATOR с тем же именем и является, как мы видим, виртуальным. Наследник может изменить содержание его методов set и get.

Далее разместим и прокомментируем описание конструкторов объектов класса TINTEGRATOR protected internal TINTEGRATOR ( ushort N, TEQUATIONS Equations, CALLBACKEVENTHANDLER CALLBACK

)

{ if ( N == 0 ) throw (new APPLICATIONEXCEPTION ("\NЧИСЛО уравнений равно нулю!?"));

n = N;

if ( Equations == null ) throw (new APPLICATIONEXCEPTION ("\NУРАВНЕНИЯ не заданы!!!"));

equations = Equations;

Initialize ();

this.CALLBACK = CALLBACK;

} protected internal TINTEGRATOR (ushort N, //Число уравнений

TEQUATIONS Equations//уравнения

): this (N, Equations, null) { }

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

Вы можете ЗАГРУЗИТЬ и ПОВЫСИТЬ уникальность
своей работы


Новые загруженные работы

Дисциплины научных работ





Хотите, перезвоним вам?