Выбери формат для чтения
Загружаем конспект в формате doc
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ
ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ
«ДОНСКОЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ»
(ДГТУ)
Кафедра «Вычислительные системы и информационная безопасность»
Методические рекомендации
по изучению дисциплины «Программирование на языке высокого уровня»
для студентов 3-го курса
направления подготовки 09.03.02 Информационные системы и технологии
заочной формы обучения
Ростов-на-Дону
2016 г.
Составители:
А.Р. Айдинян,
В.В. Галушка
УДК 681.3
Подготовлено на кафедре «Вычислительные системы и информационная безопасность»
Методические рекомендации
по изучению дисциплины «Программирование на языке высокого уровня»
/ ДГТУ, Ростов-на-Дону, 2016, 74 с.
Методические рекомендации по изучению дисциплины для студентов представляют собой комплекс рекомендаций и разъяснений, позволяющих студенту оптимальным образом организовать процесс изучения данной дисциплины. Методические рекомендации могут быть использованы для самостоятельной работы.
Опубликовывается по решению методического совета факультета «Энергетика и нефтегазопромышленность».
Цели и задачи дисциплины «Программирование на языке высокого уровня»
Целью изучения дисциплины «Программирование на языке высокого уровня» является подготовка бакалавров к деятельности, связанной с использованием языков программирования для решения профессиональных задач. При изучении данного курса у студентов формируются знания, и навыки, необходимые для разработки программного обеспечения и сопроводительной документации.
Задачи дисциплины:
1) изучение основных подходов к организации процесса разработки программного обеспечения;
2) выработка навыков использования языков программирования при решении практических задач профессиональной сферы;
3) выработка способности поддерживать работоспособность информационных систем и технологий в заданных функциональных характеристиках и соответствии критериям качества;
4) выработка способности составлять инструкции по эксплуатации программного обеспечения;
5) способностью адаптировать приложения к изменяющимся условиям функционирования.
Описание последовательности изучения УМК
Учебно-методический комплекс (УМК) призван помочь студентам в организации самостоятельной работы по освоению курса «Программирование на языке высокого уровня». Дисциплина «Программирование на языке высокого уровня» изучается в 6 семестре. Завершающим этапом изучения дисциплины является сдача экзамена.
Комплекс содержит рабочую программу дисциплины, составленную в строгом соответствии с учебным планом по специальности и государственным образовательным стандартом ВО. Учебно-методические материалы по подготовке лекционных и лабораторных занятий в УМК представлены отдельно по каждому разделу курса «Программирование на языке высокого уровня» в соответствии с программой дисциплины и последовательностью изучения.
В каждом разделе даны:
1) учебно-методические материалы лекционного курса, включающие подробный план лекций по каждой изучаемой теме, вопросы и задания для самоконтроля, список основной и дополнительной литературы, с указанием конкретных разделов;
2) учебно-методические материалы по подготовке лабораторных занятий, содержащие планы проведения занятий с указанием последовательности рассматриваемых тем, задания для самостоятельной работы, краткие теоретические и учебно-методические материалы по теме, систему заданий для самопроверки. Выполнение заданий даст возможность студентам глубже усвоить теоретический материал, применить полученные знания на практике, выработать прочные умения и навыки использования технологии программирования при решении практических задач профессиональной сферы, в том числе и в кооперации с коллегами.
Задания для самопроверки сопровождаются теоретическими справками и методическими рекомендациями.
Рекомендации изучения отдельных тем курса
Материал курса разбит на 4 раздела.
При изучении раздела «Основные понятия дисциплины» следует необходимо разобраться в понятии и классификации языков программирования, понятии среды программирования и критериях качества программного обеспечения.
В разделе «Структурное программирование на языке C#» изучаются переменные и типы данных, арифметические операторы, операторы ветвления, операторы цикла, массивы. При изучении второго раздела предусмотрены лабораторные работы, на которых изучается структурное программирование на языке C#.
В разделе «Объектно-ориентированное программирование на языке C#» изучается реализация объектно-ориентированного программирования. При изучения раздела предусмотрена лабораторная работа, на которой изучается объектно-ориентированное программирование на языке C#.
В разделе «Классы и технологии .NET» изучаются коллекции и технология LINQ. При изучении второго раздела предусмотрены 2 лабораторные работы.
Особенности самостоятельного изучения курса.
Советы для подготовки к экзамену
Прежде чем приступить к выполнению заданий для самоконтроля, студентам необходимо изучить материал лекций и сопоставить его с трактовками, предлагаемыми в рекомендуемой по каждой теме литературе.
На очном отделении в качестве основных элементов учебного процесса выступают проблемно-ориентированные лекции с объяснением и иллюстрированием ключевых понятий и категорий технологии программирования. Лабораторные занятия организованы в компьютерных классах по выполнению конкретных заданий.
Для студентов особое значение приобретает самостоятельная проработка материала курса по учебникам и пособиям.
Общий список учебной, учебно-методической и научной литературы представлен в рабочей программе.
Следует учитывать тот факт, что отводимые на изучение курса часы не позволяют в полной мере охватить все разделы, отведенные к данному курсу. Более подробно, некоторые разделы курса предлагается студентам изучить индивидуально. Индивидуальная работа студента предполагает самостоятельное составление конспекта и изучение рекомендуемой литературы. Работу следует начинать с прочтения материала с целью уяснения его содержания, основной идеи, выделения выводов и аргументов автора. Конспектировать рекомендуется лишь при повторном чтении.
Особое внимание следует обращать на определение основных понятий дисциплины. Студент должен подробно разбирать приведенные примеры на лекциях и лабораторных (практических) занятиях и уметь строить аналогичные примеры самостоятельно. Это является одним из важных условий усвоения дисциплины.
Все пропущенные лабораторные (практические) занятия подлежат отработке. Форма отработки — сдача задания по теме. В рамках самостоятельной работы организуется проведение консультаций, на которых будет осуществляться обсуждение прошедших заданий, прием отработки пропущенных лабораторных занятий.
Распределение времени для самостоятельной работы студентов
Для контроля самостоятельной работы студентов используется экзамен. Экзамен проводится в письменной форме, вопросы для подготовки к экзамену составлены с учетом всех тем курса в соответствии с программой.
№
Автор
Название
Издательство
Гриф издания
Год издания
Кол-во в библиотеке
Ссылка на электронный ресурс
Доступность
1
2
3
4
5
6
7
8
9
6.1 Основная литература
6.1.1
Гагарина Л.Г., Кокорева Е.В., Виснадул Б.Д.
Технология разработки программного обеспечения и информационных систем
М.: И НТУИТ
2016
http://www.biblioclub.ru
С любой точки доступа для зарегистрированных пользователей
6.1.2
Белоцерковская И.Е., Галина Н.В., Катаева Л.Ю.
Алгоритмизация. Введение в язык программирования С++
М.: И НТУИТ
2016
http://www.biblioclub.ru
С любой точки доступа для зарегистрированных пользователей
6.1.3
Хейлсберг А.
Язык программирования C#
СПб.: Питер
2012
12
6.1.4
Семакин И.Г.
Основы алгоритмизации и программирования
М. : ACADEMIA
2013
12
6.1.5
Зольников В.К., Машевич П.Р., Анциферова В.И., Литвинов Н.Н.
Введение в теорию программирования. Объектно-ориентированный подход
М.: И НТУИТ
2016
http://www.biblioclub.ru
С любой точки доступа для зарегистрированных пользователей
6.2 Дополнительная литература
6.2.1
Павловская Т.А.
С#. Программирование на языке высокого уровня
СПб.: Питер
2009
–
6.2.2
Орлов С.А.
Технологии разработки программного обеспечения
СПб.: Питер
Доп. МО РФ
2002
15
6.2.3
Иванова Г.С.
Технология программирования:
Учеб. для вузов
М.: МГТУ
Рек. УМО ВУЗов
2003
16
6.2.4
Кариев Ч.А.
Разработка Windows-приложений на основе Visual C#
М.: Интернет-Университет Информационных Технологий
2007
http://www.biblioclub.ru
С любой точки доступа по логину и паролю
6.2.5
Подбельский В.В.
Язык C#. Базовый курс
М.: Финансы и статистика
2010
http://www.biblioclub.ru
С любой точки доступа по логину и паролю
6.3 Периодическая литература
6.3.1
Вычислительные методы и программирование: новые вычислительные технологии
М.: Научно-исследовательский вычислительный центр МГУ им. М.В. Ломоносова
2014
2015
http://elibrary.ru
С любой точки доступа для зарегистрированных пользователей
6.4 Программно- информационное обеспечение дисциплины, Интернет-ресурсы
6.4.1. Технология программирования: Учебное пособие / Кафедра системного анализа и телекоммуникаций ТРТУ. - Таганрог: ТРТУ. - 78 с.
http://window.edu.ru/resource/943/28943/files/tsure169.pdf – свободный доступ.
6.4.2. Сайт Visual Studio — Режим доступа: https://www.visualstudio.com
Курс лекций по дисциплине
«Программирование на языке высокого уровня»
Раздел
(название)
Тема, литература
Содержание
1. Основные понятия дисциплины
1.1 Понятие и классификация языков программирования [6.2.2, 6.2.3]
язык программирования высокого и низкого уровня, процедурные и непроцедурные языки программирования, функциональное и логическое программирование, структурное программирование, объектно-ориентированное программирование. Обобщенное программирование.
1.2 Платформы java и .net [6.2.1]
понятие транслятора, интерпретатора и компилятора, интерфейс прикладного программирования, переносимость приложений, виртуальная машина и среда выполнения Java, байт-код
1.3 Понятие среды программирования [6.2.1]
понятие среды (системы) программирования, отладчик, компоновщик, библиотеки компонентов, примеры сред программирования
1.4 Критерии качества программного обеспечения
Критерии качества программного обеспечения.
2. Структурное программирования на языке C#
2.1 Переменные и типы данных [6.2.1]
объявление переменных, константы, типы данных, их назначение и ограничения, целочисленные типы данных, типы данных с плавающей точкой, логический тип данных, символьный и строковый типы
2.2 Арифметические операторы [6.2.1]
стандартные арифметические операторы, оператор деления по модулю, постфиксный и префиксный инкремент и декремент, операторы отношения, логические операторы, приоритет операций
2.3 Операторы ветвления [6.2.1]
оператор if … else if … else, оператор switch … case … default, проверка нескольких условий, тернарный оператор для реализации ветвлений
2.4 Операторы цикла [6.2.1]
циклы с предусловием и постусловием, оператор while и do … while, цикл с параметрами, оператор for, прерывание цикла, операторы break и continue
2.5 Массивы [6.2.1]
понятие массива, синтаксис объявления одномерных массивов, присваивание значений элементам массива, обращение к элементам массива, индекс, понятие двумерного и многомерного массива, ввод-вывод массивов, ступенчатые массивы и массивы массивов
3. Объектно-ориентированное программирования на языке C#
3.1 Основные понятия объектно-ориентированного программирования [6.1.3, гл. 13, гл. 16; 6.1.5, 6.2.1, 6.2.3, 6.2.5]
актуальность и определение объектно-ориентированного программирования, абстрагирование, инкапсуляцию, наследование, полиморфизм, класс, объект класса
3.2 Методы класса [6.1.3, гл. 13, гл. 16; 6.1.5, 6.2.1, 6.2.3, 6.2.5]
понятие метода, модификаторы доступа к членам класса private и public, конструктор и деструктор как особые методы класса, перегрузка методов, передача параметров по ссылке,
3.3 Свойства [6.1.3, 6.1.5, 6.2.3, 6.2.5]
свойства как элемент класса, назначение свойств, аксессоры get и set, свойства, доступные только для чтения или только для записи
3.4 Наследование [6.1.3, 6.1.5, 6.2.1, 6.2.3, 6.2.5]
понятие наследования, преимущества применения наследования, модификатор доступа protected, порядок вызова конструкторов при наследовании, сокрытие имён при наследовании, синтаксис наследования классов
3.5 Полиморфизм [6.1.3, 6.1.5, 6.2.3, 6.2.5]
понятие полиморфизма, абстрактные классы, виртуальные методы, механизм позднего связывания, перегрузка операций
4. Классы и технологии .NET
4.1. Коллекции [6.1.4, п.8.3, 6.1.5, гл.5, 6.2.1, 6.2.5]
Виды коллекций. Необобщенные и обобщенные коллекции. Интерфейсы IEnumerable, IEumerator. Необобщенные коллекции: ArrayList, BitArray, Hashtable, Queue, SortedList, Stack. Обобщенные коллекции: список List, двухсвязный список LinkedList, очередь Queue, стек Stack
4.2 Технология LINQ [6.1.3, 6.2.4]
Понятие функционального программирования. Виды коллекций. Технология LINQ для работы с коллекцией целых чисел. Технология LINQ для работы с коллекцией объектов.
1. Основные понятия дисциплины
1.1 Понятие и классификация языков программирования
Язык программирования — формальная знаковая система, предназначенная для записи компьютерных программ. Он определяет набор лексических, синтаксических и семантических правил, задающих внешний вид программы и действия, которые выполнит исполнитель (компьютер) под ее управлением.
Существует большое количество языков программирования. Приведём их классификацию.
По наиболее распространенной классификации все языки программирования, в соответствии с тем, в каких терминах необходимо описать задачу, делят на языки низкого и высокого уровня.
Если язык программирования близок к естественному языку, то он называется языком высокого уровня, если ближе к машинным командам, – языком низкого уровня.
Языки программирования также можно классифицировать на процедурные и непроцедурные.
В процедурных языках программа явно описывает действия, которые необходимо выполнить, а результат задается только способом получения его при помощи некоторой процедуры, которая представляет собой определенную последовательность действий.
Среди процедурных языков выделяют в свою очередь структурные и операционные языки. В структурных языках одним оператором записываются целые алгоритмические структуры: ветвления, циклы и т.д. В операционных языках для этого используются несколько операций. Широко распространены следующие структурные языки: Паскаль, Си, Ада, ПЛ/1. Среди операционных известны Фортран, Бейсик, Фокал.
Непроцедурное (декларативное) программирование появилось в начале 70-х годов 20 века. К непроцедурному программированию относятся функциональные и логические языки.
В функциональных языках программа описывает вычисление некоторой функции. Обычно эта функция задается как композиция других, более простых, те в свою очередь делятся на еще более простые задачи и т.д. Один из основных элементов функциональных языков — рекурсия. Оператора присваивания и циклов в классических функциональных языках нет.
В логических языках программа вообще не описывает действий. Она задает данные и соотношения между ними. После этого системе можно задавать вопросы. Машина перебирает известные и заданные в программе данные и находит ответ на вопрос. Порядок перебора не описывается в программе, а неявно задается самим языком. Классическим языком логического программирования считается Пролог. Программа на Прологе содержит, набор предикатов-утверждений, которые образуют проблемно-ориентированную базу данных и правила, имеющие вид условий.
Структурное программирование — методология разработки программного обеспечения, в основе которой лежит представление программы в виде иерархической структуры блоков.
В соответствии с данной методологией любая программа представляет собой структуру, построенную из трёх типов базовых конструкций: последовательное исполнение, ветвление, цикл. Повторяющиеся фрагменты могут оформляться в виде подпрограмм (процедур или функций).
Объектно-ориентированное программирование (в дальнейшем ООП) — методология разработки программного обеспечения, в которой основными концепциями являются понятия объектов и классов. На таких языках не описывают подробной последовательности действий для решения задачи, хотя они содержат элементы процедурного программирования.
1.2 Платформы java и .net
Для преобразования программы, написанной на одном из языков высокого уровня, в программу, состоящую из машинных команд используется транслятор (англ. translator — переводчик).
Трансляторы реализуются в виде компиляторов или интерпретаторов. С точки зрения выполнения работы компилятор и интерпретатор существенно различаются.
Компилятор (англ. compiler — составитель, собиратель) читает всю программу целиком, делает ее перевод и создает законченный вариант программы на машинном языке, который затем и выполняется.
Интерпретатор (англ. interpreter — истолкователь, устный переводчик) переводит и выполняет программу строка за строкой.
Рис. 1.
Часть действий программа не может выполнять напрямую с оборудованием, а обращается к операционной системе (ОС) с соответствующими запросами. Набор классов, процедур, функций, структур и констант, предоставляемых ОС для использования в программах называется интерфейсом прикладного программирования (API — application programming interface).
По такому принципу работают многие языки программирования, например C и C++.
Создание С знаменует собой начало современной эпохи программирования. Язык С был разработан Деннисом Ритчи в 1970-е годы для программирования на мини-ЭВМ DEC PDP-11 под управлением операционной системы Unix. Несмотря на то что в ряде предшествовавших языков, в особенности Pascal, был достигнут значительный прогресс, именно С установил тот образец, которому до сих пор следуют в программировании. Язык С появился в результате революции в структурном программировании в 1960-е годы. До появления структурного программирования писать большие программы было трудно, поскольку логика программы постепенно вырождалась в запутанный клубок безусловных переходов, вызовов и возвратов, которые трудно отследить. В структурированных языках программирования этот недостаток устранялся путем ввода строго определенных управляющих операторов, подпрограмм с локальными переменными и других усовершенствований.
Язык C++ был разработан в 1979 году Бьярне Страуструпом, работавшим в компании Bell Laboratories. Язык С полностью входит в состав C++, а следовательно, С служит основанием, на котором зиждется C++. Большая часть дополнений, введенных Страуструпом, обеспечивала плавный переход к ООП. И вместо того чтобы изучать совершенно новый язык, программирующему на С требовалось лишь освоить ряд новых свойств, чтобы воспользоваться преимуществами методики ООП.
Подход, показанный на рис. 1 обладает одним важным недостатком — плохая переносимость приложений, т.е. при необходимости использовать программу в другой ОС необходимо частично переписать её и заново скомпилировать.
Рис. 2.
Следующим важным шагом в развитии языков программирования стала разработка Java. Работа над языком Java, началась в 1991 году в компании Sun Microsystems. Java представляет собой структурированный, объектно-ориентированный язык с синтаксисом и конструктивными особенностями, унаследованными от C++. До появления на широкой арене Интернета большинство программ писалось, компилировалось и предназначалось для конкретного процессора и операционной системы. Как известно, программисты всегда стремились повторно использовать свой код, но, несмотря на это, легкой переносимости программ из одной среды в другую уделялось меньше внимания, чем более насущным задачам. Тем не менее с появлением Интернета, когда в глобальную сеть связывались разнотипные процессоры и операционные системы, застаревшая проблема переносимости программ вновь возникла с неожиданной остротой. Для решения проблемы переносимости потребовался новый язык, и им стал Java.
Самым важным свойством (и причиной быстрого признания) Java является способность создавать межплатформенный, переносимый код. Переносимость программ на Java достигается благодаря трансляции исходного кода в промежуточный, называемый байт-кодом. Этот байт-код затем выполнялся виртуальной машиной Java (JVM) — основной частью исполняющей системы Java.
Java Runtime Environment (сокр. JRE) — минимальная реализация виртуальной машины, необходимая для исполнения Java-приложений, без компилятора и других средств разработки.
Таким образом, программа на Java могла выполняться в любой среде, для которой была доступна JVM. А поскольку JVM реализуется относительно просто, то она сразу же стала доступной для большого числа сред. Применением байт-кода Java коренным образом отличается от С и C++, где исходный код практически всегда компилируется в исполняемый машинный код, который, в свою очередь, привязан к конкретному процессору и операционной системе. Так, если требуется выполнить программу на С или C++ в другой системе, ее придется перекомпилировать в машинный код специально для данной вычислительной среды. Следовательно, для создания программы на С или C++, которая могла был выполняться в различных средах, потребовалось бы несколько разных исполняемых версий этой программы. Это оказалось бы не только непрактично, но и дорого. Изящным и рентабельным решением данной проблемы явилось применение в Java промежуточного кода. Именно это решение было в дальнейшем приспособлено для целей языка С#.
К недостаткам концепции виртуальной машины относят то, что исполнение байт-кода виртуальной машиной может снижать производительность программ и алгоритмов, реализованных на языке Java в 1.5 — 2 раза.
Рис. 3.
Язык С# непосредственно связан с С, C++ и Java. И это не случайно. Ведь это три самых широко распространенных и признанных во всем мире языка программирования. Кроме того, на момент создания С# практически все профессиональные программисты уже владели С, C++ или Java.
Родственные связи С# и Java более сложные. Как пояснялось выше, Java также происходит от С и C++ и обладает общим с ними синтаксисом и объектной моделью. Как и Java, C# предназначен для получения переносимого кода, но С# не происходит непосредственно от Java. Напротив, С# и Java — это близкие, но не кровные родственники, имеющие общих предков, но во многом отличающиеся друг от друга.
1.3 Среда программирования
Всякий компилятор является составной частью системного программного обеспечения. Основное назначение компиляторов — служить для разработки новых прикладных и системных программ с помощью языков высокого уровня.
Однако сам по себе компилятор не решает полностью всех задач, связанных с разработкой новой программы.
Система программирования (среда программирования, интегрированная среда разработки, англ. IDE — Integrated development environment) — комплекс программных средств, предназначенных для написания, тестирования и отладки программного обеспечения.
Современная система программирования это достаточно сложный комплекс различных программно-технических средств.
Текстовый редактор
Текстовый редактор в системе программирования — это программа, позволяющая создавать, изменять и обрабатывать исходные тексты программ на языках высокого уровня.
В итоге в современных системах программирования текстовый редактор стал важной составной частью, которая не только позволяет пользователю подготавливать исходные тексты программ, но и выполняет все интерфейсные и сервисные функции предоставляемые пользователю системой программирования.
Транслятор
Отладчик
Отладчик — это программный модуль который позволяет выполнить основные задачи связанные с мониторингом процесса выполнения результирующей прикладной программы. Этот процесс называется отладкой и включает в себя следующие основные возможности:
• последовательное пошаговое выполнение результирующей программы на основе шагов по машинным командам или по операторам входного языка;
• выполнение результирующей программы до достижения ею одной из заданных точек останова (адресов останова);
• выполнение результирующей программы до наступления некоторых заданных условий, связанных с данными и адресами, обрабатываемыми этой программой;
• просмотр содержимого областей памяти, занятых командами или данными результирующей программы.
Отладчики в современных системах программирования представляют собой модули с развитым интерфейсом пользователя, работающие непосредственно с текстом и модулями исходной программы. Многие их функции интегрированы с функциями текстовых редакторов исходных текстов, входящих в состав систем программирования.
Библиотеки
Библиотека в программировании (от англ. library) — сборник подпрограмм или объектов, используемых для разработки программного обеспечения (ПО).
Библиотеки подпрограмм составляют существенную часть систем программирования. Наряду с дружественностью пользовательского интерфейса состав доступных библиотек подпрограмм во многом определяет возможности системы программирования и ее позиции на рынке средств разработки программного обеспечения.
С точки зрения системы программирования, библиотеки подпрограмм состоят из двух основных компонентов. Это собственно файл (или множество файлов) библиотеки, содержащий объектный код, и набор файлов описаний функций, подпрограмм, констант и переменных, составляющих библиотеку. Описания оформляются на соответствующем входном языке (например, для языка С или С++ это будет набор заголовочных файлов).
Объектный код библиотеки подключается компоновщиком к результирующей программе при создании исполняемого модуля. По структуре он обычно мало чем отличается от обычных объектных файлов, порождаемых компилятором. Чаще всего система программирования храпит объектный код входящих в ее состав библиотек в некотором упакованном виде.
Динамические библиотеки в отличие от традиционных (статических) библиотек подключаются к программе не в момент её компоновки, а непосредственно в ходе выполнения, как только программа затребовала ту или иную функцию, находящуюся в библиотеке. Преимущества таких библиотек очевидны — они не требуют включать в программу объектный код часто используемых функции, чем существенно сокращают объем кода. Различные программы, выполняемые в ОС, могут пользоваться кодом одной и той же библиотеки, содержащейся в данной ОС.
Широкий набор динамических библиотек поддерживается всеми современными ОС. Как правило, они содержат системные функции ОС и общедоступные функции программного интерфейса (API). Кроме того, многие независимые разработчики предоставляют для различных систем программирования свои динамические библиотеки как отдельные товары на рынке средств разработки прикладных программ.
Статическая библиотека — это коллекция объектных файлов, которые присоединяются к программе во время линковки программы. Таким образом статические библиотеки используются только при создании программы. Потом в работе самой программы они не принимают участие, в отличие от динамических библиотек.
Динамическая библиотека — это созданная специальным образом библиотека, которая присоединяется к результирующей программе в два этапа. Первый этап, это естественно этап компиляции. На этом этапе линковщик встраивает в программу описания требуемых функций и переменных, которые присутствуют в библиотеке. Сами объектные файлы из библиотеки не присоединяются к программе. Присоединение этих объектных файлов (кодов функций) осуществляет системный динамический загрузчик во время запуска программы. Загрузчик проверяет все библиотеки прилинкованные с программе на наличие требуемых объектных файлов, затем загружает их в память и присоединяет их в копии запущенной программы, находящейся в памяти.
Сложный процесс загрузки динамических библиотек замедляет запуск программы, но у него есть существенный, даже можно сказать неоценимый плюс — если другая запускаемая программа линкована с этой же загруженной динамической библиотекой, то она использует туже копию библиотеки. Это означает, что требуется гораздо меньше памяти для запуска нескольких программ, сами загрузочные файлы меньше по размеру, что экономит место на дисках.
Компоновщик
Компоновщик (или редактор связей) предназначен для связывания между собой объектных файлов, порождаемых компилятором, а также файлов библиотек, входящих в состав системы программирования.
Объектный модуль (также — объектный файл, англ. object file) — файл с промежуточным представлением отдельного модуля программы, полученный в результате обработки исходного кода компилятором. Объектный файл содержит в себе особым образом подготовленный код (часто называемый бинарным), который может быть объединён с другими объектными файлами при помощи редактора связей (компоновщика) для получения готового исполнимого модуля, либо библиотеки.
Примеры систем программирования:
Microsoft Visual Studio
C++ Builder
Borland C++
NetBeans
Eclipse
MonoDevelop
KDevelop
1.4. Критерии качества программного обеспечения
Качество (quality) ПО — это совокупность его черт и характеристик, которые влияют на его способность удовлетворять заданные потребности пользователей. Качество ПО является удовлетворительным, когда оно обладает указанными свойствами в такой степени, чтобы гарантировать успешное его использование.
Поэтому при описании качества ПО, прежде всего, должны быть фиксированы критерии отбора требуемых свойств ПО. В настоящее время критериями качества ПО (criteria of software quality) принято считать: функциональность, надежность, легкость применения, эффективность, сопровождаемость, мобильность.
Функциональность — это способность ПС выполнять набор функций, удовлетворяющих заданным или подразумеваемым потребностям пользователей.
Надежность (reliability) — это способность ПС безотказно выполнять определенные функции при заданных условиях в течение заданного периода времени с достаточно большой вероятностью. При этом под отказом в ПО понимают проявление в нем ошибки. Таким образом, надежное ПС не исключает наличия в нем ошибок — важно лишь, чтобы эти ошибки при практическом применении проявлялись достаточно редко. Убедиться, что ПО является надежным можно при его тестировании, а также при практическом применении. Таким образом, можно разрабатывать лишь надежные, а не правильные ПО. Степень надежности можно характеризовать вероятностью работы ПО без отказа в течение определенного периода времени. При оценке степени надежности ПС следует также учитывать последствия каждого отказа. Некоторые ошибки в ПС могут вызывать лишь некоторые неудобства при его применении, тогда как другие ошибки могут иметь катастрофические последствия. Поэтому для оценки надежности ПС иногда используют дополнительные показатели, учитывающие стоимость (вред) для пользователя каждого отказа.
Основным методом обеспечения надежности программного средства является борьба со сложностью. Известны два общих метода борьбы со сложностью систем:
- обеспечения независимости компонентов системы;
- использование в системах иерархических структур.
Обеспечение независимости компонент означает разбиение системы на такие части, между которыми должны остаться по возможности меньше связей. Одним из воплощений этого метода является модульное программирование. Использование иерархических структур допускает связи только между компонентами, принадлежащими смежным уровням иерархии. Этот метод означает разбиение большой системы на подсистемы путем абстрагирования.
При реализации ПО сначала необходимо обеспечить требуемую функциональность и надежность ПС, а затем уже доводить остальные критерии качества до приемлемого уровня.
Легкость применения — это характеристики ПС, которые позволяют минимизировать усилия пользователя по подготовке исходных данных, применению ПО и оценке полученных результатов, а также вызывать положительные эмоции пользователя. Легкость применения определяется составом и качеством пользовательской документации, а также некоторыми свойствами, реализуемыми программным путем (например, пользовательский интерфейс).
Пользовательский интерфейс представляет средство взаимодействия пользователя с ПС. При разработке пользовательского интерфейса следует учитывать потребности, опыт и способности пользователя. Поэтому потенциальные пользователи должны быть вовлечены в процесс разработки такого интерфейса.
При разработке интерфейса необходимо соблюдать следующие принципы: базирование на терминах и понятиях, знакомых пользователю; единообразность; возможность пользователю получать справочную информацию как по запросу, так и генерируемую ПО.
В настоящее время широко распространены командные и графические пользовательские интерфейсы.
Эффективность — это отношение уровня услуг, предоставляемых ПО пользователю при заданных условиях, к объему используемых ресурсов.
На эффективность ПС влияет выбор способа представления данных и выбор алгоритмов, а также особенности их реализации (включая выбор языка программирования. При этом постоянно приходится разрешать противоречие между временнόй эффективностью и эффективностью по памяти (ресурсам).
Для отыскания критических модулей с точки зрения временнóй эффективности ПО потребуется получить распределение по модулям времени работы ПО путем соответствующих измерений во время выполнения ПО. Это может быть сделано с помощью динамического анализатора (специального программного инструмента), который может определить частоту обращения к каждому модулю в процессе применения ПО.
Сопровождаемость — это характеристика ПО, которая позволяет минимизировать усилия по внесению изменений для устранения в нем ошибок и по его модификации в соответствии с изменяющимися потребностями пользователей. Обеспечение сопровождаемости ПО сводится к обеспечению изучаемости ПО и к обеспечению его модифицируемости.
Мобильность — это способность ПО быть перенесенным из одной среды (окружения) в другую, в частности, с одного компьютера на другой.
Критерии качества являются противоречивыми: хорошее обеспечение одного какого-либо критерия качества может существенно затруднить или сделать невозможным обеспечение некоторых других. Поэтому существенная часть процесса обеспечения качества состоит из поиска приемлемых компромиссов.
2. Структурное программирования на языке C#
2.1 Переменные и типы данных
Объявление переменных
тип имя;
тип имя = значение;
Константы
const тип имя = значение;
Типы данных
Целочисленные типы:
byte — 8-битное целое без знака (0:255)
sbyte — 8-битное целое со знаком (128:127)
short — 16-битное целое со знаком –32768:32767 (–215:215–1)
ushort — 16-битное целое без знака 0:65535 (0:216–1)
int — 32-битное целое со знаком (–231:231–1)
uint — 32-битное целое без знака (0:232–1)
long — 64-битное целое со знаком (–263:263–1)
ulong — 64-битное целое без знака (064–1)
Типы с плавающей запятой:
float — 32-битное с плавающей точкой одинарной точности
double — 64-битное с плавающей точкой двойной точности
decimal — 128-битное с плавающей точкой в десятичной нотации с высокой точностью
Логический тип:
bool — два значения true и false. 0 или 1 использовать нельзя.
Символьный тип:
char — хранит 1 символ. Для присвоения необходимо заключать в одинарные кавычки ’ ’.
Строковый тип:
string — хранит строки. Для присвоения необходимо заключать в двойные кавычки ” ”.
2.2 Арифметические операторы
Арифметические операторы:
+
–
*
/
% — деление по модулю. a = 10 % 3; // a = 1.
++
––
Оба оператора инкремента и декремента можно указывать до операнда (в префиксной форме) или же после операнда (в постфиксной форме).
++х; // префиксная форма
х++; // постфиксная форма
Если оператор инкремента или декремента используется в длинном выражении, то отличие в форме его записи уже имеет значение. Когда оператор инкремента или декремента предшествует своему операнду, то результатом операции становится значение операнда после инкремента или декремента. А когда оператор инкремента или декремента следует после своего операнда, то результатом операции становится значение операнда до инкремента или декремента. Рассмотрим следующий фрагмент кода.
х = 10;
у = ++х;
В данном случае значение переменной у будет установлено равным 11, поскольку значение переменной х сначала увеличивается на 1, а затем присваивается переменной у. Но во фрагменте кода
х = 10;
у = х++;
значение переменной у будет установлено равным 10, так как в этом случае значение переменной х сначала присваивается переменной у, а затем увеличивается на 1. В обоих случаях значение переменной х оказывается равным 11. Отличие состоит лишь том, когда именно это значение станет равным 11: до или после его присваивания переменной
Деление на целое число даёт целое и может привести к ошибкам.
Операторы отношения:
== Равно
!= Не равно
> Больше
< Меньше
>= Больше или равно
<= Меньше или равно
Логические операторы:
& И
| ИЛИ
^ Исключающее ИЛИ
&& Укороченное И
|| Укороченное ИЛИ
! НЕ
Результатом выполнения оператора отношения или логического оператора является логическое значение типа bool.
Поясним это на следующих примерах логических операций. Если первый операнд логической операции И имеет ложное значение (false), то ее результат будет иметь ложное значение независимо от значения второго операнда. Если же первый операнд логической операции ИЛИ имеет истинное значение (true), то ее результат будет иметь истинное значение независимо от значения второго операнда. Благодаря тому что значение второго операнда в этих операциях вычислять не нужно, экономится время и повышается эффективность кода.
Оператор присваивания:
х = у = z = 100; // присвоить значение 100 переменным х, у и z
Составные операторы присваивания:
х += 10; х = х + 10;
Оператор ?
Оператор ? относится к числу самых примечательных в С#. Он представляет собой условный оператор и часто используется вместо определенных видов конструкций if-then-elsе. Оператор ? иногда еще называют тернарным, поскольку для него требуются три операнда. Ниже приведена общая форма этого оператора.
Выражение1 ? Выражение2 : Выражение3;
Здесь Выражение1 должно относиться к типу bool, а Выражение2 и Выражение3 — к одному и тому же типу. Обратите внимание на применение двоеточия и его местоположение в операторе ?. Значение выражения ? определяется следующим образом. Сначала вычисляется Выражение1. Если оно истинно, то вычисляется Выражение2, а полученный результат определяет значение всего выражения ? в целом. Если же Выражение1 оказывается ложным, то вычисляется Выражение3, и его значение становится общим для всего выражения ?.
Рассмотрим следующий пример, в котором переменной absval присваивается значение переменной val.
absval = val < 0 ? –val : val; // получить абсолютное значение переменной val
В данном примере переменной absval присваивается значение переменной val, если оно больше или равно нулю. Если же значение переменной val отрицательно, то переменной absval присваивается результат отрицания этого значения, что в итоге дает положительное значение.
Приоритеты операций
При вычислении выражения каждая операция обрабатывается по очереди, что вовсе не обязательно означает, что вычисление этих операций происходит строго в порядке слева направо. В качестве типичного примера, возьмем следующее выражение:
varl = var2 + var3;
Здесь операция + будет вступать в силу раньше, чем =. Однако бывают ситуации, когда приоритеты выполнения операций и не является столь очевидными, как, например, в следующем выражении:
varl = var2 + var3 * var4;
В этом выражении первой будет выполняться операция *, за ней — операция + и только потом — операция =. Такой порядок выполнения соответствует тому, который применяется в математике, и обеспечивает получение точно такого же результата, как и в случае выполнения аналогичного алгебраического подсчета вручную.
Использование круглых скобок, как и в математике, позволяет управлять порядком выполнения операций; например, рассмотрим следующее выражение:
varl = (var2 + var3) * var4;
высокий префиксные ++ – –, унарные + и –
* / %
+ –
= *= /= %= += –=
низкий постфиксные ++ – –
Примеры.
double b = (Math.Pow(Math.Sin(2 * a + 3.14 / 3), 2) + Math.Pow(Math.Cos(3.14 * a), 3)) / (Math.Tan(Math.Sqrt(a)) * Math.Sqrt(Math.Sin(Math.Pow(a,2))));
double c = Math.Pow(Math.Sin(2 * a + 3.14 / 3), 2) + Math.Pow(Math.Cos(3.14 * a), 3);
double z = Math.Tan(Math.Sqrt(a)) * Math.Sqrt(Math.Sin(Math.Pow(a, 2)));
double b = c / z;
2.3 Операторы ветвления
Ветвлением называется процесс управления тем, какая строка кода должна выполняться следующей. За то, на какую строку должен осуществляться переход, отвечает определенный вид условного оператора. Действие этого условного оператора основано на сравнении проверочного значения с одним или более возможными значениями с применением булевской логики.
if (<условие>) <код, выполняемый, если <условие> равно ;
После выполнения этого кода, или невыполнения из-за того, что в результате вычисления выражения <условие> было получено false, выполнение программы снова возобновляется со следующей строки кода.
Вместе с оператором if также может указываться и дополнительный код с помощью оператора else. Этот оператор выполняется в случае, если при вычислении выражения <условие> получается false:
if (<условие>) <код, выполняемый, если <условие> равно true>;
else <код, выполняемый, если <условие> равно false>;
Оба раздела кода могут занимать несколько строк и представлять собой заключенные в фигурные скобки блоки:
if (<условие>)
{
<код, выполняемый, если <условие> равно true>;
}
else
{
<код, выполняемый, если <условие> равно false>;
}
Для проверки нескольких условий:
if (<условие>)
{
<код, выполняемый, если <условие> равно true>;
}
else if (<условие2>)
{
<код, выполняемый, если <условие2> равно true>;
}
else
{
<код, выполняемый, если ни одно из условий не равно true>;
}
Оператор switch:
switch (<проверяемая_переменная>)
{
case <значение_для_сравнения_1>:
<код, подлежащий выполнению, если <проверяемая_переменная> == <значение_для_сравнения_2>
break;
case <значение_для_сравнения_2>:
<код, подлежащий выполнению, если <проверяемая_переменная> == <значение_для_сравнения_2>
break;
case <значение_для_сравнения_N>:
<код, подлежащий выполнению, если <проверяемая_переменная> == <значение_для_сравнения_N>
break;
default:
<код, подлежащий выполнению, во всех остальных случаях>
break;
}
В конце кода в каждом разделе обязательно добавляется такая дополнительная команда, как break, исключающая вероятность перехода после обработки одного блока case к обработке следующего оператора case, потому что это недопустимо.
Пример. Программа для решения квадратных уравнений
ax2+bx+c=0
1. Вычисление дискриминанта:
D=b2–4ac
2. Если D > 0, то уравнение имеет 2 корня:
Если D = 0, то уравнение имеет 1 корень:
Если D < 0, то действительных корней нет.
static void Main(string[] args)
{
double a;
double b;
double c;
Console.Write("Введите a: ");
a = double.Parse(Console.ReadLine());
Console.Write("Введите b: ");
b = double.Parse(Console.ReadLine());
Console.Write("Введите c: ");
c = double.Parse(Console.ReadLine());
double D = Math.Pow(b, 2) - 4 * a * c;
if (D > 0)
{
double x1 = (-b + Math.Sqrt(D)) / (2 * a);
double x2 = (-b - Math.Sqrt(D)) / (2 * a);
Console.WriteLine("x1 = " + x1);
Console.WriteLine("x2 = " + x2);
}
else if (D == 0)
{
double x = -b / (2 * a);
Console.WriteLine("x = " + x);
}
else
{
Console.WriteLine("Действительных корней нет");
}
}
2.4. Циклы
Организация циклов подразумевает повторяющееся выполнение операторов. Эта методика является очень полезной, поскольку позволяет делать так, чтобы необходимые операции выполнялись повторно столько, сколько требуется раз, без повторного написания одного и того же кода снова и снова.
Цикл do
Циклы do функционируют следующим образом. Сначала выполняется код, предназначенный для прогона в цикле, затем производится булевская проверка, и если в ее результате возвращается true, данный код выполняется снова, и так повторяется до тех пор, пока после проверки не будет возвращено значение false, после чего цикл завершается.
Do
{
<код, подлежащий выполнению в цикле>;
} while (<условие>);
Пример. Выводит числа от 1 до 10.
Int i = 1;
do
{
Console.WriteLine(i++);
} while (i <= 10);
Console.WriteLine(++i);
Выведет числа от 2 до 11.
Console.WriteLine(i);
будет бесконечно выводить число 1.
Циклы while
Циклы while очень похожи на циклы do, но имеют одно важное отличие: булевская проверка в них выполняется в начале, а не в конце цикла. Если после выполнения этой проверки возвращается false, код в цикле вообще не выполняется. Вместо этого поток выполнения переходит сразу на код, следующий непосредственно за циклом.
While (<проверка>)
{
<код, подлежащий выполнению в цикле>;
}
Пример.
Int i = 1;
while (i <= 10)
{
Console.WriteLine(i++);
}
Цикл for
Цикл этого типа выполняется определенное количество раз и обладает собственным счетчиком. Для определения цикла for требуется описанная ниже информация:
• начальное значение для инициализации переменной, играющей роль счетчика;
• условие для продолжения цикла, в котором участвует переменная-счетчик;
• операция, которая должна выполняться над переменной-счетчиком в конце каждого цикла.
For {<инициализация>; <условие>; <операция>)
{
<код, подлежащий выполнению в цикле>;
}
Пример.
For (int i = 1; i <= 10; ++i)
{
Console.WriteLine(i);
}
Прерывание циклов
В некоторых случаях бывает необходимо иметь возможность более точного управления ходом обработки содержащегося в циклах кода.
Break — приводит к немедленному завершению цикла;
continue — приводит к немедленному завершению текущей итерации цикла (выполнение продолжается со следующей итерации);
2.5. Массивы
1. Переменные
тип имя=значение;
2. Массивы
double[] m = new double[5]; — массив из пяти элементов типа double.
double[] m = {2,4,6,8,0};
double[] m = new double[5] {2,4,6,8,0};
double[] m = new double[] {2,4,6,8,0};
3. Обращение к элементам массива
Все элементы массива имеют одно имя, идинаковый тип и различаются по индексу
m[1] = 0.25;
double s = m[0] + m[4];
4. Ввод элементов массива
m[0]=double.Parse(Console.ReadLine());
5. Ввод с помощью цикла
double[] m = new double[5];
for (int i = 0; i < 5; i++)
{
Console.Write("Введите " + i + "-ый элемент массива: ");
m[i] = double.Parse(Console.ReadLine());
}
6. Вывод
int n = 3;
double[] m = new double[n];
for (int i = 0; i < n; i++)
{
Console.Write("Введите " + i + "-ый элемент массива: ");
m[i] = double.Parse(Console.ReadLine());
}
for (int i = 0; i < n; i++)
{
Console.Write(m[i]+", ");
}
7. Вычисление суммы
double sum = 0;
for (int i = 0; i < n; i++)
{
sum += m[i];
}
Console.WriteLine(" Сумма равна " + sum);
8. Произведение элементов, модуль которых больше 2
double proiz = 1;
for (int i = 0; i < n; i++)
{
if (Math.Abs(m[i]) > 2)
{
proiz *= m[i];
}
}
Console.WriteLine(" Произведение равно " + proiz);
9. Поиск максимального элемента в массиве
double max = m[0];
for (int i = 1; i < n; i++)
{
if (m[i] > max)
{
max = m[i];
}
}
Console.WriteLine();
Console.WriteLine(" Максимальный элемент = " + max);
10. Последний положительный элемент
11. Сортировка
for (int j = 0; j < n - 1; j++)
{
double min = m[j];
int imin = j;
for (int i = j+1; i < n; i++)
{
if (m[i] < min)
{
min = m[i];
imin = i;
}
}
m[imin] = m[j];
m[j] = min;
}
Двумерные массивы
int[,] table = new int[4, 5];
Первым указывается число строк, вторым число столбцов.
int[,] matrix = {
{1,2,3},
{4,5,6},
{7,8,9}
};
int[,] matrix = new int[3,3] {
{1,2,3},
{4,5,6},
{7,8,9}
};
int[,] matrix = new int[,] {
{1,2,3},
{4,5,6},
{7,8,9}
};
Ввод-вывод двумерных массивов
int n = 3;
int m = 4;
double[,] matrix = new double[n, m];
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
Console.Write("Введите элемент [" + i + "," + j + "] ");
matrix[i, j] = double.Parse(Console.ReadLine());
}
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
Console.Write(matrix[i,j]);
}
Console.WriteLine();
}
Транспонирование матрицы
const int n = 3;
const int m = 4;
int[,] matrix = new int[n,m] {
{1,2,3,4},
{4,5,6,7},
{7,8,9,0}
};
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
Console.Write(matrix[j,i]);
}
Console.WriteLine();
}
Диагонали матрицы
Главная диагональ
for (int i = 0; i < n; i++)
{
Console.WriteLine(matrix[i,i]);
}
Побочная диагональ
for (int i = 0; i < n; i++)
{
Console.WriteLine(matrix[i,n-1-i]);
}
Многомерные массив
int[,,] multidim = new int[4, 10, 3];
Ступенчатые массивы
В С# можно также создавать специальный тип двумерного массива, называемый ступенчатым массивом. Ступенчатый массив представляет собой массив массивов, в котором длина каждого массива может быть разной. Следовательно, ступенчатый массив может быть использован для составления таблицы из строк разной длины.
тип[][] имя_массива = new тип [размер][];
где размер обозначает число строк в массиве.
static void Main(string[] args)
{
int[][] matrix = new int[3][];
matrix[0] = new int[2];
matrix[1] = new int[3];
matrix[2] = new int[5];
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < matrix[i].Length; j++)
{
Console.Write(matrix[i][j]);
}
Console.WriteLine();
}
}
С каждым массивом связано свойство Length, содержащее число элементов, из которых может состоять массив. Следовательно, у каждого массива имеется специальное свойство, позволяющее определить его длину.
3. Объектно-ориентированное программирования на языке C#
Объектно-ориентированное (ООП) — парадигма программирования, в которой основными концепциями являются понятия объектов и классов.
Класс представляет собой шаблон, по которому определяется форма объекта. В нем указываются данные и код, который будет оперировать этими данными. В С# используется спецификация класса для построения объектов, которые являются экземплярами класса. Следовательно, класс, по существу, представляет собой ряд схематических описаний способа построения объекта. При этом очень важно подчеркнуть, что класс является логической абстракцией. Физическое представление класса появится в оперативной памяти лишь после того, как будет создан объект этого класса во время исполнения программы.
Основные понятия объектно-ориентированного программирования:
Абстрагирование — выделение набора значимых характеристик объекта, исключая из рассмотрения незначимые.
Инкапсуляция — объединение данных и методов, работающих с ними, в классе с целью скрытия деталей реализации от пользователя класса (под пользователем класса понимается программист, который при написании программы использует возможности класса через методы класса (интерфейс) не зная деталей реализации).
Наследование — возможность создавать новые определения классов на основе существующих, расширяя и переопределяя их функциональность. Наследование используется для повторного использования кода. Класс, от которого производится наследование, называется базовым, родительским или суперклассом, новый класс — потомком, наследником или производным классом.
Полиморфизм — возможность объектов с одинаковой спецификацией иметь различную реализацию. Полиморфизм позволяет поддерживать выполнение нужного действия в зависимости от типа объекта и применяется для универсальной обработки схожих объектов разных типов.
1. Объявление класса на языке C#
Для реализации инкапсуляции в языке C# используются модификаторы доступа. Язык С# поддерживает следующие модификаторы доступа (модификаторы видимости):
— public — поля, свойства и методы являются общедоступными;
— private — поля, свойства и методы будут доступны только в классе, в котором они определены;
— protected — поля, свойства и методы будут доступны как в классе, в котором они определены, так и в любом производном класса;
— internal — поля, свойства и методы будут доступны во всех классах внутри сборки, в которой определен класс.
Для контроля доступа к полям класса в языке C# можно использовать свойства. Внутри класса свойство определяется в виде пары методов для присвоения значения свойств и для чтения значения свойства. Для пользователей объектов класса свойство представляется как поле класса. Синтаксис определения свойства класса:
<тип> <имя_свойства> {
get {
return <значение>;
}
set {
<имя_поля> = value;
}
}
Метод get используется для получения значения свойства. Метод set используется для изменения значения свойства. Внутри метода set новое значение свойства передается в виде переменной value. С помощью свойств можно проверить новое значение поля, прежде, чем изменить поле; выполнить дополнительные действия при изменении или чтении поля, представить для пользователя в виде поля информацию, которая на самом деле не является полем класса.
Язык C# не поддерживает множественное наследование.
Для реализации классического наследования в языке C# используется следующий синтаксис:
class <имя_класса_потомка> : <имя_класса_родителя> {
}
Для вызова в конструкторе класса-потомка конструктора класса-родителя используется ключевое слово base:
class <имя_класса_потомка> : <имя_класса_родителя> {
<имя_класса_потомка> (<аргументы>) : base(<аргументы>) {
}
}
Также ключевое слово base используется для вызовов методов класса-родителя из переопределенных методов в классе-потомке. Язык C# также поддерживает наследование включением.
Для реализации полиморфизма, основанного на виртуальных методах, в языке C# используются ключевые слова virtual и override:
class <имя_класса_родителя> {
virtual <имя_метода1> (аргументы) {
}
}
class <имя_класса_потомка> : <имя_класса_родителя> {
override <имя_метода1> (<аргументы>) {
}
}
Кроме того, язык C# поддерживает объявление абстрактных методов с помощью ключевого слова abstract. Абстрактные методы не содержат реализации и должны быть переопределены с помощью ключевого слова override в классах-потомках. Класс, в котором объявлен хотя бы один абстрактный метод, является абстрактным и должен быть объявлен в помощью ключевого слова abstract. Объекты абстрактного класса не могут быть созданы.
При определении класса объявляются данные, которые он содержит, а также код, оперирующий этими данными.
class <имя_класса> {
// Объявление переменных.
<модификатор_доступа> <имя_типа> <имя_переменной1>;
<модификатор_доступа> <имя_типа> <имя_переменной2>;
//...
<модификатор_доступа> <имя_типа> <имя_переменнойN>;
// Объявление методов.
<модификатор_доступа> <возращаемый_тип> <имя_метода1> (<параметры>) {
<тело метода1>
}
<модификатор_доступа> <возращаемый_тип> <имя_метода2> (<параметры>) {
<тело метода2>
}
//...
<модификатор_доступа> <возращаемый_тип> <имя_методаN> (<параметры>) {
<тело методаN>
}
}
2. Свойства класса
Для удобства программирования в класс введено понятие свойства. Свойство представляет собой совокупность поля и одного или двух методов для считывания и/или записи данных в поле. Это позволяет использовать более предпочтительный синтаксис обращения к свойству как к полю, а не через синтаксис метода и одновременно позволяет скрыть данные в классе.
Так в языке Java свойств нет и обращение, например, к полю m_Fam осуществляется через методы следующим образом:
string s;
stud a = new stud();
s=a.getFam();
a.setFam(s).
Метод getFam служит для считывания информации, записанной в поле m_Fam, а метод setFam служит для записи нового значения в поле m_Fam.
В данном случае класс имеет вид:
class stud
{
private string m_Fam;
public string getFam (void) { return m_Fam; }
public void setFam (string s);
}
В языке C# свойство позволяет использовать более удобный синтаксис обращения к свойству (как будто это поле):
string s;
stud a = new stud();
s = a.Fam;
a.Fam = s;
Синтаксис описания свойства следующий:
class stud
{
private string m_Fam;
public string Fam
{
get { return m_Fam; }
set { m_Fam = value; }
}
}
Как видно полю m_Fam сопоставлено свойство с двумя методами: метод get — для считывания данных из поля и метод set — для записи данных в поле. Свойство является удобным способом предотвратить некорректную работу с полем, например, запись информации в неверном формате или неверном диапазоне:
public string Fam
{
get { return m_Fam; }
set
{
//поле реально изменится, если длина больше 1 буквы
//и первая буква русская строчная
if (value.length >1 & value[0]>=’А’ & value[0]<=’Я’)
m_Fam = value;
}
}
Начиная с версии C# 3.0 имеется возможность использовать короткий синтаксис объявления свойства:
public string Fam { get; set; }
В таком синтаксисе нет возможности объявить тела методов get и set. Они принимаются по умолчанию
get { return m_Fam; }
set { m_Fam = value; }
Это не позволяет добавить проверку значения при записи в поле. Поле также описывать не нужно.
Например, класс Grajdanin содержит 3 свойства типа string:
class Grajdanin
{
public string Fam { get; set; } //фамилия
public string Nam { get; set; } //имя
public string Country { get; set; } //страна
}
Запись
public string Fam { get; set; } //фамилия
вводит свойство Fam и автоматически создаваемое поле и является более компактной и удобной формой ранее изучаемой записи
3. Методы класса и методы объекта.
Функциональность класса реализуется в методах. Методы могут быть статическими (методами класса) и методами объекта. Методы могут обрабатывать данные: поля класса и аргументы метода. Аргументы могут быть входными, аргументами ссылками (ref) и выходными аргументами (out).
Входные аргументы используются для передачи методу переменной по значению — в переменную метода передается копия аргумента. При передаче значения в функцию можно передать константное выражение.
void func(int a)
{
a++;
}
func (4); //можно так вызвать функцию func
int c=0;
func (c);
Console.Write(c); //будет 0
Однако для объектов (экземпляров класса) передача по значению всегда означает передачу по ссылке (для экономии памяти):
void func(Grajdanin a)
{
a.Fam = ”Иванов”;
}
Stud c=new Grajdanin(“Козлов”);
Console.Write(c.Fam); //будет “Иванов”
Аргументы-ссылки (ref) позволяют передать значение по ссылке, которое метод мог бы изменить. Перед передачей аргумента ref в метод переменную надо инициализировать. Переменная должна иметь тот же тип, что и аргумент. Передавать константное выражение недопустимо.
void func(ref int a)
{
a++;
}
int c=0;
func (ref c); //будет c=1
func (ref 0); //нельзя — ошибка компиляции
Выходные аргументы (out) предназначены для возврата значения из метода. Отличие от параметра ref в том, что предварительная инициализация переменной не требуется.
void Sq(float a, out float sq)
{
sq = a*a;
}
float s; //не инициализировано
Sq(4, out s); //будет s=16
Поскольку функция имеет только один результат, то выходные параметры (out) могут использоваться для возврата из функции большего количества значений.
В C# допускается совместное использование одного и того же имени двумя или более методами одного и того же класса. В этом случае говорят, что методы перегружаются, а сам процесс называется перегрузкой методов.
Перегрузка методов. Перегрузка методов в C# означает, что в классе можно определить несколько методов с одним и тем же именем при условии, что эти методы получают разное число или типы параметров.
Перегрузка методов относится к одному из способов реализации полиморфизма в C#. В общем, для перегрузки метода достаточно объявить разные его варианты, а об остальном позаботится компилятор. Но при этом необходимо соблюсти следующее важное условие: тип или число аргументов у каждого метода должны быть разными. Совершенно недостаточно, чтобы два метода отличались только типами результата. Они должны отличаться типами или числом своих аргументов. Когда вызывается перегружаемый метод, то выполняется тот его вариант, параметры которого соответствуют (по типу и числу) передаваемым аргументам. Модификаторы параметров ref и out также учитываются, когда принимается решение о перегрузке метода, но отличие между ними не столь существенно.
class UserInfo
{
// Перегружаем метод ui
public void ui() //1
{
Console.WriteLine("Пустой метод\n");
}
public void ui(string Name) //2
{
Console.WriteLine("Имя пользователя: {0}",Name);
}
public void ui(string Name, string Family) //3
{
}
public void ui(string Name, string Family, byte Age) //4
{
}
public void ui(string Name, string Family, ref byte Age) //5
{
}
}
UserInfo user1 = new UserInfo();
// Разные реализации вызова перегружаемого метода
user1.ui(); //вызывается метод 1
user1.ui("Александр"); //вызывается метод 2
user1.ui("Александр", "Ерохин", 26); //вызывается метод 4
byte y = 20;
user1.ui("Александр", "Ерохин", ref y); //вызывается метод 5
4. Конструкторы.
Конструктор — метод, вызываемый при создании объекта.
В классе автоматически создается конструктор по умолчанию. Это правило выполняется для любого класса, не имеющего явно описанного конструктора. В случае, если программист описал хотя бы один конструктор в классе, то конструктор по умолчанию автоматически не создается.
Перегрузка конструкторов. Как и методы, конструкторы также могут перегружаться. Это дает возможность конструировать объекты самыми разными способами.
Одна из самых распространенных причин для перегрузки конструкторов заключается в необходимости предоставить возможность одним объектам инициализировать другие разными способами.
Когда приходится работать с перегружаемыми конструкторами, то иногда очень полезно предоставить возможность одному конструктору вызывать другой. В C# это дается с помощью ключевого слова this. Ниже приведена общая форма такого вызова:
<имя_конструктора> (<параметры1>) : this(<параметры2>)
{
<тело конструктора, которое может быть пустым>
}
В исходном конструкторе сначала выполняется перегружаемый конструктор, список параметров которого соответствует критерию <параметры2>, а затем все остальные операторы, если таковые имеются в исходном конструкторе. Ниже приведен соответствующий пример:
class UserInfo
{
public string Name, Family;
public byte Age;
// Используем ключевое слово this для
// создания "цепочки" конструкторов
public UserInfo() : this("None","None",0)
{
}
public UserInfo(UserInfo obj) : this(obj.Name, obj.Family, obj.Age)
{
}
public UserInfo(string Name, string Family, byte Age)
{
this.Name = Name;
this.Family = Family;
this.Age = Age;
}
}
Вызывать перегружаемый конструктор с помощью ключевого слова this полезно, в частности, потому, что он позволяет исключить ненужное дублирование кода.
5. Создание списка
Для создания списка используется класс List< > — обобщенный класс.
Для создания списка для хранения граждан необходимо записать
List list = new List( ),
где List — класс, в котором может храниться только список объектов класса Grajdanin или его потомков.
При таком описании создается пустой список, в который можно добавлять объекты класса Grajdanin, используя метод Add класса List, и конструктор по умолчанию класса Grajdanin.
Добавление нового объекта класса Grajdanin в список записывается следующим образом:
list.Add(new Grajdanin());
list[0].Fam = “Иванов”;
list[0].Nam = “Иван”;
list[0].Country = “Россия”;
или инициализировать поля до добавления в список так:
Grajdanin g = new Grajdanin();
g.Fam = “Иванов”;
g.Nam = “Иван”;
g.Country = “Россия”;
list.Add(g);
или инициализированные конструктором:
Grajdanin g = new Grajdanin(“Иванов”, “Иван”, “Россия”);
list.Add(g);
list.Add(“Петров”, “Петр”, “Россия”);
6. Сокращенная инициализация
В языке C#, начиная с версии 2.5 (Visual Studio 2008), имеется возможность добавлять объекты в список и инициализировать поля с помощью более компактной и удобной записи, которая выполняет то же, что и предыдущий программный код.
List list = new List( )
{
new Grajdanin { Fam = "Иванов", Nam = "Иван", Country ="Russia"},
new Grajdanin { Fam = "Петров", Nam = "Петр", Country ="Russia"},
new Grajdanin { Fam = "Сидоров", Nam = "Семен", Country ="Germany"},
new Grajdanin { Fam = "Кизин", Nam = "Киз", Country ="Израиль"}
};
Обратите внимание на то, что новая коллекция заполняется непосредственно внутри фигурных скобок без использования метода Add класса List.
Данный синтаксис инициализации будет работать даже при отсутствии конструктора с тремя параметрами для класса Grajdanin, поскольку в этом случае не вызывается конструктор.
Класс Grajdanin с кострукторами и методом ToString() будет иметь, например, следующий вид:
class Grajdanin
{
public string Fam { get; set; }
public string Nam { get; set; }
public string Country { get; set; }
public override string ToString()
{
return Fam + " " + Nam + " " + Country;
}
// конструктор по умолчанию (в классе может быть только один)
public Grajdanin()
{
Fam = "Иванов"; Nam = "Иван"; Country = "Russia";
}
// конструктор копирования (в классе может быть только один)
public Grajdanin(Grajdanin g)
{
Fam = g.Fam; Nam = g.Nam; Country = g.Country;
}
// конструкторы с параметрами
// конструкторов может быть сколько угодно,
// если различаются типы или количество аргументов
public Grajdanin(string fam, string nam)
{
Fam = fam; Nam = nam; Country = "Russia";
}
public Grajdanin(string fam, string nam, string contry)
{
Fam = fam; Nam = nam; Country = contry;
}
}
7. Перекрытие и сокрытие методов
Перекрытие методов позволяет в производном классе переопределить методы базового класса, если они определены как виртуальные (virtual). В производном классе можно создать метод с тем же именем, теми же аргументами и той же доступностью, дописав ключевое слово override.
Важной особенностью перекрытия является вызов метода производного класса при приведении производного класса к базовому классу.
class Rod
{
public virtual int f(int a) { }
}
class Reb: Rod
{
public override int f (int a) { }
}
Reb r = new Reb( );
r.f(1); //вызывается метод f ребенка
((Rod) r).f (1); //и здесь вызывается метод f ребенка,
//поскольку r в действительности объект класса Reb.
То есть решение о выборе вызываемого метода принимается во время работы программы, а не при компиляции. Это сказывается на быстродействии программы, но позволяет, например, одним программным кодом вызывать различные методы.
Преимущество перекрытия методов при использовании списков. В ссылку на родителя можно записать ссылку на родителя и на любого потомка (ребенка). То есть:
Rod r = new Rod();
Rod r1 = new Reb();
То есть в массив или список с элементами типа родителя можно поместить элементы — ссылки на ребенка.
Listlist=new List();
List.Add(new Rod());
List.Add(new Rod());
List.Add(new Reb());
List.Add(new Reb());
for (int i = 0; i {
<имя_типа_метода> <имя_метода> (<аргументы>);
<имя_типа_свойства> <имя_свойства> { get; set;}
}
Особенности использования интерфейсов:
— все методы интерфейса по определению являются открытыми, при этом запрещено использовать в определении методов модификаторы доступа;
— тип интерфейса можно использовать в объявлении параметров методов и переменных, но создавать объекты типа интерфейс нельзя.
— вместо изменения уже используемого интерфейса следует воспользоваться наследованием интерфейса;
— интерфейсы реализуются с помощью классов. Под реализацией классом интерфейса понимается написание в классе программного кода для каждого из объявленных в интерфейсе методов и свойств. Для реализации интерфейса необходимо:
— после имени класса, реализующего интерфейс, поставить двоеточие и написать имя интерфейса (если в классе необходимо реализовать несколько интерфейсов, следует разделить их имена запятыми);
— включить в класс все методы и свойства, определенные в интерфейсе;
— для каждого реализованного метода и свойства указать модификатор доступа public.
Возможность реализации одним классом нескольких интерфейсов заменяет отсутствие множественного наследования. Для получения доступа к интерфейсу объекта применяются следующие способы:
— явное приведение типа (с помощью операции () ) —
(<имя_интерфейса>) <имя_объекта>;
— с помощью ключевого слова as —
<имя объекта> as <имя_интерфейса>;
с помощью ключевого слова is —
if (<имя_объекта> is <имя_интерфейса>).
Для реализации наследования интерфейсов в языке C# используется следующий синтаксис:
interface <имя_интерфейса >: <имя_интерфейса_родителя> {
<тело интерфейса>
}
11. Вызов базовых версий методов.
Ключевое слово base явно указывает компилятору, что происходит обращение к методу базового класса.
public class CustomerAccount
{
public virtual decimal CalculatePrice (CustomerAccount account)
{
return 2000M;
}
}
public class GoldAccount : CustomerAccount
{
public override decimal CalculatePrice(CustomerAccount account)
{
return base.CalculatePrice(account) * 0.9M;
}
}
12. Исключения.
В результате работы программы могут произойти непредвиденные ситуации, например, деление на 0 или переполнение разрядной сетки переменной.
Далеко не всегда ошибки случаются по вине того, кто кодирует приложение. Иногда приложение генерирует ошибку из-за действий конечного пользователя, или же ошибка вызвана контекстом среды, в которой выполняется код. В любом случае программист всегда должен ожидать возникновения ошибок в своих приложениях и проводить кодирование в соответствии с этими ожиданиями.
В .NET Framework предусмотрена развитая система обработки ошибок. Механизм обработки ошибок С# позволяет закодировать пользовательскую обработку для каждого типа ошибочных ситуаций, а также отделить код, потенциально порождающий ошибки, от кода, обрабатывающего их.
Основу обработки исключительных ситуаций в С# составляет пара ключевых слов try и catch. Эти ключевые слова действуют совместно и не могут быть использованы порознь. Ниже приведена общая форма определения блоков try/catch для обработки исключительных ситуаций:
try {
// Блок кода, проверяемый на наличие ошибок.
}
catch (<тип_исключения> exOb) {
// Обработчик исключения типа ExcepType1.
}
catch (<тип_исключения> exOb) {
// Обработчик исключения типа ExcepType2.
}
...
где <тип_исключения> — это тип возникающей исключительной ситуации. Когда исключение генерируется оператором try, оно перехватывается составляющим ему пару оператором catch, который затем обрабатывает это исключение. В зависимости от типа исключения выполняется и соответствующий оператор catch. Так, если типы генерируемого исключения и того, что указывается в операторе catch, совпадают, то выполняется именно этот оператор, а все остальные пропускаются. Когда исключение перехватывается, переменная исключения exOb получает свое значение. На самом деле указывать переменную exOb необязательно. Так, ее необязательно указывать, если обработчику исключений не требуется доступ к объекту исключения, что бывает довольно часто. Для обработки исключения достаточно и его типа.
Для включения проверки переполнения в локальном месте программы можно использовать ключевое слово checked, а для отлавливания исключений try … catch.
private void button1_Click(object sender, EventArgs e)
{
int a = 20;
int s=1;
try
{
for (int i = 1; i <= a; i++)
checked { s *= i; }
}
catch (System.OverflowException)
{
//этот код будет выполнен при возникновения исключения
//System.OverflowException - переполнения
MessageBox.Show("Слишком большое число");
}
catch ( )
{
//этот код будет выполнен при возникновения любого
//другого исключения, не отловленного выше
MessageBox.Show("Какое-то исключение");
}
Text = s.ToString();
}
Внутри оператора try помещается код, в котором могут генерироваться исключения, а в блоке catch они обрабатываются.
Проверка двух исключнений
private void button1_Click(object sender, EventArgs e)
{
int a = 2000000;
int s=2;
try
{
a = a / s;
checked { a = a * 100000000; }
}
catch (System.OverflowException)
{
MessageBox.Show("Переполнение");
}
catch (System.DivideByZeroException)
{
MessageBox.Show("Деление на ноль");
}
Text = a.ToString();
}
Если s=2, то произойдет исключение переполнения, если s=0 – то исключение деления на 0. Как только происходит исключение – прерывается выполнение блока try и управление передается в блок catch. То есть при делении на 0, умножение выполняться не будет и a останется равной 20.
Стандартные исключения
Программная генерация исключений.
Программист может прописать генерацию исключения с помощью ключевого слова throw, указав определенное исключение
throw new ArgumentOutOfRangeException(“Аргумент должен быть больше 5”);
class Stol{
private float dl;
public float dlina
{
set
{
if (value<=0)
throw new
ArgumentOutOfRangeException(“Аргумент должен быть больше 0”);
dl=value;
}
get {return dl; }
}
}
class Program
{
Stol s = new Stol( );
void main()
{
try{
s.dlina = -100;
}
catch(ArgumentOutOfRangeException)
{
MessageBox(“Длина не допустимая”);
s.dlina = 1;
}
}
}
Оператор throw можно включить в блок catch, чтобы заново вызвать исключение, перехваченное блоком catch. В следующем примере извлекаются сведения об источнике из исключения IOException, а затем это исключение вызывается для родительского метода:
catch (FileNotFoundException e)
{
// FileNotFoundExceptions are handled here.
}
catch (IOException e)
{
// Извлекаем информацию об исключении и вызываем родительский метод
// т.к. IOException родитель FileNotFoundException
if (e.Source != null)
Console.WriteLine("IOException source: {0}", e.Source);
throw;
}
Вы можете перехватывать одно исключение и сгенерировать другое исключение, как показано в следующем примере:
catch (InvalidCastException e)
{
// Выполняем некоторый код здесь и затем генерируем новое исключение
throw new YourCustomException ("Put your error message here.", e);
}
Также можно повторно вызывать исключение при выполнении указанного условия, как показано в следующем примере:
catch (InvalidCastException e)
{
if (e.Data = = null)
{
throw;
}
else
{
// Выполняем некоторые действия здесь
}
}
В блоке try инициализируйте только те переменные, которые в нем объявлены. В противном случае до завершения выполнения блока может возникнуть исключение. Например, в следующем примере кода переменная n инициализируется внутри блока try. Попытка использовать данную переменную вне этого блока try в инструкции Write(n) приведет к ошибке компилятора.
static void Main()
{
int n;
try
{
// Переменная n не описана в try, поэтому
// не надо инициализировать в try …
n = 123;
}
catch
{
}
// … так как в случае возникновения исключения здесь
//переменная n не будет инициализирована.
Console.Write(n);
4. Классы и технологии .NET
4.1. Коллекции
Коллекция в C# – совокупность объектов. В среде .NET Framework имеется немало интерфейсов и классов, в которых определяются и реализуются различные типы коллекций. Коллекции упрощают решение различных задач программирования, требующих сложных структур данных. К ним относятся списки, стеки, динамические массивы, очереди, хеш-таблицы.
Главное преимущество коллекций заключается в том, что они стандартизируют обработку групп объектов в программе. В среде .NET Framework поддерживаются четыре типа коллекций: необобщенные, специальные, с поразрядной организацией и обобщенные.
Первоначально существовали только классы необобщенных коллекций. Затем появились обобщенные версии классов и интерфейсов.
Необобщенные коллекции оперируют данными типа object (базовый класс для всех классов). Таким образом, они служат для хранения объектов любого типа, причем в одной коллекции допускается наличие объектов разных типов. Это их преимущество и недостаток. Это преимущество, если нужно оперировать объектами разных типов в одном списке или если типы объектов заранее неизвестны. Но эти коллекции не обеспечивают типовую безопасность. Пространство имен System.Collections.
Специальные коллекции оперируют данными конкретного типа или же делают это каким-то особым образом. Например, имеется специальные коллекции для символьных строк. Пространство имен System.Collections.Specialized.
Пространство имен System.Collections.Specialized.
Коллекции с поразрядной организацией поддерживают поразрядные операции над двоичными разрядами (И, ИЛИ). К ним относится только класс BitArray. Пространство имен System.Collections.
Обобщенные коллекции обеспечивают обобщенную реализацию нескольких стандартных структур данных, включая связанные списки, стеки, очереди и словари. Такие коллекции являются типизированными. Это означает, что в обобщенной коллекции могут храниться только такие элементы данных, которые совместимы по типу с данной коллекцией. Это исключает случайное несовпадение типов. Пространство имен System.Collections.Generic.
Также имеются классы, поддерживающие создание собственных обобщенных коллекций. Пространство имен System.Collections.ObjectModel.
Основополагающим для всех коллекций является понятие перечислителя, который обеспечивает стандартный способ поочередного доступа к элементам коллекций. В каждой коллекции должна быть реализована обобщенная или необобщенная форма интерфейса IEnumarable, поэтому элементы любого класса коллекции должны быть доступны посредством методов, определенных в интерфейсе IEnumerator или IEnumerator. Для поочередного обращения к элементам коллекции в цикле foreach используется перечислитель.
С перечислителем связано другое средство — итератор.
К обобщенным коллекциям относятся:
— Dictionary — обобщенная коллекция ключей и значений.
— LinkedList — двусвязный список.
— List — последовательный список элементов с динамически изменяемым размером.
— Queue — очередь.
— SortedDictionary — словарь, поддерживаемый в отсортированном порядке пары «ключ/значение».
— SortedSet — коллекция объектов, поддерживаемых в отсортированном порядке без дублирования.
— Stack — стек.
Инициализация коллекций осуществляется следующим образом:
List myList = new List{0,1,2,3,4,5,6,7,8};
Если контейнер управляет коллекцией классов и структур, то можно смешивать синтаксис инициализации объектов с инициализации объектов с синтаксисом инициализации коллекций, создавая некоторый функциональный код:
List m = new List
{
new Point {X=2, Y=3},
new Point {X=6, Y=4},
new Point (Color.Red) {X=2, Y=3}
};
Класс List
Класс List имеет следующие методы:
1. Метод AddRange() – добавление множества элементов в коллекцию за один прием. Метод AddRange принимает один аргумент типа IEnumerable
points.AddRange(new Point[] { new Point {X=5, Y=6}, new Point(5,8) });
2. Метод Insert() – вставка элемента в определенную позицию.
points.Insert(3, new Point(3,5));
Если указывается индекс, превышающий количество элементов в коллекции, то генерируется исключение типа ArgumentOutOfRangeException.
3. Метод InsertRange() – вставка элементов в определенную позицию за один прием. Аналогично AddRange.
4. Доступ к элементам осуществляется с помощью индексатора []. Первый элемент доступен по индексу 0.
Point p = points[0];
4. Проход по элементам коллекции возможен с помощью оператора foreach блягодаря реализации интерфейса IEnumerable:
foreach(Point p in points)
{
Console.WriteLine(p);
}
5. Метод ForEach(), который может использоваться вместо оператора foreach также, объявленный с параметром Action. Action должен быть объявлен как метод с параметром того же типа, что и элемент коллекции и возвращающий void.
Например,
points.ForEach(Console.WriteLine);
points.ForEach(p=>Console.WriteLine(p));
6. Метод RemoveAt() – удаление элемента по индексу.
7. Метод Remove() – удаление элемента по ссылке.
Удаление элемента по ссылке работает быстрее, поскольку при этом не надо выполнять поиск удаляемого элемента по всей коллекции. Метод Remove() сначала ищет удаляемый элемент с помощью метода IndexOf(), а затем использует индекс для удаления элемента.
8. Метод RemoveRange() удаляет множество элементов из коллекции. Первый параметр определяет индекс, начиная с которого располагаются удаляемые элементы, а второй параметр задает количество удаляемых элементов.
points.RemoveRange(3,6);
9. Метод RemoveAll() – удаление всех элементов с некоторыми характеристиками, указанных предикатом.
10. Метод Clear() – удаление всех элементов коллекции.
11. Метод IndexOf(), LastIndexOf(), FindIndex(), FindLastIndex(), Find(), FindLast() – поиск элемента коллекции.
12. Метод IndexOf() – возвращает индекс элемента, указанного в качестве параметра. Также можно указать индекс первого элемента и количество элементов среди которых необходимо осуществлять поиск. Возвращает -1, если элемент не найден.
13. Метод FindIndex() – возвращает индекс элемента с определенными свойствами, заданными предикатом. Также можно указать индекс первого элемента и количество элементов среди которых необходимо осуществлять поиск.
Point points.FindIndex(p=>p.X == 5);
14. Метод Find() – возвращает элемент (а не индекс) с определенными свойствами, заданными предикатом.
15. Метод FindAll() – возвращает все элементы с определенными свойствами, заданными предикатом.
List p = points.FindIndex(p=>p.X > 5);
16. Метод Exists() – проверка существования элемента.
17. Метод Sort() осуществляет сортировку элементов. Метод Sort без параметров позволяет сортировать только объекты, для которых реализован интерфейс IComparable. Причем сортировка осуществляется тем способом, который предусмотрен по умолчанию типом элементов.
Сортировка элементов коллекции способом, который не поддерживается по умолчанию
Для сортировки элементов другим способом, не тем, который поддерживается по умолчанию типом элементов, необходимо использовать интерфейс IComparer для типа элемента коллекции. Интерфейс IComparer определяет метод Compare(), который необходим для сортировки. В реализации этого метода используется метод CompareTo() типов string и int.
public class PointComparer: IComparer
{
public enum CompareType
{
X,
Y,
Name
}
private CompareType compareType;
public PointComparer(CompareType compareType)
{
this.compareType = compareType;
}
public int Compare(Point a, Point b)
{
if(a==null) throw new ArgumentNullException(“a”);
if(b==null) throw new ArgumentNullException(“b”);
int result;
switch(compareType)
{
case CompareType.X: return a.X.CompareTo(b.X);
case CompareType.Y:
result = a.Y.CompareTo(b.Y);
if (result==0) return a.X.CompareTo(b.X);
else return result;
case CompareType.Name: return a.Name.CompareTo(b.Name);
default: throw new ArgumentException(“Недопустимое поле для сравнения”);
}
}
Теперь коллекцию точек можно отсортировать по X, Y или Name.
points.Sort(new PointComparer(PointComparer.CompareType.X));
points.Sort(new PointComparer(PointComparer.CompareType.Y));
points.Sort(new PointComparer(PointComparer.CompareType.Name));
Класс Stack
Класс Stack представляет коллекцию элементов, работающую по алгоритму «последний вошел – первый вышел» (LIFO) и имеет следующие методы:
1. Метод Push() — вставка элемента в стек. Вставка элементов осуществляется в вершину стека, так же как и извлечение.
2. Метод Pop() — извлечение элемента из стека.
3. Метод Peek() — возвращает элемент из вершины стека без его удаления.
При попытке извлечения элемента из пустого стека генерируется исключение InvalidOperationException.
Класс Queue
Класс Queue представляет коллекцию элементов, работающую по алгоритму «первый вошел – первый вышел» (FIFO) и имеет следующие методы:
1. Метод Enqueue () — вставка элемента в очередь. Вставка осуществляется в конец очереди.
2. Метод Dequeue() — извлечение элемента из очереди. Извлечение осуществляетися из начала очереди.
3. Метод Peek() – просмотреть элемент из начала очереди без его удаления.
При попытке извлечения элемента из пустой очереди генерируется исключение InvalidOperationException.
Класс SortedSet
Класс SortedSet удобен тем, что при вставке или удалении элементов он автоматически обеспечивает сортировку элементов в наборе. При создании объекта SortedSet, его конструктору необходимо передать объект, реализующий интерфейс IComparer, который будет информировать о том, как будут сортироваться объекты.
SortedSet points =
new SortedSet ( new PointComparer(PointComparer.CompareType.X) );
Класс ObservableCollection
Класс ObservableCollection — коллекция аналогичная List, но генерирует событие при изменении содержимого коллекции. То есть она информирует внешние объекты о том, что произошло изменение коллекции. Пространство имен System.Collections.ObjectModel.
4.2 Технология LINQ
Введение в LINQ. Актуальность и примеры.
По мере становления платформы .NET Framework и поддерживаемых ею языков С# и VB, стало ясно, что одной из наиболее проблемных областей для разработчиков остается доступ к данным из разных источников.
Проблемы, связанные с базами данных, многочисленны. Первая сложность в том, что взаимодействие с базой данных происходит с помощью строковой команды на языке SQL, а не программно на уровне языка, на котором пишется программа. Это приводит к синтаксическим ошибкам, которые не проявляются вплоть до момента запуска. Неправильные ссылки на поля базы данных тоже не обнаруживаются.
Вместо того чтобы просто добавить больше классов и методов для постепенного восполнения этих недостатков, в Microsoft решили разработать универсальную технологию LINQ (язык встроенных запросов). LINQ — технология Microsoft, предназначенная для поддержки запросов к данным всех типов на уровне языка. Запросы могут выполняться над массивами (в том числе строками) и коллекциями в памяти, базами данных, документами XML и другими данными.
LINQ включает в себя около 50 стандартных операций запросов, разделяемых на 2 большие группы - отложенные операции (выполняются не во время инициализации, а только при их вызове) и не отложенные операции (выполняются сразу).
К отложенным операциям относятся: AsEnumerable, Cast, OfType, Concat, DefaultEmpty, Except, Distinct, Intersect, Union, Empty, Range, Repeat, GroupBy, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, Reverse, Select, SelectMany, Skip, SkipWhile, Take, TakeWhile, Where.
К не отложенным операциям относятся: Aggregate, Average, Min, Max, Count, Sum, ToArray, ToLookup, ToDictionary, ToList, ElementAt, ElementAtOrDefault, First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, Any, All, Contains, SequenceEqual.
На рисунке ниже наглядно показана "градация" операций LINQ:
Запросы LINQ возвращают набор объектов, единственный объект или подмножество полей из объекта или набора объектов. В LINQ этот возвращенный набор называется последовательностью (sequence). Большинство последовательностей LINQ имеют тип IEnumerable, где Т — тип объектов, находящихся в последовательности. Например, если есть последовательность целых чисел, они должны храниться в переменной типа IEnumerable.
Пример 1. Сортировка массива строк таким образом, чтобы числа, хранящиеся в строках, были упорядочены по возрастанию.
string[] numbers = { "40", "2012", "176", "5" };
// Преобразуем массив строк в массив типа int и сортируем по возрастанию,
// используя LINQ
int[] nums = numbers.Select(s => Int32.Parse(s)).OrderBy(s => s).ToArray();
foreach (int n in nums)
Console.Write(n + " ");
Вывод: 5, 40, 176, 2012
Если бы это были по-прежнему строки, то 5 оказалось бы в конце списка, а 176 — в начале.
Пример 2. Сортировка массива строк, таким образом, чтобы строки были упорядочены по возрастанию длины строки. В результате получим массив строк.
var nums = numbers.Select(s => s.Length()).OrderBy(s => s).ToArray();
foreach (var n in nums)
Console.Write(n + " ");
Вывод: 5, 40, 176, 2012
Пример 3. Сортировка массива строк, таким образом, чтобы строки были упорядочены по возрастанию длины строки. В результате получим последовательность IEnumerable.
var nums = numbers.Select(s => s.Length()).OrderBy(s => s);
foreach (var n in nums)
Console.Write(n + " ");
Вывод: 5, 40, 176, 2012
В LINQ-запросах можно использовать SQL-подобный синтаксис и стандартную точечную нотацию С#.
При записи запроса в стандартной точечной нотации не происходит никакой трансформации при компиляции.
Пример 4. Получение названий марок автомобилей длиной менее 6 символов с использованием точечной нотации и синтаксиса запросов.
string[] names = {
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",
"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Obama", "Pierce", "Polk", "Reagan", "Roosevelt",
"Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
// Использование точечной нотации
IEnumerable sequence = names
.Where(n => n.Length < 6)
.Select(n => n);
// Использование синтаксиса запросов
IEnumerable sequence = from n in names
where n.Length < 6
select n;
foreach (string name in sequence)
{
Console.WriteLine("{0}", name);
}
Синтаксис выражений запросов поддерживается только для наиболее распространенных операций запросов: Where, Select, SelectMany, Join, GroupJoin, GroupBy, OrderBy, ThenBy, OrderByDescending и ThenByDescending.
2. Интерфейс IEnumerable
Функциональность LINQ to Objects (LINQ для работы с данными в памяти) обеспечивается интерфейсом IEnumerable.
Если есть переменная типа IEnumerabie, то можно сказать, что имеется последовательность элементов типа Т. Например, IEnumerable означает последовательность строк. Любая переменная, объявленная как IEnumerable для типа T, рассматривается как последовательность типа Т. Последовательность — это термин для обозначения коллекции, реализующей интерфейс IEnumerable.
Большинство стандартных операций запросов представляют собой расширяющие методы в статическом классе System.Linq.Enumerable и прототипированы с IEnumerable в качестве первого аргумента. Поскольку они являются расширяющими методами, предпочтительно вызывать их на переменной типа IEnumerable, что позволяет синтаксис расширяющих методов, а не передавать переменную типа IEnumerable в первом аргументе.
Пример 5. Имеется метод Where, который возвращает последовательность, удовлетворяющую условию. Например, необходимо получить последовательность из элементов массива, в которой останутся только отрицательные числа.
int [] m = {10, -5, -6, -2, 4, -4, 1, 0, -8, 12};
IEnumerable m2 = m.Where(x=>x<0);
Метод Where вызывается на объекте m (массиве), однако не является методом массива, а представляет собой расширяющий метод – статический метод, который может вызываться на объекте, и описан в другом классе.
В качестве аргумента функции указана анонимная функция «X переходит в X<0», которая возвращает величину true, если очередной элемент последовательности меньше нуля и false – в противном случае и представляет собой более короткую запись функции
bool f(int x)
{
return x<0;
}
или
bool f(int x)
{
if (x<0)
return true;
else
return false;
}
Важно помнить, что хотя многие из стандартных операций запросов прототипированы на возврат IEnumerable, и IEnumerable воспринимается как последовательность, на самом деле операции не возвращают последовательность в момент их вызова. То есть выполнение запроса откладывается до перечисления. Вместо этого операции возвращают объект, который не является последовательностью, но при перечислении выдает очередной элемент последовательности. Только во время перечисления возвращенного объекта (например, при выводе на экран или подсчете суммы) запрос выполняется, и выданный элемент помещается в выходную последовательность.
Для облегчения написания перечислителей в язык C# введено специальное ключевое слово yield.
Пример 6. Демонстрация места возникновения исключения при выходе за границы массива.
Вдобавок, поскольку такого рода запросы, возвращающие IEnumerable, являются отложенными, код определения запроса может быть вызван однажды и затем использован многократно, с перечислением его результатов несколько раз. В случае изменения данных при каждом перечислении результатов будут выдаваться разные результаты. Ниже показан пример отложенного запроса, где результат не кэшируется и может изменяться от одного перечисления к другому:
Давайте более подробно рассмотрим, что здесь происходит. Когда вызывается операция Select, возвращается объект, хранящийся в переменной ints типа, реализующего интерфейс IEnumerable. В этой точке запрос в действительности еще не выполняется, но хранится в объекте по имени ints. Другими словами, поскольку запрос еще не выполнен, последовательность целых чисел пока не существует, но этот объект ints знает, как получить последовательность, выполнив присвоенный ему запрос, которым в этом случае является операция Select.
Когда оператор foreach выполняется на ints в первый раз, объект ints производит запрос и получает последовательность по одному элементу за раз.
После этого в исходном массиве целых чисел изменяется один элемент. Затем снова запускается оператор foreach. Это заставляет ints снова выполнить запрос. Поскольку элемент в исходном массиве был изменен, а запрос выполнен снова, т.к. заново запущено перечисление ints, на этот раз возвращается измененный элемент.
Обратите внимание, что несмотря на однократный вызов запроса, результаты двух перечислений отличаются. Это еще одно доказательство того, что запрос является отложенным. Если бы это было не так, то результаты двух перечислений совпали бы.
Если не хотите, чтобы в таких ситуациях результаты отличались, воспользуйтесь одной из операций преобразования, которые не возвращают IEnumerable, так что запрос получается не отложенным. К таким операциям относятся ToArray, ToList, ToDictionary или ToLookup и любые другие не отложенные операции.
Ниже показан тот же код, что и в предыдущем примере, но запрос возвращает не IEnumerable, a List — за счет вызова операции ToList:
// Создать массив целых чисел.
int[] intArray = new int[] { 1, 2, 3 };
List ints = intArray.Select(i => i).ToList();
foreach (int i in ints)
Console.WriteLine(i);
// Изменить элемент, в источнике данных
intArray[0] = 5;
Console.WriteLine("---------");
foreach (int i in ints)
Console.WriteLine(i);
Вывод: 1, 2, 3 в обоих случаях.
Обратите внимание, что результаты, полученные от двух перечислений, одинаковы. Причина в том, что метод ToList не является отложенным, и запрос на самом деле выполняется в тот момент, когда он был вызван.
Отличие между этим примером и предыдущим, где операция Select отложена, заключается в том, что операция ToList является не отложенной. Когда ToList вызывается в операторе запроса, она немедленно перечисляет объект, возвращенный оператором Select, в результате чего весь запрос перестает быть отложенным.
Операция Where
Операция Where используется для фильтрации элементов в последовательность. Операция Where имеет два прототипа.
Первый прототип Where
public static IEnumerable Where(
this IEnumerable source,
Func predicate);
Этот прототип Where принимает входную последовательность и делегат метода-предиката, а возвращает объект, который при перечислении проходит по входной последовательности, выдавая элементы, для которых делегат метода-предиката возвращает true.
Благодаря расширяющим методам нет необходимости передавать первый аргумент в стандартную операцию запроса, первый аргумент которой помечен модификатором — ключевым словом this, при условии, что операция вызывается на объекте того же типа, что у первого аргумента.
При вызове Where передается делегат метода-предиката. Этот метод-предикат должен принимать тип Т в качестве входного, где Т — тип элементов, содержащихся во входной последовательности, и возвращать bool. Операция Where вызовет метод-предикат для каждого элемента входной последовательности и передаст ему этот элемент. Если метод-предикат вернет true, то Where выдаст этот элемент в выходную последовательность Where. Если метод-предикат вернет false, то Where этого не сделает.
Второй прототип Where:
public static IEnumerable Where(
this IEnumerable source,
Func predicate);
Второй прототип Where идентичен первому, но с тем отличием, что он указывает на то, что делегат метода-предиката принимает дополнительный целочисленный аргумент. Этот аргумент будет индексом элемента во входной последовательности.
IEnumerable sequence = cars.Where((p, i) => (i & 1) == 1);
foreach (string s in sequence)
Console.WriteLine(s);
Операция Select
Операция Select используется для создания выходной последовательности одного типа элементов из входной последовательности элементов другого типа. Эти типы не обязательно должны совпадать.
Существуют два прототипа этой операции, которые описаны ниже:
Первый прототип Select
public static IEnumerable Select(
this IEnumerable source,
Func selector);
Этот прототип Select принимает входную последовательность и делегат метода-селектора в качестве входных параметров, а возвращает объект, который при перечислении проходит по входной последовательности и выдает последовательность элементов типа S. Как упоминалось ранее, Т и S могут быть как одного, так и разных типов.
При вызове Select делегат метода-селектора передается в аргументе selector. Метод-селектор должен принимать тип Т в качестве входного, где Т — тип элементов, содержащихся во входной последовательности, и возвращать элемент типа S. Операция Select вызовет метод-селектор для каждого элемента входной последовательности, передав ему этот элемент. Метод-селектор выберет интересующую часть входного элемента, создаст новый элемент — возможно, другого типа (даже анонимного) — и вернет его.
Второй прототип Select
public static IEnumerable Select(
this IEnumerable source,
Func selector);
В этом прототипе операции Select методу-селектору передается дополнительный целочисленный параметр. Это индекс, начинающийся с нуля, входного элемента во входной последовательности.
Пример вызова первого прототипа показан ниже:
string[] cars = { "Nissan", "Aston Martin", "Chevrolet", "Alfa Romeo", "Chrysler", "Dodge", "BMW", "Ferrari", "Audi", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
IEnumerable sequence = cars.Select(p => p.Length);
foreach (int i in sequence)
Console.Write(i + " ");
Обратите внимание, что метод-селектор передается через лямбда-выражение. В данном случае лямбда-выражение вернет длину каждого элемента из входной последовательности. Также отметьте, что, хотя тип входных элементов — строка, тип выходных элементов — int.
Это простой пример, потому что никакого класса не генерируется. Ниже приведена более интересная демонстрация использования первого прототипа:
var sequence = cars.Select(p => new { p, p.Length });
foreach (var i in sequence)
Console.WriteLine(i);
Обратите внимание, что лямбда-выражение создает экземпляр нового анонимного типа. Компилятор динамически сгенерирует анонимный тип, который будет содержать string р и int p.Length, и метод-селектор вернет этот вновь созданный объект. Поскольку тип возвращаемого элемента является анонимным, выходная последовательность присваивается переменной, указанной с помощью ключевого слова var.
С этим кодом связана одна проблема: управлять именами членов динамически сгенерированного анонимного класса нельзя. Однако, благодаря средству инициализации объектов С#, можно написать лямбда-выражение и задать имена членов анонимного класса, как показано ниже:
var carObj = cars.Select(p => new { LastName = p, Length = p.Length });
foreach (var i in carObj)
Console.WriteLine("Автомобиль {0} имеет длину {1} символов", i.LastName, i.Length);
Для примера второго прототипа будет добавлен индекс, который передается методу-селектору, в тип элемента выходной последовательности:
var carObj = cars.Select((p, i) => new { Index = i + 1, LastName = p });
foreach (var i in carObj)
Console.WriteLine( i.Index + ". " + i.LastName);
Этот пример выводит номер индекса плюс единица, за которым следует имя. Код производит следующий результат:
Операция Take возвращает указанное количество элементов из входной последовательности, начиная с ее начала.
Ниже показан пример использования операции Take:
string[] cars = { "Nissan", "Aston Martin", "Chevrolet", "Alfa Romeo", "Chrysler", "Dodge", "BMW", "Ferrari", "Audi", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
IEnumerable auto = cars.Take(5);
foreach (string str in auto)
Console.WriteLine(str);
Этот код вернет первые пять элементов из массива cars.
Операция TakeWhile
Операция TakeWhile возвращает элементы из входной последовательности, пока истинно некоторое условие, начиная с начала последовательности. Остальные входные элементы пропускаются.
Ниже приведен пример вызова первого прототипа:
string[] cars = { "Nissan", "Chevrolet", "Alfa Romeo", "Chrysler", "Dodge", "BMW", "Aston Martin", "Ferrari", "Audi", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
IEnumerable auto = cars.TakeWhile(s => s.Length < 12);
foreach (string str in auto)
Console.WriteLine(str);
В приведенном коде входные элементы извлекаются до тех пор, пока их длина не превышает 11 символов.
Элемент, который заставил операцию TakeWhile прекратить обработку входной последовательности — Aston Martin.
Рассмотрим пример второго прототипа операции TakeWhile:
IEnumerable auto = cars.TakeWhile((s, i) => s.Length < 12 && i < 5);
foreach (string str in auto)
Console.WriteLine(str);
Код в этом примере прекращает выполнение, когда входной элемент превысит 11 символов в длину или когда будет достигнут шестой элемент — в зависимости от того, что произойдет раньше.
Операция Skip
Операция Skip пропускает указанное количество элементов из входной последовательности, начиная с ее начала, и выводит остальные.
Операция Skip получает входную последовательность и целое число count, задающее количество входных элементов, которое должно быть пропущено, и возвращает объект, который при перечислении пропускает первые count элементов и выводит все последующие элементы.
Ниже приведен пример вызова операции Skip:
string[] cars = { "Nissan", "Chevrolet", "Alfa Romeo", "Chrysler", "Dodge", "BMW", "Aston Martin", "Ferrari", "Audi", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
IEnumerable auto = cars.Skip(5);
foreach (string str in auto)
Console.WriteLine(str);
В данном примере пропускаются первые 5 элементов. Обратите внимание, что в следующем выводе действительно пропущены первые пять элементов входной последовательности:
Операция SkipWhile
Операция SkipWhile обрабатывает входную последовательность, пропуская элементы до тех пор, пока условие истинно, а затем выводит остальные в выходную последовательность. У операции SkipWhile есть два прототипа, описанные ниже:
public static IEnumerable SkipWhile(
this IEnumerable source,
Func predicate);
Операция SkipWhile принимает входную последовательность и делегат метода-предиката, а возвращает объект, который при перечислении пропускает элементы до тех пор, пока метод-предикат возвращает true. Как только метод-предикат вернет false, операция SkipWhile начинает вывод всех прочих элементов. Метод-предикат принимает элементы входной последовательности по одному и возвращает признак того, должен ли элемент быть пропущен из входной последовательности.
Этот прототип подобен первому во всем, за исключением дополнительного параметра — индекса элемента из входной последовательности, начинающегося с нуля.
Ниже приведен пример вызова первого прототипа операции SkipWhile:
string[] cars = { "Alfa Romeo", "Aston Martin", "Audi", "Nissan", "Chevrolet", "Chrysler", "Dodge", "BMW", "Ferrari", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
IEnumerable auto = cars.SkipWhile(s => s.StartsWith("A"));
foreach (string str in auto)
Console.WriteLine(str);
В этом примере метод SkipWhile должен пропускать элементы до тех пор, пока они начинаются с буквы "А". Все остальные элементы выдаются в выходную последовательность.
Теперь рассмотрим пример использования второго прототипа SkipWhile:
string[] cars = { "Alfa Romeo", "Aston Martin", "Audi", "Nissan", "Chevrolet", "Chrysler", "Dodge", "BMW", "Ferrari", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
IEnumerable auto = cars.SkipWhile((s, i) => s.StartsWith("A") && i < 10);
foreach (string str in auto)
Console.WriteLine(str);
В данном примере входные элементы пропускаются до тех пор, пока они начинаются с буквы "А" или пока не будет достигнут десятый элемент. Остальные элементы выдаются в выходную последовательность.
Пропуск элементов был прекращен, как только встретился элемент Nissan, поскольку он начинается с N, хотя его индексом является 3.
Concat
Операция Concat соединяет две входные последовательности и выдает одну выходную последовательность.
В этом прототипе две последовательности одного типа Т — first и second — являются входными. Возвращается объект, который при перечислении проходит по первой последовательности, выдавая каждый ее элемент в выходную последовательности за которым начинается перечисление второй входной последовательности с выдачей каждого ее элемента в ту же выходную последовательность.
Ниже приведен пример использования операции Concat, а также операций Take и Skip:
string[] cars = { "Alfa Romeo", "Aston Martin", "Audi", "Nissan", "Chevrolet", "Chrysler", "Dodge", "BMW", "Ferrari", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
IEnumerable auto = cars.Take(5).Concat(cars.Skip(5));
foreach (string str in auto)
Console.WriteLine(str);
Этот код берет пять первых членов из входной последовательности cars и соединяет со всеми, кроме первых пяти входных элементов из последовательности cars.
Альтернативный подход к соединению предусматривает вызов операции SelectMany на массиве последовательностей, как показано ниже:
string[] cars = { "Alfa Romeo", "Aston Martin", "Audi", "Nissan", "Chevrolet", "Chrysler", "Dodge", "BMW", "Ferrari", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
IEnumerable auto = new[] {
cars.Take(5),
cars.Skip(5)}
.SelectMany(s => s);
В данном примере создается экземпляр массива, состоящего из двух последовательностей: одной, созданной вызовом операции Take на входной последовательности, и другой, созданной вызовом операции Skip на входной последовательности. Обратите внимание, что это подобно предыдущему примеру во всем, за исключением того, что на массиве последовательностей вызывается операция SelectMany. С учетом того, что операция Concat позволяет объединять только две последовательности, при наличии массива последовательностей продемонстрированный прием может оказаться удобнее.
Результат получается тем же, что и при использовании операции Concat.
OrderBy и OrderByDescending
Операции упорядочивания позволяют выстраивать входные последовательности в определенном порядке. Важно отметить, что и OrderBy, и OrderByDescending требуют входной последовательности типа IEnumerable и возвращают последовательность типа IOrderedEnumerable. Передавать операциям OrderBy и OrderByDescending в качестве входной последовательности IOrderedEnumerable нельзя. Причина в том, что последующие вызовы операций OrderBy и OrderByDescending не принимают во внимание порядок, созданный предыдущими вызовами OrderBy и OrderByDescending. Это значит, что передавать последовательность, возвращенную из OrderBy либо OrderByDescending, в последующий вызов операции OrderBy или OrderByDescending не имеет смысла.
Если требуется большая степень упорядочивания, чем возможно достичь с помощью одиночного вызова операции OrderBy или OrderByDescending, необходимо последовательно вызывать операции ThenBy или ThenByDescending, речь о которых пойдет далее.
OrderBy
Операция OrderBy позволяет упорядочить входную последовательность на основе метода keySelector, который возвращает значение ключа для каждого входного элемента. Упорядоченная выходная последовательность IOrderedEnumerable выдается в порядке возрастания на основе значений возвращенных ключей.
Сортировка, выполненная операцией OrderBy, определена как неустойчивая. Это значит, что она не сохраняет входной порядок элементов. Если два входных элемента поступают в операцию OrderBy в определенном порядке, и значения ключей этих двух элементов совпадают, их расположение в выходной последовательности может остаться прежним или поменяться, причем ни то, ни другое не гарантируется. Даже если все выглядит нормально, поскольку порядок определен как неустойчивый, всегда следует исходить из этого. Это значит, что никогда нельзя полагаться на порядок элементов, поступающих из операций OrderBy или OrderByDescending, для любого поля кроме указанного в вызове метода. Сохранение любого порядка, который существует в последовательности, передаваемой любой из этих операций, не может гарантироваться.
Операции ThenBy и ThenByDescending
Вызовы ThenBy и ThenByDescending могут соединяться в цепочку, т.к. они принимают в качестве входной последовательности IOrderedEnumerable и возвращают в качестве выходной последовательности тоже IOrderedEnumerable.
Например, следующая последовательность вызовов не разрешена:
inputSequence.OrderBy(s => s.LastName).OrderBy(s => s.FirstName)...
Вместо нее должна использоваться такая цепочка:
inputSequence.OrderBy(s => s.LastName).ThenBy(s => s.FirstName).
Операция ThenBy позволяет упорядочивать входную последовательность типа IOrderedEnumerable на основе метода keySelector, который возвращает значение ключа. В результате выдается упорядоченная последовательность типа IOrderedEnumerable.
В отличие от большинства операций отложенных запросов LINQ to Objects, операции ThenBy и ThenByDescending принимают другой тип входных последовательностей — IOrderedEnumerable. Это значит, что сначала должна быть вызвана операция OrderBy или OrderByDescending для создания последовательности IOrderedEnumerable, на которой можно затем вызывать операции ThenBy и ThenByDescending.
Сортировка, выполняемая операцией ThenBy, является устойчивой. Другими словами, она сохраняет входной порядок элементов с эквивалентными ключами. Если два входных элемента поступили в операцию ThenBy в определенном порядке, и ключевое значение обоих элементов одинаково, то порядок тех же выходных элементов гарантированно сохранится. В отличие от OrderBy и OrderByDescending, операции ThenBy и ThenByDescending выполняют устойчивую сортировку.
Ниже показан пример использования первого прототипа:
string[] cars = { "Alfa Romeo", "Aston Martin", "Audi", "Nissan", "Chevrolet", "Chrysler", "Dodge", "BMW", "Ferrari", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
IEnumerable auto = cars.OrderBy(s => s.Length).ThenBy(s => s);
foreach (string str in auto)
Console.WriteLine(str);
Этот код сначала упорядочивает элементы по их длине, в данном случае — длине названия автомобиля. Затем упорядочивает по самому элементу. В результате получается список названий, отсортированный по длине от меньшей к большей (по возрастанию), а затем — по имени в алфавитном порядке:
Операции ToArray и ToList
Следующие операции преобразования предоставляют простой и удобный способ преобразования последовательностей в другие типы коллекций.
Операция ToArray создает массив типа Т из входной последовательности типа T. Эта операция имеет один прототип, описанный ниже:
public static Т[] ToArray( this IEnumerable source);
Эта операция берет входную последовательность source с элементами типа Т и возвращает массив элементов типа Т.
Операция ToList создает List типа Т из входной последовательности типа Т. Эта операция имеет один прототип, описанный ниже:
public static List ToList(
this IEnumerable source);
Эта операция часто полезна для кэширования последовательности, чтобы она не могла измениться перед ее перечислением. Также, поскольку эта операция не является отложенной и выполняется немедленно, множество перечислений на созданном списке List всегда видят одинаковые данные.
Операции Any, All и Contains
Операция Any возвращает true, если любой из элементов входной последовательности отвечает условию.
Операция All возвращает true, если каждый элемент входной последовательности отвечает условию.
all = cars.All(s => s.Length > 2);
Console.WriteLine("Правда ли, что все элементы коллекции Cars длинее 2х символов: " + all);
Операция Contains возвращает true, если любой элемент входной последовательности соответствует указанному значению.
Для демонстрации работы первого прототипа, рассмотрим следующий пример:
string[] cars = { "Alfa Romeo", "Aston Martin", "Audi", "Nissan", "Chevrolet", "Chrysler", "Dodge", "BMW", "Ferrari", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
Console.WriteLine("Операция Contains\n\n********\n");
bool contains = cars.Contains("Jaguar");
Console.WriteLine("Наличие \"Jaguar\" в массиве: " + contains);
contains = cars.Contains("BMW");
Console.WriteLine("Наличие \"BMW\" в массиве: " + contains);
В данном пример проверяется наличие слов "Jaguar" и "BMW" в исходном массиве Cars с помощью первого прототипа операции Cars.
Операции Count, LongCount и Sum
Операция Count возвращает количество элементов во входной последовательности.
Операция LongCount возвращает количество элементов входной последовательности как значение типа long.
Операция Sum возвращает сумму числовых значений, содержащихся в элементах последовательности. Эта операция имеет два прототипа, описанные ниже:
long optionsSum = options.Sum(o => o.optionsCount);
Console.WriteLine("Сумма опционов сотрудников: {0}", optionsSum);
Операции Min и Max
Операция Min возвращает минимальное значение входной последовательности. Эта операция имеет четыре прототипа, которые описаны ниже:
Первый прототип операции Min возвращает элемент с минимальным числовым значением из входной последовательности source.
Второй прототип операции Min ведет себя подобно первому, за исключением того, что он предназначен для нечисловых типов.
public static Т Min(
this IEnumerable source);
Операция Max возвращает максимальное значение из входной последовательности.
Операции Average и Aggregate
Операция Average возвращает среднее арифметическое числовых значений элементов входной последовательности.
Операция Aggregate выполняет указанную пользователем функцию на каждом элементе входной последовательности, передавая значение, возвращенное этой функцией для предыдущего элемента, и возвращая ее значение для последнего элемента.
int N = 5;
int agg = Enumerable
.Range(1, N)
.Aggregate((av, e) => av * e);
Console.WriteLine("{0}! = {1}",N,agg);
В этом коде генерируется последовательность, содержащая целые числа от 1 до 5, для чего используется операция Range. Затем вызывается операция Aggregate, которой передается лямбда-выражение, умножающее переданное агрегатное значение на сам переданный элемент.
Для деления вещественных чисел 12/6/2/1:
List m = new List(){6,2,1};
var agg = m.Aggregate(12.0, (av, ee) => av / ee);
обратите внимание, что 12.0 – для того, чтобы Аккумулятор был типа float..
Тематика лабораторных работ по дисциплине
«Программирование на языке высокого уровня»
1. Объектно-ориентированное программирование
2. Коллекции
3. Технология LINQ