Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Итераторы и вложенные классы



 

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

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

Вложенные классы имеют доступ ко всем членам, видимым содержащему их классу, даже если эти члены являются приватными. Рассмотрим следующий код, который представляет контейнерный класс, включающий экземпляры GeometricShape:

using System.Collections;

public abstract class GeometricShape

{

public abstract void Draw();

}

public class Rectangle: GeometricShape

{

public override void Draw()

{

System.Console.WriteLine ( " Rectangle.Draw" );

}

}

public class Circle: GeometricShape

{

public override void Draw()

{

System.Console.WriteLine( " Circle.Draw" );

}

}

public class Drawing: IEnumerable

{

private ArrayList shapes;

private class Iterator: IEnumerator

{

public Iterator( Drawing drawing )

{

this.drawing = drawing;

this.current = -1;

}

public void Reset()

{

current = -1;

}

public bool MoveNext ()

{

++current;

if ( current < drawing.shapes.Count ) {

return true;

} else {

return false;

}

}

public object Current

{

get

{

return drawing.shapes [ current ];

}

}

private Drawing drawing;

private int current;

}

public Drawing ()

shapes = new ArrayList ();

public IEnumerator GetEnumerator ()

return new Iterator ( this );

public void Add( GeometricShape shape )

shapes.Add( shape );

}

}

public class EntryPoint

{

static void Main()

{

Rectangle rectangle = new Rectangle ();

Circle circle = new Circle ();

Drawing drawing = new Drawing();

drawing.Add( rectangle );

drawing.Add( circle );

foreach( GeometricShape shape in drawing ) {

shape.Draw();

}

}

}

В этом примере демонстрируется ряд новых концепций, в том числе интерфейсы IEnumerable и IEnumerator.

Давайте сначала более внимательно посмотрим, как работает цикл foreach. Ниже описано, что на самом деле происходит в цикле foreach, осуществляющем проход по коллекции collectionObject.

1. Вызывается метод collectionObject.GetEnumerator (), который возвращает ссылку на IEnumerator. Этот метод доступен через реализацию интерфейса IEnumerable, хотя она является необязательной.

2. На возвращенном интерфейсе вызывается метод MoveNext ().

3. Если метод MoveNext () возвращает true, с помощью свойства Current интерфейса IEnumerator получается ссылка на объект, которая используется в цикле foreach.

4. Два последних шага повторяются до тех пор, пока MoveNext () не вернет false, после чего цикл завершается.

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

Более простой альтернативой является использование итератора. Применение итераторов, по сути, приводит к автоматической генерации большого объема кода " за кулисами" с надлежащей привязкой к нему. Синтаксис использования операторов также гораздо более прост в освоении.

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

Давайте в первую очередь сосредоточим внимание на использовании вложенного класса. В коде видно, что класс Drawing поддерживает метод GetEnumerator, являющийся частью реализации IEnumerable. Он создает и возвращает экземпляр вложенного класса Iterator.

Но вот что интересно. Класс Iterator принимает ссылку на экземпляр содержащего его класса Drawing в виде параметра конструктора. Затем он сохраняет этот экземпляр для последующего использования, чтобы можно было добраться до коллекции shapes внутри объекта drawing. Обратите внимание, что коллекция shapes в классе Drawing объявлена как private. Это не имеет значения, потому что вложенные классы имеют доступ к приватным членам охватывающего их класса.

Также обратите внимание, что класс Iterator сам по себе объявлен как private. Не вложенные классы могут объявляться только как public или internal и по умолчанию являются internal. К вложенным классам можно применять те же модификаторы доступа, что и к любым другим членам класса. В данном случае класс Iterator объявлен как private, так что внешний код вроде процедуры Main не может создавать экземпляры Iterator непосредственно. Это может делать только сам класс Drawing. Возможность создания экземпляров Iterator не имеет смысла ни для чего другого, кроме Drawing.GetEnumerator.

 

Индексаторы

 

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

