- •Розділ 1. Основи платформи .Net
- •1.1. Основи платформи .Net
- •1.2. Загальні відомості об'єктно-орієнтованого програмування
- •1.3. Середовище Visual Studio .Net
- •1.4 Консольні додатки
- •Розділ 2. Основні поняття мови
- •2.1. Склад мови
- •2.1.1 Алфавіт і лексеми
- •2.1.2. Ідентифікатори і ключові слова
- •2.1.3. Знаки операцій і роздільники
- •2.1.4. Літерали
- •Константи в с#
- •Суфікси цілих і дійсних констант
- •Управляючі послідовності у с#
- •2.1.5. Коментарі
- •2.2. Типи даних
- •2.2.1. Класифікація типів
- •2.2.2. Типи літералів
- •2.2.3. Типи-значення і посилальні типи
- •2.2.4. Упаковка і розпаковування
- •2.3. Рекомендації по програмуванню
- •Розділ 3. Змінні, іменовані константи, операції і вирази
- •3.1. Змінні і іменовані константи
- •3.2. Операції і вирази
- •3.2.1. Перетворення вбудованих арифметичних типів-значень
- •3.2.2. Введення у виключення
- •3.2.3. Основні операції с#
- •Операнд_1 ? операнд_2 : операнд_3.
- •3.3. Лінійні програми (програмування лінійних обчислювальних процесів)
- •3.3.1. Просте введення-виведення даних
- •3.3.2. Математичні функції - клас Math
- •Розділ 4. Оператори
- •4.1. Вирази, блоки
- •4.2. Оператори розгалуження
- •4.2.1. Умовний оператор if
- •4.2.1. Умовний оператор switch
- •4.3. Оператори циклу
- •4.3.1. Цикл з передумовою while
- •4.3.2. Цикл з постумовою do
- •4.3.3. Цикл з параметром for
- •For ( ініціалізація; вираз; модифікації ) оператор;
- •4.3.4. Цикл перебору foreach
- •4.3.5. Рекомендації по вибору оператора циклу
- •4.4. Обробка виняткових ситуацій
- •4.4.1. Оператор try
- •Try блок [ блоки catch ] [ блок finally ]
- •4.4.2. Оператор throw
- •Throw [ вираз ];
- •4.4.3. Клас Exception
- •4.4.4. Оператори checked и unchecked
- •4.5. Рекомендації по програмуванню
- •Розділ 5. Класи: основні поняття
- •5.1. Привласнення і порівняння об'єктів
- •5.2. Дані: поля і константи
- •5.3. Методи
- •5.3.1. Параметри методів
- •5.3.2. Параметри-значення
- •5.3.3. Параметри-посилання
- •5.3.4. Вихідні параметри
- •5.4. Ключове слово this
- •5.5. Конструктори
- •5.6. Властивості
- •[ Атрибути ] [ специфікатори ] тип ім’я_властивості
- •[ Get код_доступа ] [ set код_доступа ]
- •5.7. Рекомендації по програмуванню
- •Розділ 6. Масиви і рядки
- •6.1. Одновимірні масиви
- •6.2. Прямокутні масиви
- •6.3. Ступінчасті масиви
- •6.4. Клас System.Array
- •6.5. Клас Random
- •6.6. Оператор foreach
- •6.7. Масиви об’єктів
- •6.8. Символи і рядки
- •6.8.1. Символи
- •6.8.2. Масиви символів
- •6.8.3. Рядки типу string
- •6.8.4. Форматування рядків
- •6.8.5. Рядки типу StringBuilder
- •6.9. Рекомендації з програмування
- •Розділ 7. Класи: подробиці
- •7.1. Перевантаження методів
- •7.2. Рекурсивні методи
- •7.3. Методи із змінною кількістю аргументів
- •7.4. Метод Main
- •7.5. Індексатори
- •7.6. Операції класу
- •7.6.1. Унарні операції
- •7.6.2. Бінарні операції
- •7.6.3. Операції перетворення типу
- •7.7. Деструктор
- •7.8. Вкладені типи
- •7.9. Рекомендації по програмуванню
- •Розділ 8. Ієрархії класів
- •8.1. Спадкоємство
- •8.2. Віртуальні методи
- •8.3. Абстрактні класи
- •8.4. Безплідні класи
- •8.5. Клас object
- •8.6. Рекомендації по програмуванню
- •Розділ 9. Інтерфейси і структурні типи
- •9.1. Синтаксис інтерфейсу
- •9.2. Реалізація інтерфейсу
- •9.3. Робота з об'єктами через інтерфейси. Операції is і as
- •9.4. Інтерфейси і спадкоємство
- •9.5. Стандартні інтерфейси .Net
- •9.5.1. Порівняння об'єктів (інтерфейс iComparable)
- •9.5.2 Сортування по різних критеріях (інтерфейс iComparer)
- •9.5.3 Перевантаження операцій відношення
- •9.5.4. Клонування об'єктів (інтерфейс iСloneable)
- •9.5.5. Перебір об'єктів (інтерфейс iEnumerable) і ітератори
- •9.6. Структури
- •9.7. Перелічення
- •9.7.1 Операції з переліченнями
- •9.7.2. Базовий клас System.Enum
- •9.8. Рекомендації по програмуванню
- •Розділ 10. Делегати, події і потоки виконання
- •10.1. Делегати
- •10.1.1. Опис делегатів
- •10.1.2. Використання делегатів
- •10.1.3. Патерн “спостерігач”
- •10.1.4. Операції
- •10.1.5. Передача делегатів в методи
- •10.1.6. Обробка виключень при виклику делегатів
- •10.3. Багатопотокові додатки
- •10.3.1. Клас Thread
- •Lock ( вираз ) блок_операторів
- •10.3.2. Асинхронні делегати
- •10.4. Рекомендації по програмуванню
- •Розділ 11. Робота з файлами
- •11.1. Потоки байтів
- •11.2. Асинхронне уведення-виведення
- •11.3. Потоки символів
- •11.4. Двійкові потоки
- •11.5. Консольне уведення-виведення
- •11.6. Робота з каталогами і файлами
- •11.7. Збереження об'єктів (серіалізація)
- •11.8. Рекомендації по програмуванню
- •Розділ 12. Збірки, бібліотеки, атрибути, директиви
- •12.2. Створення бібліотеки
- •12.3. Рефлексія
- •12.4. Атрибути
- •12.5. Простір імен
- •12.6. Директиви препроцесора
- •# Константний_вираз
- •[ #Elif константний_вираз
- •[ #Elif константний_вираз
- •Розділ 13. Структури даних, колекції і класи-прототипи
- •13.1. Абстрактні структури даних
- •13.2. Простір імен System.Collections
- •13.3. Клас ArrayList
- •13.4. Класи-прототипи
- •13.5. Створення класу-прототипу
- •13.6. Узагальнені методи
- •13.7. Часткові типи
- •13.8. Типи, що обнуляються
- •13.9. Рекомендації по програмуванню
- •Розділ 14. Додаткові засоби с#
- •14.1. Небезпечний код
- •Unsafe блок
- •14.1.1. Синтаксис вказівок
- •14.1.2. Перетворення та ініціалізація вказівок
- •14.1.3. Операції з вказівками
- •14.2. Регулярні вирази
- •14.2.1. Метасимволи
- •14.2.2. Класи бібліотеки .Net для роботи з регулярними виразами
- •14.3. Документування у форматі xml
- •Лабораторні роботи
- •Лабораторна робота 5. Одновимірні масиви
- •Лабораторна робота 6. Двовимірні масиви
- •Лабораторна робота 7. Рядки
- •Лабораторна робота 8. Класи і операції
- •Лабораторна робота 9. Спадкоємство
- •Лабораторна робота 10. Структури
- •Лабораторна робота 11. Інтерфейси і параметризовані колекції
- •Список літератури
- •Додатки Додаток 1. Специфікатори формату для рядків с#
9.5.4. Клонування об'єктів (інтерфейс iСloneable)
Клонування - це створення копії об'єкту. Копія об'єкту називається клоном. Як вам відомо, при привласненні одного об'єкту посилального типу іншому копіюється посилання, а не сам об'єкт (рис. 9.1, а). Якщо необхідно скопіювати в іншу область пам'яті поля об'єкту, можна скористатися методом MemberwiseСlone, який будь-який об'єкт успадковує від класу object. При цьому об'єкти, на які указують поля об'єкту, що у свою чергу є посиланнями, не копіюються (рис. 9.1, б). Це називається поверхневим клонуванням.
Рис. 9.1. Клонування об'єктів
Для створення повністю незалежних об'єктів необхідне глибоке клонування, коли в пам'яті створюється дублікат всього дерева об'єктів, тобто об'єктів, на які посилаються поля об'єкту, поля полів і так далі (рис. 9.1, в). Алгоритм глибокого клонування дуже складний, оскільки вимагає рекурсивного обходу всіх посилань об'єкту і відстежування циклічних залежностей.
Об'єкт, що має власні алгоритми клонування, повинен оголошуватися як спадкоємець інтерфейсу ICloneable і перевизначати його єдиний метод Clone. У лістингу 9.4 приведений приклад створення поверхневої копії об'єкту класу Monster за допомогою методу MemberwiseClone, а також реалізований інтерфейс ICloneable. У демонстраційних цілях в ім'я клона об'єкту додано слово “Клон”.
Метод MemberwiseClone можна викликати тільки з методів класу. Він не може бути викликаний безпосередньо, оскільки оголошений в класі object як захищений (protected).
Лістинг 9.5. Клонування об'єктів
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace examp58
{
class Monster : ICloneable
{
string name;
int health;
int ammo;
public Monster(int health, int ammo, string name)
{
this.health = health;
this.ammo = ammo;
this.name = name;
}
public Monster ShallowClone() // поверхнева копія
{ return (Monster)this.MemberwiseClone(); }
public object Clone() // призначена копія для користувача
{
return new Monster(this.health, this.ammo, "Клон " + this.name);
}
virtual public void Passport()
{
Console.WriteLine("Monster {0} \t health = {1} ammo = {2}",
name, health, ammo);
}
}
class Program
{
static void Main(string[] args)
{
Monster Вася = new Monster(70, 80, "Вася" );
Monster X = Вася;
X.Passport();
Monster Y = Вася.ShallowClone();
Y.Passport();
Monster Z = (Monster)Вася.Clone();
Z.Passport();
}
}
}
Об'єкт X посилається на ту ж область пам'яті, що і об'єкт Вася. Отже, якщо ми внесемо зміни до одного з цих об'єктів, це відіб'ється на іншому. Об'єкти Y і Z, створені шляхом клонування, володіють власними копіями значень полів і незалежні від початкового об'єкту.
9.5.5. Перебір об'єктів (інтерфейс iEnumerable) і ітератори
Оператор foreach є зручним засобом перебору елементів об'єкту. Масиви і всі стандартні колекції бібліотеки .NET дозволяють виконувати такий перебір завдяки тому, що в них реалізовані інтерфейси IEnumerable і IEnumerator. Для застосування оператора foreach до призначеного для користувача типу даних потрібно реалізувати в нім ці інтерфейси. Давайте подивимося, як це робиться.
Інтерфейс IEnumerable (що перераховує) визначає всього один метод - GetEnuraerator, що повертає об'єкт типу IEnumerator (нумератор), який можна використовувати для проглядання елементів об'єкту.
Інтерфейс IEnumerator задає три елементи:
властивість Current, що повертає поточний елемент об'єкту;
метод Movenext, що просуває нумератор на наступний елемент об'єкту;
метод Reset, що встановлює нумератор в початок перегляду.
Цикл foreach використовує ці методи для перебору елементів, з яких складається об'єкт.
Таким чином, якщо потрібно, щоб для перебору елементів класу міг застосовуватися цикл foreach, необхідно реалізувати чотири методи: GetEnumerator, Current, MoveNext і Reset. Наприклад, якщо внутрішні елементи класу організовані в масив, потрібно буде описати закрите поле класу, що зберігає поточний індекс в масиві, в методі Movenext задати зміну цього індексу на 1 з перевіркою виходу за межу масиву, в методі Current - повернення елементу масиву по поточному індексу і так далі.
Це не цікава робота, а виконувати її доводиться часто, тому і були введені засоби, що полегшують виконання перебору в об'єкті, - ітератори. Ітератором є блок коду, задаючий послідовність перебору елементів об'єкту. На кожному проході циклу foreach виконується один крок ітератора, що закінчується видачею чергового значення. Видача значення виконується за допомогою ключового слова yield.
Розглянемо створення ітератора на прикладі лістингу 9.6. Нехай потрібно створити об'єкт, що містить бойову групу екземплярів типу Monster. Для простоти обмежимо максимальну кількість бійців в групі десятьма.
Лістинг 9.6. Клас з ітератором
using System;
using System.Collections;
namespace examp59
{
class Monster
{
string name;
int health;
int ammo;
public Monster( int health, int ammo, string name )
{
this. health = health;
this.ammo = ammo;
this.name = name;
}
public Monster()
{
name = "ЛЮДА";
ammo = 100;
health = 100;
}
public Monster(string name)
{
this.name = name;
ammo = 200;
health = 200;
}
public void Passport()
{
Console.WriteLine(" {0} {1} {2} ", name, ammo, health);
}
}
class Daemon : Monster
{
public int brain;
public Daemon(): base()
{
brain = 1;
}
}
class Stado : IEnumerable // 1
{
private Monster[] mas;
private int n;
public Stado()
{
mas = new Monster[10]; n = 0;
}
public IEnumerator GetEnumerator()
{
for (int i = 0; i < n; ++i)
yield return mas[i]; // 2
}
public IEnumerable Backwards() // у зворотному порядку
{
for (int i = n - 1; i >= 0; --i ) yield return mas[i];
}
public IEnumerable MonstersOnly() // тільки монстри
{
for ( int i = 0; i < n; ++i )
if ( mas [i].GetType().Name == "Monster" ) yield return mas[i];
}
public void Add( Monster m )
{
if ( n >= 10 ) return;
mas[n] = m;
++n;
}
}
class Program
{
static void Main(string[] args)
{
Stado s = new Stado();
s.Add( new Monster());
s.Add( new Monster("Bacя"));
s.Add( new Monster("Петя"));
foreach ( Monster i in s ) i.Passport();
foreach ( Monster i in s.Backwards()) i.Passport();
foreach ( Monster i in s.MonstersOnly()) i.Passport();
}
}
}
Все, що потрібно зробити для підтримки перебору, - вказати, що клас реалізує інтерфейс IEnumerable (оператор 1), і описати ітератор (оператор 2). Доступ до нього може бути здійснений через методи MoveNext і Current інтерфейсу IEnumerator.
За кодом, приведеним в лістингу 9.6, стоїть велика внутрішня робота компілятора. На кожному кроці циклу foreach для ітератора створюється “оболонка” - службовий об'єкт, який запам'ятовує поточний стан ітератора і виконує все необхідне для доступу до елементів об'єкту, що проглядаються. Іншими словами, код, що становить ітератор, не виконується так, як він виглядає - у вигляді безперервної послідовності, а розбитий на окремі ітерації, між якими стан ітератора зберігається.
У лістингу 9.7 приведений приклад ітератора, що перебирає чотири задані рядки.
Лістинг 9.7. Простий ітератор
usingSystem;
using System.Collections;
namespace examp60
{
class Num : IEnumerable
{
public IEnumerator GetEnumerator()
{
yield return "one";
yield return "two";
yield return "three";
yield return "oops";
}
}
class Program
{
static void Main(string[] args)
{
foreach ( string s in new Num() ) Console.WriteLine( s );
}
}
}
Результат роботи програми:
one
two
three
oops
Наступний приклад демонструє перебір значень в заданому діапазоні (від 1 до 5):
using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace examp61
{
class Program
{
public static IEnumerable Count( int from, int to )
{
from = 1;
while ( from <= to ) yield return from++;
}
static void Main(string[] args)
{
foreach (int i in Count(1, 5)) Console.WriteLine(i);
}
}
}
Перевага використання ітераторів полягає в тому, що для одного і того ж класу можна задати різний порядок перебору елементів. У лістингу 9.8 описано дві додаткові стратегії перебору елементів класу Stado, введеного в лістингу 9.6, - перебір в зворотному порядку і вибірка тільки тих об'єктів, які є екземплярами класу Monster (для цього використаний метод отримання типу об'єкту GetType, успадкований від базового класу object).
Лістинг 9.8. Реалізація декількох стратегій перебору
using System;
using System.Collections;
namespace examp62
{
class Monster
{
string name;
int health;
int ammo;
public Monster(int health, int ammo, string name)
{
this.health = health;
this.ammo = ammo;
this.name = name;
}
public Monster()
{
name = "ЛЮДА";
ammo = 100;
health = 100;
}
public Monster(string name)
{
this.name = name;
ammo = 200;
health = 200;
}
public void Passport()
{
Console.WriteLine(" {0} {1} {2} \n", name, ammo, health);
}
}
class Daemon : Monster
{
public int brain;
public Daemon()
: base()
{
brain = 1;
}
}
class Stado : IEnumerable // 1
{
private Monster[] mas;
private int n;
public Stado()
{
mas = new Monster[10]; n = 0;
}
public IEnumerator GetEnumerator()
{
for (int i = 0; i < n; ++i)
yield return mas[i]; // 2
}
public IEnumerable Backwards() // у зворотному порядку
{
for (int i = n - 1; i >= 0; --i) yield return mas[i];
}
public IEnumerable MonstersOnly() // тільки монстри
{
for (int i = 0; i < n; ++i)
if (mas[i].GetType().Name == "Monster") yield return mas[i];
}
public void Add(Monster m)
{
if (n >= 10) return;
mas[n] = m;
++n;
}
}
class Program
{
static void Main(string[] args)
{
Stado s = new Stado();
s.Add( new Monster());
s.Add( new Monster("Bacя"));
s.Add( new Monster("Петя"));
s.Add(new Daemon());
foreach ( Monster i in s ) i.Passport();
foreach ( Monster i in s.Backwards()) i.Passport();
foreach ( Monster i in s.MonstersOnly()) i.Passport();
}
}
}
Блок ітератора синтаксично є звичайним блоком і може зустрічатися в тілі методу, операції або частині get властивості, якщо відповідне повертане значення має тип IEnumerable або IEnumerator.
У тілі блоку ітератора можуть зустрічатися дві конструкції:
yield return формує значення, що видається на черговій ітерації;
yield break сигналізує про завершення ітерації.
Ключове слово yield має спеціальне значення для компілятора тільки в цих конструкціях.
Код блоку ітератора виконується не так, як звичайні блоки. Компілятор формує службовий об'єкт-нумератор, при виклику методу MoveNext якого виконується код блоку ітератора, що видає чергове значення за допомогою ключового слова yield. Наступний виклик методу MoveNext об'єкту-нумератора відновлює виконання блоку ітератора з моменту, на якому він був припинений в попередній раз.