В общем случае индексаторы немного похожи на метод по имени this. Как и для почти любой сущности системы типов С#, к индексаторам можно применять атрибуты метаданных. К ним также можно применять те же самые модификаторы, что и для любых других членов класса, за исключением одного — static, поскольку индексаторы не бывают статическими. Таким образом, индексаторы всегда относятся к экземпляру и работают с заданным экземпляром объекта определяющего их класса. За модификаторами в объявлении следует тип индексатора. Индексатор возвратит этот тип объекта вызывающему коду. Затем указывается ключевое слово this, за которым следует список параметров в квадратных скобках, что будет продемонстрировано в следующем примере.

По сути, индексатор ведет себя как некий гибрид свойства и метода. В конце концов, " за кулисами" он представляет собой один из специальных методов, определяемых компилятором при объявлении индексатора. Концептуально индексатор подобен методу в том, что он может принимать набор параметров. Однако он также ведет себя и как свойство, поскольку для него объявляются средства доступа с использованием аналогичного синтаксиса. К индексаторам могут быть применены многие из тех же модификаторов, что применяются к методам. Например, индексаторы могут быть виртуальными, они могут переопределять базовый индексатор или же могут быть перегружены в зависимости от списка параметров — точно так же, как методы. За списком параметров следует блок кода индексатора, который по синтаксису похож на блок кода свойства. Главное отличие состоит в том, что средства доступа индексатора могут принимать списки переменных-параметров, в то время как средства доступа свойств не используют параметры, определяемые пользователем. Давайте добавим индексатор к объекту Drawing, чтобы посмотреть, как его использовать:

using System.Collections;

public abstract class GeometricShape

{

public abstract void Draw();

}

public class Rectangle: GeometricShape

{

public override void Draw()

{

System.Console.WriteLine ( " Rectangle.Draw" );

}

}

public class Circle: GeometricShape

{

public override void Draw()

{

System.Console.WriteLine ( " Circle.Draw" );

}

}

public class Drawing

{

private ArrayList shapes;

public Drawing()

{

shapes = new ArrayList();

}

Public int Count

{

Get

{

return shapes.Count;

}

}

public GeometricShape this[ int index ]

{

Get

{

return (GeometricShape) shapes[index];

}

}

public void Add ( GeometricShape shape )

{

shapes.Add( shape );

}

}

public class EntryPoint

{

static void Main()

{

Rectangle rectangle = new Rectangle ();

Circle circle = new Circle ();

Drawing drawing = new Drawing ();

drawing.Add( rectangle );

drawing.Add( circle );

for( int i = 0; i < drawing.Count; ++i ) {

GeometricShape shape = drawing[i];

shape.Draw();

}

}

}

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

Следует отметить одно различие между реальным массивом и объектом, предоставленным индексатором. Передавать результат вызова индексатора на объекте в качестве out- или ref-параметра методу, как это можно делать с реальным массивом, не разрешено. Аналогичное ограничение накладывается и на свойства.

 

 

Перегрузка операций

 

Перегрузка операций (operator overloading) позволяет использовать стандартные операции, такие как +, > и т.д., в классах собственной разработки. " Перегрузкой" этот прием называется потому, что предусматривает предоставление для этих операций собственных реализаций, когда операции используются с параметрами специфических типов. Это во многом похоже на перегрузку методов, при которой методам с одинаковым именем передаются разные параметры.

Для перегрузки операции + можно использовать такой код:

 

public class AddClassl

{

public int val;

public static AddClassl operator + (AddClassl opl, AddClassl op2)

{

AddClassl return Val = new AddClassl ();

return Val. val = opl.val + op2.val;

return returnVal;

}

}

Как здесь видно, перегрузки операций выглядят во многом подобно стандартным объявлениям статических методов, но только в них используется ключевое слово operator и сама операция, а не имя метода. Теперь операцию + можно успешно использовать с данным классом:

AddClassl орЗ = opl + ор2;

Ниже перечислены операции, которые могут быть перегружены:

- унарные операции: +, -, !, ~, ++, —, true, false;

- бинарные операции: +, -, *, /, %, &, |, А, < <, > >;

- операции сравнения: ==, ! =, <, >, < =, > =.

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

Перегрузка операции + означает, что и операция += будет функционировать должным образом. Перегружать операцию = не допускается из-за ее фундаментального предназначения, но с ней связаны определяемые пользователем операции преобразования

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

Некоторые операции, например, < и >, должны перегружаться парами. То есть перегрузить операцию < и не перегружать при этом операцию > не допускается. Во многих случаях внутри реализации можно просто вызвать другую операцию, сократив объем необходимого кода (и, следовательно, ошибок, которые могут возникать):

public class AddClassl

{

public int val;

public static bool operator > =(AddClassl opl, AddClassl op2)

{

return (opl.val > = op2.val);

}

public static bool operator < (AddClassl opl, AddClassl op2)

{

return! (opl > = op2);

}

// Также необходимы реализации операций < = и >.

}

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

То же самое касается операций == и! =, но в их случае часто лучше переопределять методы Object.Equals () и Object.GetHashCode (), поскольку они также могут использоваться для сравнения объектов. Переопределение этих методов гарантирует, что какой бы прием не применялся пользователями класса, будет получен один и тот же результат.

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

 

public class AddClassl

{

public int val;

public static bool operator ==(AddClassl opl, AddClassl op2)

return (opl.val == op2.val);

public static bool operator! =(AddClassl opl, AddClassl op2)

return! *(opl == op2);

public override bool Equals(object opl)

return val = ((AddClassl)opl).val;

public override int GetHashCode()

return val;

}

Метод GetHashCode () служит для получения уникального значения int для экземпляра объекта на основе его состояния. Использование val здесь является допустимым, потому что val тоже является значением типа int.

Обратите внимание, что в методе Equals () применяется параметр типа object. Такая сигнатура является обязательной, иначе это будет перегрузка, а не переопределение данного метода, и тогда его реализация по умолчанию все равно будет доступна пользовате-лям класса. Поэтому в нем для получения необходимого результата должно использоваться приведение типов. Часто полезно еще и проверять тип объекта с помощью рассмотренной ранее операции is, как показано ниже:

 

public override bool Equals(object opl)

{

if (opl is AddClassl)

{

return val == ((AddClassl)opl).val;

}

else

{

throw new ArgumentException (

" Cannot compare AddClassl objects with objects of type "

+ opl.GetType().ToString());

}

}

В этом коде исключение генерируется в случае, если передаваемый методу Equals операнд относится не к тому типу, а его преобразование к правильному типу невозможно. Конечно, такое поведение может оказаться не тем, что нужно. Может потребоваться возможность сравнения объектов одного типа с объектами другого типа; в этом случае необходимо дополнительное ветвление. Или же может понадобиться ограничить сравнение объектами в точности одинакового типа, что требует внесения следующего изменения в первый оператор if:

if (opl.GetType() == typeof(AddClassl))

 

ОБОРУДОВАНИЕ

Персональный компьютер: процессо­р с частотой 1, 6ГГц или выше, 1024 МБ ОЗУ, жесткий диск со скоростью 5400 об/мин, видеоадаптер с поддержкой DirectX 9 и с разрешением 1280 х 1024 (или более высоким), операционная система Windows 7/8, интегрированная среда разработки приложений Visual Studio 2010/2012 с комплектами документации MSDN, каталог Tprog\Lab6, содержащий исходные файлы проектов, файл Labtprog6.doc (методи­ческие указания к данной лаборатор­ной работе), не менее 200 Mб свободной памяти на логическом диске, со­держащем каталог Tprog\Lab6.

 

ЗАДАНИЕ НА РАБОТУ

 

4.1. Ознакомиться с технологией объектно-ориентированного программирования на базе языка C# и инструментальной среды разработки MS Visual Studio.NET 2010/2012.

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

4.3. Разработать и отладить объектно-ориентированную программу на языке С# в интегрированной среде разработки приложений Visual Studio 2010/2012 в соответствии с заданием преподавателя. Примерами заданий могут быть следующие.

1. Написать тексты исходных файлов для базового класса Point и производного класса Rect (прямоугольник). Описание классов:

Класс Элементы данных Интерфейс
Point x, y Конструкторы, функции move, assign, print
Rect dx, dy Конструкторы, функция square, операции assign, +, < <

Разработать и отладить программу с примерами создания и использования объектов классов Point и Rect.

2. Написать тексты исходных файлов для базового класса Point и производного класса Rect (прямоугольник). Описание классов:

Класс Элементы данных Интерфейс
Point x, y Конструкторы, функции move, print, операции assign, +, ==
Rect p2(типа Point) Конструкторы, функции move, square, операции assign, <, < <

Разработать и отладить программу с примерами создания и использования объектов классов Point и Rect.

3. Написать тексты исходных файлов для базового класса Point и производного Circle (окружность). Описание классов:

Класс Элементы данных Интерфейс
Point x, y Конструкторы, операции +, =, < <
Circle r Конструкторы, функции move, square, операции assign, ==, print

Разработать и отладить программу с примерами создания и использования объектов классов Point и Circle.

 

ПОРЯДОК ВЫПОЛНЕНИЯ РАБОТЫ

 

5.1. Создать личный каталог, в котором будут размещаться создаваемые во время лабораторной работы проекты.

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

5.3. Разработать и отладить объектно-ориентированную программу на языке С# в интегрированной среде разработки приложений Visual Studio 2010/2012 в соответствии с заданием преподавателя.

 

ОФОРМЛЕНИЕ ОТЧЕТА

 

Отчет должен содержать:

· цель работы и индивидуальное задание;

· тексты исходных файлов, содержащие описание и реализацию классов, используемых в лабораторной работе;

· тексты исходных файлов, содержащие описание и реализацию классов в соответствии с заданием преподавателя;

· текст тестовой программы и результаты работы программы.

 

КОНТРОЛЬНЫЕ ВОПРОСЫ

 

7.1. Что называется сборкой в контексте среды CLR?

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

7.3. Как определяются свойства?

7.4. Перечислите ограничения при использовании типов значений.

7.5. Перечислите случаи, когда необходимо использовать слово this.

7.6. Перечислите случаи, когда необходимо использовать слово base.

7.7. Как реализуется цикл foreach?

 

БИБЛИОГРАФИЧЕСКИЙ СПИСОК

 

1. Уотсон К., Нейгел К., Педерсен Я., Рид Д., Скиннер М. Visual С# 2010: полный курс.: Пер. с англ. - М.: ООО " И.Д. Вильяме", 2011. - 960 с.

2. Нэш Т. C# 2010: ускоренный курс для профессионалов.: Пер. с англ. — М.: ООО " И.Д. Вильяме", 2010..— 592с.: ил.

2. Троелсен Э. Язык программирования С# 2008 и платформа.NET 3.5, 4-е изд.: Пер. с англ. — М.: ООО " И.Д. Вильяме", 2010. — 1344 с.: ил.

3. Дейтел, Х.М. C#: пер.с англ. / Х.М.Дейтел [и др.].— СПб.: БХВ-Петербург, 2006. - 1056с.

4. Жарков, В.А. Visual C# 2005 в учебе, науке и технике / В.А.Жарков.— М.: Жарков Пресс, 2007. - 818с.

5. MSDN 2010. Электронная документация Microsoft для разработчиков программного обеспечения. – 500000 с.

 

 


ЛАБОРАТОРНАЯ РАБОТА № 7

Основные расширения языка в C# 3.0

 

ЦЕЛЬ И ЗАДАЧИ РАБОТЫ

 

Ознакомление с основными расширениями языка в C# 3.0 и с технологией LINQ для создания интегрированных запросов на языке C#.

 

ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-05-03; Просмотров: 694; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.103 с.)
Главная | Случайная страница | Обратная связь