5. Поширені класи .NET
Тут ми наводимо кілька класів платформи .NET, які часто використовуються. Спочатку ми покажемо, як отримати інформацію про кілька сотень доступних класів. Ця довідка є незамінною навіть для досвідченого розробника на C#. Рівень якості довідки (легкість доступу, зрозуміла структура, актуальність інформації тощо) може визначити успіх або провал середовища розробки.
5.1. Пошук довідки щодо класів .NET
Тут ми наводимо кілька вказівок щодо пошуку довідки у Visual Studio.NET
5.1.1. Довідка/Зміст
![]() |
- у [1], виберіть опцію Help/Contents у меню.
- у [2] виберіть опцію Visual C# Express Edition
- у [3] — дерево довідки щодо C#
- у [4], ще одним корисним пунктом є .NET Framework, який надає доступ до всіх класів фреймворку .NET.
Давайте ознайомимося з заголовками розділів довідки C#:
![]() |
- [1]: загальний огляд C#
- [2]: серія прикладів щодо окремих аспектів C#
- [3]: курс з C# — міг би з успіхом замінити цей документ…
![]() |
- [4]: для детального вивчення C#
- [5]: корисно для розробників на C++ або Java. Допомагає уникнути деяких підводних каменів.
- [6]: якщо ви шукаєте приклади, можете почати звідси.
![]() |
- [7]: що потрібно знати для створення графічних інтерфейсів
- [8]: як ефективніше використовувати IDE Visual Studio Express
![]() |
- [9]: SQL Server Express 2005 — це якісний SGBD, що розповсюджується безкоштовно. Ми будемо використовувати його в цьому курсі.
Довідка з C# — це лише частина того, що потрібно розробнику. Інша частина — це довідка щодо сотень класів фреймворку .NET, які полегшать йому роботу.
![]() |
- [1]: вибираємо довідку щодо фреймворку .NET
- [2]: довідка знаходиться у гілці .NET Framework SDK
- [3]: гілка «.NET Framework Class Library» містить усі класи «.NET» відповідно до простору імен, до якого вони належать
- [4]: простір імен System, який найчастіше використовувався у прикладах попередніх розділів
![]() |
- [5]: у просторі імен System, приклад — у даному випадку структура DateTime
![]() |
- [6]: довідка щодо структури DateTime
5.1.2. Help/Index/Search
Довідка, що надається MSDN, є надзвичайно обширною, і іноді важко зорієнтуватися, де саме шукати потрібну інформацію. У такому випадку можна скористатися індексом довідки:
![]() |
- у [1], скористайтеся опцією [Help/Index], якщо вікно довідки ще не відкрито; інакше скористайтеся [2] у вже відкритому вікні довідки.
- в [3] вкажіть область, в якій має відбуватися пошук
- у [4] вкажіть, що саме ви шукаєте, у даному випадку — клас
- у [5] — відповідь
Ще один спосіб пошуку допомоги — скористатися функцією пошуку в довідці:
![]() |
- у [1], скористайтеся опцією [Help/Search], якщо вікно довідки ще не відкрито; інакше скористайтеся [2] у вже відкритому вікні довідки.
- на [3], вкажіть, що саме шукаєте
- у [4] — відфільтрувати області пошуку
![]() |
- у [5] — відповідь у вигляді різних тем, у яких було знайдено шуканий текст.
5.2. Символьні ланцюжки
5.2.1. Клас System.String
![]() | ![]() | ![]() |
Клас System.String ідентичний простому типу string. Він має багато властивостей і методів. Ось деякі з них:
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
Слід звернути увагу на важливий момент: коли метод повертає рядок, цей рядок відрізняється від того, до якого було застосовано метод. Так, S1.Trim() повертає рядок S2, а S1 і S2 — це два різні рядки.
Рядок C можна розглядати як масив символів. Отже
- C[i] — це i-й символ у C
- C.Length — це кількість символів у C
Розглянемо такий приклад:
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
string uneChaine = "l'oiseau vole au-dessus des nuages";
affiche("uneChaine=" + uneChaine);
affiche("uneChaine.Length=" + uneChaine.Length);
affiche("chaine[10]=" + uneChaine[10]);
affiche("uneChaine.IndexOf(\"vole\")=" + uneChaine.IndexOf("vole"));
affiche("uneChaine.IndexOf(\"x\")=" + uneChaine.IndexOf("x"));
affiche("uneChaine.LastIndexOf('a')=" + uneChaine.LastIndexOf('a'));
affiche("uneChaine.LastIndexOf('x')=" + uneChaine.LastIndexOf('x'));
affiche("uneChaine.Substring(4,7)=" + uneChaine.Substring(4, 7));
affiche("uneChaine.ToUpper()=" + uneChaine.ToUpper());
affiche("uneChaine.ToLower()=" + uneChaine.ToLower());
affiche("uneChaine.Replace('a','A')=" + uneChaine.Replace('a', 'A'));
string[] champs = uneChaine.Split(null);
for (int i = 0; i < champs.Length; i++) {
affiche("champs[" + i + "]=[" + champs[i] + "]");
}//for
affiche("Join(\":\",champs)=" + System.String.Join(":", champs));
affiche("(\" abc \").Trim()=[" + " abc ".Trim() + "]");
}//Головна
public static void affiche(string msg) {
// виводить повідомлення
Console.WriteLine(msg);
}//відображає
}//клас
}//простір імен
Виконання дає такі результати:
Розглянемо ще один приклад:
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// рядок для аналізу
string ligne = "un:deux::trois:";
// роздільники полів
char[] séparateurs = new char[] { ':' };
// розділення
string[] champs = ligne.Split(séparateurs);
for (int i = 0; i < champs.Length; i++) {
Console.WriteLine("Champs[" + i + "]=" + champs[i]);
}
// об'єднання
Console.WriteLine("join=[" + System.String.Join(":", champs) + "]");
}
}
}
та результати виконання:
Метод Split класу String дозволяє розмістити елементи символьного рядка в масиві. Визначення методу Split, що використовується тут, є таким:
public string[] Split(char[] separator);
таблиця символів. Ці символи позначають знаки, що використовуються для розділення полів у рядку. Так, якщо рядок має вигляд «champ1, champ2, champ3"», можна використовувати separator=new char[] {','}. Якщо роздільником є послідовність пробілів, слід використовувати separator=null. | |
масив рядків, де кожен елемент масиву є полем рядка. |
Метод Join є статичним методом класу String:
public static string Join(string separator, string[] value);
масив символьних рядків | |
рядок, який слугуватиме роздільником полів | |
рядок, утворений шляхом об'єднання елементів масиву value, розділених рядком separator. |
5.2.2. Клас System.Text.StringBuilder
![]() | ![]() | ![]() |
Раніше ми зазначали, що методи класу String, застосовані до символьного рядка S1, повертають інший рядок S2. Клас System.Text.StringBuilder дозволяє оперувати S1 без необхідності створювати рядок S2. Це підвищує продуктивність, запобігаючи надмірному створенню рядків з дуже коротким терміном існування.
Клас підтримує різні конструктори:
| |
|
Об'єкт StringBuilder працює з блоками символів capacité для зберігання базового рядка. За замовчуванням capacité дорівнює 16. Третій конструктор, наведений вище, дозволяє вказати розмір блоків. Кількість блоків символів capacité, необхідних для зберігання рядка S, автоматично регулюється класом StringBuilder. Існують конструктори для встановлення максимальної кількості символів в об’єкті StringBuilder. За замовчуванням ця максимальна ємність становить 2 147 483 647.
Ось приклад, що ілюструє це поняття capacité:
using System.Text;
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// str
StringBuilder str = new StringBuilder("test");
Console.WriteLine("taille={0}, capacité={1}", str.Length, str.Capacity);
for (int i = 0; i < 10; i++) {
str.Append("test");
Console.WriteLine("taille={0}, capacité={1}", str.Length, str.Capacity);
}
// str2
StringBuilder str2 = new StringBuilder("test",10);
Console.WriteLine("taille={0}, capacité={1}", str2.Length, str2.Capacity);
for (int i = 0; i < 10; i++) {
str2.Append("test");
Console.WriteLine("taille={0}, capacité={1}", str2.Length, str2.Capacity);
}
}
}
}
- рядок 7: створення об’єкта StringBuilder із розміром блоку 16 символів
- рядок 8: str.Length — це поточна кількість символів у рядку str. str.Capacity — це кількість символів, яку може вмістити поточний рядок str до перерозподілу нового блоку.
- рядок 10: str.Append(String S) дозволяє об'єднати рядок S типу String з рядком str типу StringBuilder.
- рядок 14: створення об’єкта StringBuilder із розміром блоку 10 символів
Результат виконання:
Ці результати показують, що клас дотримується власного алгоритму для виділення нових блоків, коли його ємність є недостатньою:
- рядки 4–5: збільшення ємності на 16 символів
- рядки 8–9: збільшення ємності на 32 символи, хоча вистачило б 16.
Ось деякі методи класу:
| |
| |
| |
| |
|
Ось приклад:
using System.Text;
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// str3
StringBuilder str3 = new StringBuilder("test");
Console.WriteLine(str3.Append("abCD").Insert(2, "xyZT").Remove(0, 2).Replace("xy", "XY"));
}
}
}
та його результати:
5.3. Масиви
Масиви походять від класу Array:
![]() | ![]() | ![]() |
Клас Array має різні методи для сортування масиву, пошуку елемента в масиві, зміни розміру масиву тощо. Ми розглянемо деякі властивості та методи цього класу. Майже всі вони перевантажені, c.a.d, тобто існують у різних варіантах. Усі масиви успадковують їх.
Властивості
Методи
Наступна програма ілюструє використання деяких методів класу Array:
using System;
namespace Chap3 {
class Program {
// тип пошуку
enum TypeRecherche { linéaire, dichotomique };
// основний метод
static void Main(string[] args) {
// читання елементів масиву, введених з клавіатури
double[] éléments;
Saisie(out éléments);
// виведення несортованого масиву
Affiche("Tableau non trié", éléments);
// Лінійний пошук у несортованому масиві
Recherche(éléments, TypeRecherche.linéaire);
// сортування масиву
Array.Sort(éléments);
// виведення відсортованого масиву
Affiche("Tableau trié", éléments);
// Діхотомічний пошук у відсортованому масиві
Recherche(éléments, TypeRecherche.dichotomique);
}
// Введення значень елементів масиву
// елементи: посилання на масив, створений методом
static void Saisie(out double[] éléments) {
bool terminé = false;
string réponse;
bool erreur;
double élément = 0;
int i = 0;
// Спочатку масив не існує
éléments = null;
// цикл введення елементів масиву
while (!terminé) {
// запитання
Console.Write("Elément (réel) " + i + " du tableau (rien pour terminer) : ");
// зчитування відповіді
réponse = Console.ReadLine().Trim();
// завершення введення, якщо рядок порожній
if (réponse.Equals(""))
break;
// перевірка введених даних
try {
élément = Double.Parse(réponse);
erreur = false;
} catch {
Console.Error.WriteLine("Saisie incorrecte, recommencez");
erreur = true;
}//try-catch
// якщо помилки немає
if (!erreur) {
// ще один елемент у масиві
i += 1;
// зміна розміру масиву для розміщення нового елемента
Array.Resize(ref éléments, i);
// вставлення нового елемента
éléments[i - 1] = élément;
}
}//while
}
// загальний метод для відображення елементів масиву
static void Affiche<T>(string texte, T[] éléments) {
Console.WriteLine(texte.PadRight(50, '-'));
foreach (T élément in éléments) {
Console.WriteLine(élément);
}
}
// пошук елемента в масиві
// елементи: масив дійсних чисел
// TypeRecherche: дихотомічний або лінійний
static void Recherche(double[] éléments, TypeRecherche type) {
// Пошук
bool terminé = false;
string réponse = null;
double élément = 0;
bool erreur = false;
int i = 0;
while (!terminé) {
// питання
Console.WriteLine("Elément cherché (rien pour arrêter) : ");
// читання-перевірка відповіді
réponse = Console.ReadLine().Trim();
// закінчено?
if (réponse.Equals(""))
break;
// перевірка
try {
élément = Double.Parse(réponse);
erreur = false;
} catch {
Console.WriteLine("Erreur, recommencez...");
erreur = true;
}//try-catch
// якщо немає помилки
if (!erreur) {
// шукаємо елемент у масиві
if (type == TypeRecherche.dichotomique)
// бінарний пошук
i = Array.BinarySearch(éléments, élément);
else
// лінійний пошук
i = Array.IndexOf(éléments, élément);
// Виведення відповіді
if (i >= 0)
Console.WriteLine("Trouvé en position " + i);
else
Console.WriteLine("Pas dans le tableau");
}//if
}//while
}
}
}
- рядки 27–62: метод Saisie вводить елементи масиву éléments, введені з клавіатури. Оскільки розмір масиву неможливо визначити заздалегідь (його кінцевий розмір невідомий), доводиться змінювати його розмір після кожного нового елемента (рядок 57). Більш ефективним алгоритмом було б виділення місця для масиву групами по N елементів. Однак масив не призначений для зміни розміру. У цьому випадку краще використовувати список (ArrayList, List<T>).
- рядки 75–113: метод Recherche дозволяє шукати в масиві éléments елемент, введений з клавіатури. Спосіб пошуку залежить від того, чи відсортований масив. Для несортованого масиву виконується лінійний пошук за допомогою методу IndexOf у рядку 106. Для сортованого масиву виконується дихотомічний пошук за допомогою методу BinarySearch у рядку 103.
- рядок 18: масив сортується за допомогою методу éléments. Тут використовується варіант методу Sort, який має лише один параметр: масив, що сортується. Відношення порядку, яке використовується для порівняння елементів масиву, є неявним для цих елементів. У даному випадку елементи є числовими. Використовується природний порядок чисел.
Результати на екрані такі:
5.4. Загальні колекції
Окрім масиву, існують різні класи для зберігання колекцій елементів. У просторі імен System.Collections.Generic є генеричні версії, а в System.Collections — негенеричні. Ми розглянемо дві часто використовувані генеричні колекції: список і словник.
Список генеричних колекцій такий:

5.4.1. Генеральний клас List<T>
Клас System.Collections.Generic.List<T> дозволяє реалізовувати колекції об’єктів типу T, розмір яких змінюється під час виконання програми. З об’єктом типу List<T> можна працювати майже так само, як із масивом. Таким чином, елемент i списку l позначається як l[i].
Існує також негенеративний тип списку: ArrayList, здатний зберігати посилання на будь-які об’єкти. ArrayList функціонально еквівалентний List<Object>.. Об’єкт ArrayList виглядає так:
![]() |
У наведеному вище прикладі елементи 0, 1 та i списку вказують на об’єкти різних типів. Об’єкт потрібно спочатку створити, перш ніж додавати посилання на нього до списку ArrayList. Хоча ArrayList зберігає посилання на об’єкти, у ньому можна зберігати й числа. Це здійснюється за допомогою механізму, який називається Boxing: число інкапсулюється в об’єкт O типу Object, і саме посилання на O зберігається у списку. Цей механізм є прозорим для розробника. Таким чином, можна написати:
Це дасть такий результат:
![]() |
У наведеному вище прикладі число 4 було інкапсульовано в об’єкт O, а посилання O збережено у списку. Щоб отримати його, можна написати:
int i = (int)liste[0];
Операція «Object -> int» називається Unboxing. Якщо список повністю складається з типів int, оголошення його як List<int> покращує продуктивність. Адже числа типу int у цьому випадку зберігаються безпосередньо у списку, а не у типах Object, що знаходяться поза списком. Операції boxing/unboxing більше не виконуються.
Якщо об’єкт List<T> або T є класом, список знову зберігає посилання на об’єкти типу T:
![]() |
Ось деякі властивості та методи генеричних списків:
Властивості
Методи
Повернемося до прикладу, розглянутого раніше, з об’єктом типу Array, і тепер обробимо його з об’єктом типу List<T>.. Оскільки список є об’єктом, схожим на масив, код майже не змінюється. Ми наведемо лише найважливіші зміни:
using System;
using System.Collections.Generic;
namespace Chap3 {
class Program {
// тип пошуку
enum TypeRecherche { linéaire, dichotomique };
// основний метод
static void Main(string[] args) {
// читання елементів списку, введених з клавіатури
List<double> éléments;
Saisie(out éléments);
// кількість елементів
Console.WriteLine("La liste a {0} éléments et une capacité de {1} éléments", éléments.Count, éléments.Capacity);
// виведення несортованого списку
Affiche("Liste non triée", éléments);
// лінійний пошук у несортованому списку
Recherche(éléments, TypeRecherche.linéaire);
// сортування списку
éléments.Sort();
// виведення відсортованого списку
Affiche("Liste triée", éléments);
// Діхотомічний пошук у відсортованому списку
Recherche(éléments, TypeRecherche.dichotomique);
}
// Введення значень у список елементів
// елементи: посилання на список, створений методом
static void Saisie(out List<double> éléments) {
...
// Спочатку список порожній
éléments = new List<double>();
// цикл введення елементів списку
while (!terminé) {
...
// якщо помилок немає
if (!erreur) {
// ще один елемент у списку
éléments.Add(élément);
}
}//while
}
// загальний метод для відображення елементів об’єкта, що підлягає переліку
static void Affiche<T>(string texte, IEnumerable<T> éléments) {
Console.WriteLine(texte.PadRight(50, '-'));
foreach (T élément in éléments) {
Console.WriteLine(élément);
}
}
// пошук елемента у списку
// елементи: список дійсних чисел
// TypeRecherche: дихотомічний або лінійний
static void Recherche(List<double> éléments, TypeRecherche type) {
...
while (!terminé) {
...
// якщо немає помилки
if (!erreur) {
// шукаємо елемент у списку
if (type == TypeRecherche.dichotomique)
// дихотомічний пошук
i = éléments.BinarySearch(élément);
else
// лінійний пошук
i = éléments.IndexOf(élément);
// Виведення відповіді
...
}//if
}//while
}
}
}
- рядки 46–51: узагальнений метод Affiche<T> приймає два параметри:
- перший параметр — це текст, який потрібно записати
- другий параметр — об’єкт, що реалізує узагальнений інтерфейс IEnumerable<T>:
Структура foreach( T елемент in елементи) у рядку 48 є дійсною для будь-якого об’єкта éléments, що реалізує інтерфейс IEnumerable. Масиви (Array) та списки (List<T>) реалізують інтерфейс IEnumerable<T>. Отже, метод Affiche підходить як для відображення таблиць, так і для відображення списків.
Результати виконання програми такі самі, як і в прикладі з використанням класу Array.
5.4.2. Клас Dictionary<TKey,TValue>
Клас System.Collections.Generic.Dictionary<TKey,TValue> дозволяє реалізувати словник. Словник можна розглядати як масив із двома стовпцями:
ключ | значення |
ключ1 | значення1 |
ключ2 | значення2 |
.. | ... |
У класі Dictionary<TKey,TValue> ключі мають тип Tkey, а значення — тип TValue. Ключі є унікальними, c.a.d, тобто не може бути двох однакових ключів. Такий словник міг би виглядати так, якби типи TKey та TValue позначали класи:
![]() |
Значення, пов’язане з ключем C словника D, позначається як D[C]. Це значення доступне для читання та запису. Отже, можна записати:
Якщо ключ c не існує у словнику D, запис D[c] викликає виняток.
Основні методи та властивості класу Dictionary<TKey,TValue> такі:
Конструктори
Властивості
Методи
Розглянемо таку прикладну програму:
using System;
using System.Collections.Generic;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// створення словника <string,int>
string[] liste = { "jean:20", "paul:18", "mélanie:10", "violette:15" };
string[] champs = null;
char[] séparateurs = new char[] { ':' };
Dictionary<string,int> dico = new Dictionary<string,int>();
for (int i = 0; i <liste.Length; i++) {
champs = liste[i].Split(séparateurs);
dico[champs[0]]= int.Parse(champs[1]);
}//for
// кількість елементів у словнику
Console.WriteLine("Le dictionnaire a " + dico.Count + " éléments");
// список ключів
Affiche("[Liste des clés]",dico.Keys);
// список значень
Affiche("[Liste des valeurs]", dico.Values);
// список ключів та значень
Console.WriteLine("[Liste des clés & valeurs]");
foreach (string clé in dico.Keys) {
Console.WriteLine("clé=" + clé + " valeur=" + dico[clé]);
}
// видаляємо ключ «paul»
Console.WriteLine("[Suppression d'une clé]");
dico.Remove("paul");
// список ключів та значень
Console.WriteLine("[Liste des clés & valeurs]");
foreach (string clé in dico.Keys) {
Console.WriteLine("clé=" + clé + " valeur=" + dico[clé]);
}
// пошук у словнику
String nomCherché = null;
Console.Write("Nom recherché (rien pour arrêter) : ");
nomCherché = Console.ReadLine().Trim();
int value;
while (!nomCherché.Equals("")) {
dico.TryGetValue(nomCherché, out value);
if (value!=0) {
Console.WriteLine(nomCherché + "," + value);
} else {
Console.WriteLine("Nom " + nomCherché + " inconnu");
}
// наступний пошук
Console.Out.Write("Nom recherché (rien pour arrêter) : ");
nomCherché = Console.ReadLine().Trim();
}//while
}
// загальний метод для відображення елементів перелічуваного типу
static void Affiche<T>(string texte, IEnumerable<T> éléments) {
Console.WriteLine(texte.PadRight(50, '-'));
foreach (T élément in éléments) {
Console.WriteLine(élément);
}
}
}
}
- рядок 8: масив string, який буде використовуватися для ініціалізації словника <string,int>
- рядок 11: словник <string,int>
- рядки 12–15: його ініціалізація на основі масиву string із рядка 8
- рядок 17: кількість записів у словнику
- рядок 19: ключі словника
- рядок 21: значення словника
- рядок 29: видалення запису зі словника
- рядок 41: пошук ключа у словнику. Якщо його немає, метод TryGetValue встановить значення 0 у value, оскільки value є числовим типом. Цей прийом можна застосувати в даному випадку лише тому, що відомо, що значення 0 відсутнє у словнику.
Результати виконання такі:
5.5. Текстові файли
5.5.1. Клас StreamReader
Клас System.IO.StreamReader дозволяє зчитувати вміст текстового файлу. Насправді він здатний обробляти потоки, які не є файлами. Ось деякі з його властивостей та методів:
Конструктори
Властивості
Методи
Ось приклад:
using System;
using System.IO;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// каталог виконання
Console.WriteLine("Répertoire d'exécution : "+Environment.CurrentDirectory);
string ligne = null;
StreamReader fluxInfos = null;
// читання вмісту файлу infos.txt
try {
// читання 1
Console.WriteLine("Lecture 1----------------");
using (fluxInfos = new StreamReader("infos.txt")) {
ligne = fluxInfos.ReadLine();
while (ligne != null) {
Console.WriteLine(ligne);
ligne = fluxInfos.ReadLine();
}
}
// читання 2
Console.WriteLine("Lecture 2----------------");
using (fluxInfos = new StreamReader("infos.txt")) {
Console.WriteLine(fluxInfos.ReadToEnd());
}
} catch (Exception e) {
Console.WriteLine("L'erreur suivante s'est produite : " + e.Message);
}
}
}
}
- рядок 8: відображає назву каталогу виконання
- рядки 12, 27: блок try/catch для обробки можливого винятку.
- рядок 15: структура using flux=new StreamReader(...) спрощує роботу, оскільки дозволяє не закривати потік явно після його використання. Закриття відбувається автоматично, щойно виходимо за межі області дії using.
- рядок 15: файл, що зчитується, називається infos.txt. Оскільки це відносне ім’я, його шукатимуть у каталозі виконання, вказаному в рядку 8. Якщо його там немає, буде згенеровано виняток, який оброблятиметься за допомогою блоку try/catch.
- рядки 16–20: файл зчитується послідовно, рядок за рядком
- рядок 25: файл зчитується повністю
Файл infos.txt має такий вигляд:
і розміщений у такій папці проекту C#:
![]() |
Ми побачимо, що bin/Release — це папка виконання, коли проект запускається за допомогою Ctrl-F5.
Виконання дає такі результати:
Якщо в рядку 15 вказати ім'я файлу xx.txt, отримаємо такі результати:
5.5.2. Клас StreamWriter
Клас System.IO.StreamReader дозволяє записувати дані в текстовий файл. Як і клас StreamReader, він фактично здатний обробляти потоки, які не є файлами. Ось деякі з його властивостей та методів:
Конструктори
Властивості
Методи
Розглянемо такий приклад:
using System;
using System.IO;
namespace Chap3 {
class Program2 {
static void Main(string[] args) {
// каталог виконання
Console.WriteLine("Répertoire d'exécution : " + Environment.CurrentDirectory);
string ligne = null; // рядок тексту
StreamWriter fluxInfos = null; // текстовий файл
try {
// створення текстового файлу
using (fluxInfos = new StreamWriter("infos2.txt")) {
Console.WriteLine("Mode AutoFlush : {0}", fluxInfos.AutoFlush);
// читання рядка, введеного з клавіатури
Console.Write("ligne (rien pour arrêter) : ");
ligne = Console.ReadLine().Trim();
// цикл, поки введений рядок не є порожнім
while (ligne != "") {
// запис рядка у текстовий файл
fluxInfos.WriteLine(ligne);
// читання нового рядка з клавіатури
Console.Write("ligne (rien pour arrêter) : ");
ligne = Console.ReadLine().Trim();
}//while
}
} catch (Exception e) {
Console.WriteLine("L'erreur suivante s'est produite : " + e.Message);
}
}
}
}
- рядок 13: знову використовуємо синтаксис using(flux), щоб не доводилося явно закривати потік операцією Close. Це закриття відбувається автоматично після виходу з using.
- Чому використовується блок try/catch у рядках 11 і 27? У рядку 13 ми могли б вказати ім’я файлу у форматі /rep1/rep2/ .../fichier із шляхом /rep1/rep2/..., який не існує, що унеможливлює створення fichier. У такому разі буде згенеровано виняток. Існують й інші можливі випадки винятків (переповнений диск, недостатні права доступу тощо)
Результати виконання такі:
Файл infos2.txt було створено в папці bin/Release проекту:
![]() | ![]() |
5.6. Бінарні файли
Класи System.IO.BinaryReader та System.IO.BinaryWriter призначені для читання та запису бінарних файлів.
Розглянемо таку програму:
// синтаксис pg текст бінарний лог
// читаємо текстовий файл (text) і записуємо його вміст у бінарний файл (bin
// текстовий файл містить рядки у форматі «ім’я : вік», які будуть збережені у структурі string, int
// (logs) — це текстовий файл журналів
Текстовий файл має такий вміст:
Програма має такий вигляд:
using System;
using System.IO;
// синтаксис pg текст бінарний лог-файли
// зчитується текстовий файл (text), а його вміст зберігається у бінарному файлі (bin)
// текстовий файл містить рядки у форматі «ім'я: вік», які будуть збережені у структурі string, int
// (logs) — це текстовий файл журналів
namespace Chap3 {
class Program {
static void Main(string[] arguments) {
// потрібно 3 аргументи
if (arguments.Length != 3) {
Console.WriteLine("syntaxe : pg texte binaire log");
Environment.Exit(1);
}//if
// змінні
string ligne=null;
string nom=null;
int age=0;
int numLigne = 0;
char[] séparateurs = new char[] { ':' };
string[] champs=null;
StreamReader input = null;
BinaryWriter output = null;
StreamWriter logs = null;
bool erreur = false;
// читання текстового файлу — запис бінарного файлу
try {
// відкриття текстового файлу для читання
input = new StreamReader(arguments[0]);
// відкриття бінарного файлу для запису
output = new BinaryWriter(new FileStream(arguments[1], FileMode.Create, FileAccess.Write));
// відкриття файлу журналів для запису
logs = new StreamWriter(arguments[2]);
// обробка текстового файлу
while ((ligne = input.ReadLine()) != null) {
// ще один рядок
numLigne++;
// порожній рядок?
if (ligne.Trim() == "") {
// ігнорується
continue;
}
// рядок «ім'я: вік»
champs = ligne.Split(séparateurs);
// нам потрібно 2 поля
if (champs.Length != 2) {
// записуємо помилку
logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un nombre de champs incorrect", numLigne, arguments[0]);
// наступний рядок
continue;
}//if
// перше поле має бути не порожнім
erreur = false;
nom = champs[0].Trim();
if (nom == "") {
// записуємо помилку в журнал
logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un nom vide", numLigne, arguments[0]);
erreur = true;
}
// друге поле має бути цілим числом >=0
if (!int.TryParse(champs[1],out age) || age<0) {
// записувати помилку в журнал
logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un âge [{2}] incorrect", numLigne, arguments[0], champs[1].Trim());
erreur = true;
}//якщо
// якщо помилки немає, записуємо дані у бінарний файл
if (!erreur) {
output.Write(nom);
output.Write(age);
}
// наступний рядок
}//поки
}catch(Exception e){
Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
} finally {
// закриття файлів
if(input!=null) input.Close();
if(output!=null) output.Close();
if(logs!=null) logs.Close();
}
}
}
}
Зупинимося на операціях, що стосуються класу BinaryWriter:
- рядок 34: об’єкт BinaryWriter відкривається операцією
output=new BinaryWriter(new FileStream(arguments[1],FileMode.Create,FileAccess.Write));
Аргументом конструктора має бути потік (Stream). У даному випадку це потік, створений на основі файлу (FileStream), для якого задано:
- (продовження)
- ім’я
- операцію, яку потрібно виконати, у даному випадку FileMode.Create для створення файлу
- тип доступу, у даному випадку FileAccess.Write для доступу до файлу з правом запису
- рядки 70–73: операції запису
Клас BinaryWriter має різні перевантажені методи Write для запису різних типів простих даних
- рядок 81: операція закриття потоку
Три аргументи методу Main передаються проекту (через його властивості) [1], а текстовий файл, що обробляється, розміщується в папці bin/Release [2]:
![]() |
З таким файлом [personnes1.txt]:
результати виконання такі:
![]() |
- у [1] — створений бінарний файл [personnes1.bin], а також файл журналу [logs.txt]. Останній має такий вміст:
Вміст бінарного файлу [personnes1.bin] буде надано нам наступною програмою. Вона також приймає три аргументи:
// синтаксис pg bin текст logs
// читаємо бінарний файл bin і записуємо його вміст у текстовий файл (текст)
// бінарний файл має структуру string, int
// текстовий файл містить рядки у форматі ім'я : вік
// logs — це текстовий файл журналів
Отже, виконуємо зворотну операцію. Зчитуємо двійковий файл, щоб створити текстовий файл. Якщо отриманий текстовий файл ідентичний вихідному, ми будемо знати, що перетворення «текст → двійковий → текст» пройшло успішно. Код такий:
using System;
using System.IO;
// синтаксис: pg, bin, текст, logs
// зчитується бінарний файл bin, а його вміст записується у текстовий файл (текст)
// бінарний файл має структуру string, int
// текстовий файл містить рядки у форматі «ім'я: вік»
// logs — це текстовий файл журналу
namespace Chap3 {
class Program2 {
static void Main(string[] arguments) {
// потрібно 3 аргументи
if (arguments.Length != 3) {
Console.WriteLine("syntaxe : pg binaire texte log");
Environment.Exit(1);
}//if
// змінні
string nom = null;
int age = 0;
int numPersonne = 1;
BinaryReader input = null;
StreamWriter output = null;
StreamWriter logs = null;
bool fini;
// читання бінарного файлу — запис текстового файлу
try {
// відкриття бінарного файлу для читання
input = new BinaryReader(new FileStream(arguments[0], FileMode.Open, FileAccess.Read));
// відкриття текстового файлу для запису
output = new StreamWriter(arguments[1]);
// відкриття файлу журналів для запису
logs = new StreamWriter(arguments[2]);
// обробка бінарного файлу
fini = false;
while (!fini) {
try {
// читання імені
nom = input.ReadString().Trim();
// читання віку
age = input.ReadInt32();
// запис у текстовий файл
output.WriteLine(nom + ":" + age);
// наступна особа
numPersonne++;
} catch (EndOfStreamException) {
fini = true;
} catch (Exception e) {
logs.WriteLine("L'erreur suivante s'est produite à la lecture de la personne n° {0} : {1}", numPersonne, e.Message);
}
}//цикл while
} catch (Exception e) {
Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
} finally {
// закриття файлів
if (input != null)
input.Close();
if (output != null)
output.Close();
if (logs != null)
logs.Close();
}
}
}
}
Зупинимося на операціях, що стосуються класу BinaryReader:
- рядок 30: об’єкт BinaryReader відкривається операцією
input=new BinaryReader(new FileStream(arguments[0],FileMode.Open,FileAccess.Read));
Аргументом конструктора має бути потік (Stream). У даному випадку це потік, побудований на основі файлу (FileStream), для якого задано:
- (продовження)
- ім’я
- операцію, яку потрібно виконати, тут — FileMode.Open для відкриття існуючого файлу
- тип доступу, у даному випадку FileAccess.Read для доступу до файлу в режимі читання
- рядки 40, 42: операції читання
Клас BinaryReader має різні методи ReadXX для читання різних типів простих даних
- рядок 60: операція закриття потоку
Якщо виконати обидві програми послідовно, перетворюючи personnes1.txt на personnes1.bin, а потім personnes1.bin на personnes2.txt2, отримаємо такі результати:
![]() |
- у [1] проект налаштований на виконання другої програми
- у [2] — аргументи, передані до Main
- у [3] — файли, створені в результаті виконання додатка.
Вміст файлу [personnes2.txt] такий:
5.7. Регулярні вирази
Клас System.Text.RegularExpressions.Regex дозволяє використовувати регулярні вирази. Вони дають змогу перевіряти формат символьного рядка. Таким чином можна перевірити, чи рядок, що позначає дату, дійсно має формат дд/мм/рр. Для цього використовується шаблон, і рядок порівнюється з цим шаблоном. Отже, у цьому прикладі j, m та a мають бути цифрами. Шаблон дійсного формату дати має вигляд «\d\d/\d\d/\d\d», де символ \d позначає цифру. У шаблоні можна використовувати такі символи:
Опис | |
Позначає наступний символ як спеціальний або літеральний. Наприклад, «n» відповідає символу «n». «\n» відповідає символу нового рядка. Послідовність «\\» відповідає «\», а «\(" відповідає «(». | |
Відповідає початку введення. | |
Відповідає кінцю введення. | |
Відповідає попередньому символу нуль або більше разів. Таким чином, «zo*» відповідає «z» або «zoo». | |
Відповідає попередньому символу один або кілька разів. Таким чином, «zo+» відповідає «zoo», але не «z». | |
Відповідає попередньому символу нуль або один раз. Наприклад, «a?ve?» відповідає «ve» у слові «lever». | |
Відповідає будь-якому окремому символу, крім символу нового рядка. | |
Шукає modèle і запам'ятовує збіг. Відповідний підрядок можна витягти з отриманої колекції Matches за допомогою Item [0]...[n]. Щоб знайти збіги з символами в дужках ( ), використовуйте «\(» або «\)». | |
Відповідає або x, або y. Наприклад, «z|foot» відповідає «z» або «foot». «(z|f)oo» відповідає «zoo» або «foo». | |
n — це невід’ємне ціле число. Відповідає точно n, помноженому на символ. Наприклад, «o{2}» не відповідає «o» у слові «Bob», а відповідає двом першим «o» у слові «fooooot». | |
n — це невід’ємне ціле число. Відповідає щонайменше n-кратному кількості символів. Наприклад, «o{2,}» не відповідає «o» у слові «Bob», а відповідає всім «o» у слові «fooooot». «o{1,}» еквівалентно «o+», а «o{0,}» — «o*». | |
m та n — це невід’ємні цілі числа. Відповідає щонайменше n і щонайбільше m повторень символу. Наприклад, «o{1,3}» відповідає трьом першим «o» у слові «foooooot», а «o{0,1}» дорівнює «o?». | |
Набір символів. Відповідає одному з вказаних символів. Наприклад, «[abc]» відповідає «a» у слові «plat». | |
Негативний набір символів. Відповідає будь-якому символу, що не вказано. Наприклад, «[^abc]» відповідає «p» у слові «plat». | |
Діапазон символів. Відповідає будь-якому символу у вказаному діапазоні. Наприклад, «[a-z]» відповідає будь-якій маленькій літері алфавіту від «a» до «z». | |
Негативний діапазон символів. Відповідає будь-якому символу, якого немає у вказаній послідовності. Наприклад, «[^m-z]» відповідає будь-якому символу, якого немає між «m» і «z». | |
Відповідає межі, що позначає слово, іншими словами, позиції між словом і пробілом. Наприклад, «er\b» відповідає «er» у слові «lever», але не відповідає «er» у слові «verbe». | |
Відповідає межі, що не представляє слова. «en*t\B» відповідає «ent» у слові «bien entendu». | |
Відповідає символу, що позначає цифру. Еквівалентно [0-9]. | |
Відповідає символу, що не є цифрою. Еквівалентно [^0-9]. | |
Відповідає символу переходу на новий рядок. | |
Відповідає символу нового рядка. | |
Відповідає символу повернення каретки. | |
Відповідає будь-якому пробілу, включаючи пробіл, табуляцію, перехід на новий аркуш тощо. Еквівалентно «[ \f\n\r\t\v]». | |
Відповідає будь-якому символу, що не є пробілом. Еквівалентно «[^ \f\n\r\t\v]». | |
Відповідає символу табуляції. | |
Відповідає символу вертикального табуляції. | |
Відповідає будь-якому символу, що позначає слово та містить підкреслення. Еквівалентно «[A-Za-z0-9_]». | |
Відповідає будь-якому символу, що не позначає слово. Еквівалентно «[^A-Za-z0-9_]». | |
Відповідає num, де num — це додатне ціле число. Посилається на збережені відповідності. Наприклад, «(.)\1» відповідає двом послідовним однаковим символам. | |
Відповідає n, де n — це вісімкове екрановане значення. Вісімкові екрановані значення повинні складатися з 1, 2 або 3 цифр. Наприклад, «\11» і «\011» обидва відповідають символу табуляції. "\0011" еквівалентно "\001" & "1". Восьмиричні екрануючі значення не повинні перевищувати 256. Якщо це сталося, у виразі враховуватимуться лише перші дві цифри. Дозволяє використовувати коди ASCII у регулярних виразах. | |
Відповідає n, де n — це шістнадцяткове значення екранування. Шістнадцяткові екрануючі значення обов’язково мають складатися з двох цифр. Наприклад, «\x41» відповідає «A». «\x041» дорівнює «\x04» та «1». Дозволяє використовувати коди ASCII у регулярних виразах. |
Елемент у шаблоні може зустрічатися один або кілька разів. Розглянемо кілька прикладів із символом \d, який позначає 1 цифру:
шаблон | значення |
\d | цифра |
\d? | 0 або 1 цифра |
\d* | 0 або більше цифр |
\d+ | 1 або більше цифр |
\d{2} | 2 цифри |
\d{3,} | щонайменше 3 цифри |
\d{5,7} | від 5 до 7 цифр |
Тепер уявімо собі шаблон, здатний описати очікуваний формат рядка:
пошуковий рядок | модель |
дата у форматі дд/мм/рр | \d{2}/\d{2}/\d{2} |
час у форматі hh:mm:ss | \d{2}:\d{2}:\d{2} |
ціле число без знака | \d+ |
послідовність пробілів, яка може бути порожньою | \s* |
ціле число без знака, перед яким або після якого можуть бути пробіли | \s*\d+\s* |
ціле число, яке може бути зі знаком і перед яким або після якого можуть бути пробіли | \s*[+|-]?\s*\d+\s* |
число з дійсної області без знака, перед яким або після якого можуть бути пробіли | \s*\d+(.\d*)?\s* |
дійсне число, яке може мати знак і мати пробіли перед або після нього | \s*[+|]?\s*\d+(.\d*)?\s* |
рядок, що містить слово juste | \bjuste\b |
Можна вказати, де саме в рядку шукати шаблон:
шаблон | значення |
^шаблон | модель починає рядок |
шаблон$ | шаблон завершує рядок |
^шаблон$ | шаблон починає і закінчує рядок |
шаблон | шаблон шукається у всьому рядку, починаючи з його початку. |
шуканий рядок | шаблон |
рядок, що закінчується знаком оклику | !$ |
рядок, що закінчується крапкою | \.$ |
рядок, що починається з послідовності // | ^// |
рядок, що складається лише з одного слова, за яким можуть йти або перед яким можуть стояти пробіли | ^\s*\w+\s*$ |
рядок, що містить два слова, за якими або перед якими можуть бути пробіли | ^\s*\w+\s*\w+\s*$ |
рядок, що містить слово secret | \bsecret\b |
Підмножини шаблону можна «витягувати». Таким чином, можна не тільки перевірити, чи рядок відповідає певному шаблону, але й витягнути з цього рядка елементи, що відповідають підмножинам шаблону, які були взяті в дужки. Отже, якщо ми аналізуємо рядок, що містить дату дд/мм/рр, і хочемо додатково виокремити елементи дд, мм, рр з цієї дати, ми використаємо шаблон (\d\d)/(\d\d)/(\d\d).
5.7.1. Перевірка відповідності рядка заданому шаблону
Об’єкт типу Regex створюється таким чином:
Після створення шаблону регулярного виразу його можна порівняти з рядками за допомогою методу IsMatch:
Ось приклад:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// шаблон регулярного виразу
string modèle1 = @"^\s*\d+\s*$";
Regex regex1 = new Regex(modèle1);
// порівняння зразка з шаблоном
string exemplaire1 = " 123 ";
if (regex1.IsMatch(exemplaire1)) {
Console.WriteLine("[{0}] correspond au modèle [{1}]", exemplaire1, modèle1);
} else {
Console.WriteLine("[{0}] ne correspond pas au modèle [{1}]", exemplaire1, modèle1);
}//if
string exemplaire2 = " 123a ";
if (regex1.IsMatch(exemplaire2)) {
Console.WriteLine("[{0}] correspond au modèle [{1}]", exemplaire2, modèle1);
} else {
Console.WriteLine("[{0}] ne correspond pas au modèle [{1}]", exemplaire2, modèle1);
}//if
}
}
}
та результати виконання:
5.7.2. Пошук усіх входжень шаблону в рядку
Метод Matches дозволяє отримати елементи рядка, що відповідають шаблону:
Клас MatchCollection має властивість Count, яка є кількістю елементів колекції. Якщо résultats є об’єктом MatchCollection, то results[i] є i-м елементом цієї колекції та має тип Match. Клас Match має різні властивості, серед яких такі:
- Value: значення об’єкта Match,, тобто елемента, що відповідає шаблону
- Index: позиція, на якій елемент було знайдено в обстежуваному ланцюжку
Розглянемо такий приклад:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program2 {
static void Main(string[] args) {
// кілька входжень шаблону в зразку
string modèle2 = @"\d+";
Regex regex2 = new Regex(modèle2);
string exemplaire3 = " 123 456 789 ";
MatchCollection résultats = regex2.Matches(exemplaire3);
Console.WriteLine("Modèle=[{0}],exemplaire=[{1}]", modèle2, exemplaire3);
Console.WriteLine("Il y a {0} occurrences du modèle dans l'exemplaire ", résultats.Count);
for (int i = 0; i < résultats.Count; i++) {
Console.WriteLine("[{0}] trouvé en position {1}", résultats[i].Value, résultats[i].Index);
}//for
}
}
}
- рядок 8: шуканий шаблон — це послідовність цифр
- рядок 10: рядок, у якому шукається цей шаблон
- рядок 11: вибираються всі елементи з exemplaire3, що відповідають шаблону modèle2
- рядки 14–16: їх виводимо на екран
Результати виконання програми такі:
5.7.3. Витяг частин шаблону
Підмножини шаблону можна «витягувати». Таким чином, можна не тільки перевірити, чи відповідає рядок певному шаблону, але й витягнути з цього рядка елементи, що відповідають підмножинам шаблону, які були взяті в дужки. Отже, якщо ми аналізуємо рядок, що містить дату дд/мм/рр, і хочемо додатково виокремити елементи дд, мм, рр з цієї дати, ми використаємо шаблон (\d\d)/(\d\d)/(\d\d).
Розглянемо такий приклад:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program3 {
static void Main(string[] args) {
// витяг елементів із шаблону
string modèle3 = @"(\d\d):(\d\d):(\d\d)";
Regex regex3 = new Regex(modèle3);
string exemplaire4 = "Il est 18:05:49";
// перевірка шаблону
Match résultat = regex3.Match(exemplaire4);
if (résultat.Success) {
// екземпляр відповідає шаблону
Console.WriteLine("L'exemplaire [{0}] correspond au modèle [{1}]",exemplaire4,modèle3);
// відображаються групи дужок
for (int i = 0; i < résultat.Groups.Count; i++) {
Console.WriteLine("groupes[{0}]=[{1}] trouvé en position {2}",i, résultat.Groups[i].Value,résultat.Groups[i].Index);
}//for
} else {
// екземпляр не відповідає шаблону
Console.WriteLine("L'exemplaire[{0}] ne correspond pas au modèle [{1}]", exemplaire4, modèle3);
}
}
}
}
Виконання цієї програми дає такі результати:
Нововведення міститься в рядках 12–19:
- рядок 12: рядок exemplaire4 порівнюється з шаблоном regex3 за допомогою методу Match. Цей метод повертає об’єкт Match, який вже розглядався раніше. Тут ми використовуємо дві нові властивості цього класу:
- Success (рядок 13): вказує, чи було знайдено збіг
- Groups (рядки 17, 18): колекція, де
- Groups[0] відповідає частині рядка, що відповідає шаблону
- Groups[i] (i>=1) відповідає групі дужок № i
Якщо résultat має тип Match, то résultats.Groups належить до типу GroupCollection, а résultats.Groups[i] — до типу Group. Клас Group має дві властивості, які ми використовуємо тут:
- Value (рядок 18): значення об’єкта Group, який є елементом, що відповідає вмісту дужки
- Index (рядок 18): позиція, на якій елемент було знайдено в обстежуваному рядку
5.7.4. Програма для навчання
Знайти регулярний вираз, який дозволяє перевірити, чи рядок дійсно відповідає певному шаблону, іноді є справжнім викликом. Наступна програма дозволяє потренуватися. Вона запитує шаблон і рядок та вказує, чи відповідає рядок шаблону чи ні.
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program4 {
static void Main(string[] args) {
// дані
string modèle, chaine;
Regex regex = null;
MatchCollection résultats;
// користувачеві пропонується вказати шаблони та екземпляри, які слід порівняти з цим
while (true) {
// запитується модель
Console.Write("Tapez le modèle à tester ou rien pour arrêter :");
modèle = Console.In.ReadLine();
// готово?
if (modèle.Trim() == "")
break;
// створюємо регулярний вираз
try {
regex = new Regex(modèle);
} catch (Exception ex) {
Console.WriteLine("Erreur : " + ex.Message);
continue;
}
// запитуємо у користувача екземпляри, які потрібно порівняти з шаблоном
while (true) {
Console.Write("Tapez la chaîne à comparer au modèle [{0}] ou rien pour arrêter :", modèle);
chaine = Console.ReadLine();
// завершено?
if (chaine.Trim() == "")
break;
// виконується порівняння
résultats = regex.Matches(chaine);
// успішно?
if (résultats.Count == 0) {
Console.WriteLine("Je n'ai pas trouvé de correspondances");
continue;
}//if
// виводяться елементи, що відповідають шаблону
for (int i = 0; i < résultats.Count; i++) {
Console.WriteLine("J'ai trouvé la correspondance [{0}] en position [{1}]", résultats[i].Value, résultats[i].Index);
// піделементів
if (résultats[i].Groups.Count != 1) {
for (int j = 1; j < résultats[i].Groups.Count; j++) {
Console.WriteLine("\tsous-élément [{0}] en position [{1}]", résultats[i].Groups[j].Value, résultats[i].Groups[j].Index);
}
}
}
}
}
}
}
}
Ось приклад виконання:
5.7.5. Метод Split
Ми вже зустрічали цей метод у класі String:
|
Метод Split класу Regex дозволяє нам задавати роздільник на основі шаблону:
|
Припустимо, наприклад, що в текстовому файлі є рядки у вигляді «поле1, поле2, …, полеn». Поля розділяються комою, але перед нею або після неї можуть бути пробіли. У такому разі метод Split класу string не підходить. Рішенням є метод RegEx. Якщо ligne — це прочитаний рядок, то поля можна отримати за допомогою
як показано в наступному прикладі:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program5 {
static void Main(string[] args) {
// один рядок
string ligne = "abc , def , ghi";
// шаблон
Regex modèle = new Regex(@"\s*,\s*");
// розбиття рядка на поля
string[] champs = modèle.Split(ligne);
// відображення
for (int i = 0; i < champs.Length; i++) {
Console.WriteLine("champs[{0}]=[{1}]", i, champs[i]);
}
}
}
}
Результати виконання:
5.8. Приклад програми — V3
Ми повертаємося до додатка, розглянутого в розділах 3.6 (версія 1) та 4.10 (версія 2).
В останній розглянутій версії розрахунок податку здійснювався в абстрактному класі AbstractImpot:
namespace Chap2 {
abstract class AbstractImpot : IImpot {
// податкові шкали, необхідні для розрахунку податку
// походять із зовнішнього джерела
protected TrancheImpot[] tranchesImpot;
// розрахунок податку
public int calculer(bool marié, int nbEnfants, int salaire) {
// розрахунок кількості часток
decimal nbParts;
if (marié) nbParts = (decimal)nbEnfants / 2 + 2;
else nbParts = (decimal)nbEnfants / 2 + 1;
if (nbEnfants >= 3) nbParts += 0.5M;
// розрахунок оподатковуваного доходу та сімейного коефіцієнта
decimal revenu = 0.72M * salaire;
decimal QF = revenu / nbParts;
// розрахунок податку
tranchesImpot[tranchesImpot.Length - 1].Limite = QF + 1;
int i = 0;
while (QF > tranchesImpot[i].Limite) i++;
// повернення результату
return (int)(revenu * tranchesImpot[i].CoeffR - nbParts * tranchesImpot[i].CoeffN);
}//розрахувати
}// клас
}
Метод calculer у рядку 38 використовує масив tranchesImpot із рядка 35, який не ініціалізовано класом AbstractImpot. Саме тому вона є абстрактною і має бути похідною, щоб бути корисною. Ця ініціалізація здійснювалася похідним класом HardwiredImpot:
using System;
namespace Chap2 {
class HardwiredImpot : AbstractImpot {
// таблиці даних, необхідні для розрахунку податку
decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };
public HardwiredImpot() {
// створення таблиці податкових шкал
tranchesImpot = new TrancheImpot[limites.Length];
// заповнення
for (int i = 0; i < tranchesImpot.Length; i++) {
tranchesImpot[i] = new TrancheImpot { Limite = limites[i], CoeffR = coeffR[i], CoeffN = coeffN[i] };
}
}
}// клас
}// простір імен
У наведеному вище прикладі дані, необхідні для розрахунку податку, були «жорстко» вбудовані в код класу. У новій версії прикладу вони розміщуються у текстовому файлі:
4962:0:0
8382:0,068:291,09
14753:0,191:1322,92
23888:0,283:2668,39
38868:0,374:4846,98
47932:0,426:6883,66
0:0,481:9505,54
Оскільки обробка цього файлу може спричинити винятки, ми створюємо спеціальний клас для їх обробки:
using System;
namespace Chap3 {
class FileImpotException : Exception {
// коди помилок
[Flags]
public enum CodeErreurs { Acces = 1, Ligne = 2, Champ1 = 4, Champ2 = 8, Champ3 = 16 };
// код помилки
public CodeErreurs Code { get; set; }
// конструктори
public FileImpotException() {
}
public FileImpotException(string message)
: base(message) {
}
public FileImpotException(string message, Exception e)
: base(message,e) {
}
}
}
- рядок 4: клас FileImpotException походить від класу Exception. Він слугуватиме для збереження всіх помилок, що виникають під час обробки текстового файлу з даними.
- рядок 7: перелік, що представляє коди помилок:
- Acces: помилка доступу до текстового файлу даних
- Ligne: рядок не містить трьох очікуваних полів
- Champ1: поле № 1 містить помилку
- Champ2: поле № 2 містить помилку
- Champ3: поле № 3 містить помилку
Деякі з цих помилок можуть поєднуватися (Champ1, Champ2, Champ3). Тому перелік CodeErreurs було позначено атрибутом [Flags], що означає, що різні значення переліку мають бути степенями числа 2. Помилка в полях 1 і 2 призведе до появи коду помилки Champ1 | Champ2.
- рядок 10: автоматична властивість Code збереже код помилки.
- рядок 15: конструктор, що дозволяє створити об’єкт FileImpotException, передаючи йому як параметр повідомлення про помилку.
- рядок 18: конструктор, що дозволяє створити об’єкт FileImpotException, передаючи йому як параметри повідомлення про помилку та виняток, що спричинив цю помилку.
Клас, який ініціалізує масив tranchesImpot класу AbstractImpot, тепер має такий вигляд:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
namespace Chap3 {
class FileImpot : AbstractImpot {
public FileImpot(string fileName) {
// дані
List<TrancheImpot> listTranchesImpot = new List<TrancheImpot>();
int numLigne = 1;
// виняток
FileImpotException fe = null;
// читання вмісту файлу fileName, рядок за рядком
Regex pattern = new Regex(@"s*:\s*");
// спочатку помилок немає
FileImpotException.CodeErreurs code = 0;
try {
using (StreamReader input = new StreamReader(fileName)) {
while (!input.EndOfStream && code == 0) {
// поточний рядок
string ligne = input.ReadLine().Trim();
// порожні рядки ігноруються
if (ligne == "") continue;
// рядок розбитий на три поля, розділені:
string[] champsLigne = pattern.Split(ligne);
// чи є 3 поля?
if (champsLigne.Length != 3) {
code = FileImpotException.CodeErreurs.Ligne;
}
// перетворення 3 полів
decimal limite = 0, coeffR = 0, coeffN = 0;
if (code == 0) {
if (!Decimal.TryParse(champsLigne[0], out limite)) code = FileImpotException.CodeErreurs.Champ1;
if (!Decimal.TryParse(champsLigne[1], out coeffR)) code |= FileImpotException.CodeErreurs.Champ2;
if (!Decimal.TryParse(champsLigne[2], out coeffN)) code |= FileImpotException.CodeErreurs.Champ3; ;
}
// помилка?
if (code != 0) {
// фіксуємо помилку
fe = new FileImpotException(String.Format("Ligne n° {0} incorrecte", numLigne)) { Code = code };
} else {
// запам'ятовується новий податковий діапазон
listTranchesImpot.Add(new TrancheImpot() { Limite = limite, CoeffR = coeffR, CoeffN = coeffN });
// наступний рядок
numLigne++;
}
}
}
// переносимо список listImpot у масив tranchesImpot
if (code == 0) {
// переносимо список listImpot у масив tranchesImpot
tranchesImpot = listTranchesImpot.ToArray();
}
} catch (Exception e) {
// фіксується помилка
fe= new FileImpotException(String.Format("Erreur lors de la lecture du fichier {0}", fileName), e) { Code = FileImpotException.CodeErreurs.Acces };
}
// чи слід повідомити про помилку?
if (fe != null) throw fe;
}
}
}
- рядок 7: клас FileImpot походить від класу AbstractImpot, так само як у версії 2 походив клас HardwiredImpot.
- рядок 9: конструктор класу FileImpot призначений для ініціалізації поля trancheImpot його базового класу AbstractImpot. Він приймає як параметр ім’я текстового файлу, що містить дані.
- рядок 11: поле tranchesImpot базового класу AbstractImpot є масивом, який має бути заповнений даними з файлу filename, переданого як параметр. Зчитування текстового файлу відбувається послідовно. Кількість рядків стає відомою лише після зчитування всього файлу. Тому неможливо заздалегідь визначити розмір масиву tranchesImpot. Дані тимчасово зберігатимуться у загальному списку listTranchesImpot.
Нагадаємо, що тип TrancheImpot є структурою:
namespace Chap3 {
// податкова ставка
struct TrancheImpot {
public decimal Limite { get; set; }
public decimal CoeffR { get; set; }
public decimal CoeffN { get; set; }
}
}
- рядок 14: fe типу FileImpotException служить для інкапсуляції можливої помилки обробки текстового файлу.
- рядок 16: регулярний вираз розділювача полів у рядку champ1:champ2:champ3 текстового файлу. Поля розділяються символом :, перед яким і після якого може бути будь-яка кількість пробілів.
- рядок 18: код помилки у разі виникнення помилки
- рядок 20: обробка текстового файлу за допомогою StreamReader
- рядок 21: цикл виконується, доки залишається хоча б один рядок для зчитування і не сталося помилки
- рядок 27: прочитаний рядок розділяється на поля за допомогою регулярного виразу з рядка 16
- рядки 29–31: перевіряється, чи рядок дійсно має три поля — фіксується можлива помилка
- рядки 33–38: перетворення трьох рядків у три десяткові числа — фіксуються можливі помилки
- рядки 40–43: якщо сталася помилка, створюється виняток типу FileImpotException.
- рядки 44–47: якщо помилки не було, переходимо до зчитування наступного рядка текстового файлу після збереження даних з поточного рядка.
- рядки 52–55: після виходу з блоку while дані зі списку listTranchesImpot копіюються в масив tranchesImpot базового класу AbstractImpot. Нагадаємо, що саме це і було метою розробника.
- рядки 56–59: обробка можливого винятку. Він інкапсульований в об’єкт типу FileImpotException.
- рядок 61: якщо виняток fe із рядка 18 було ініціалізовано, то його запускають.
Повний проект на C# виглядає так:
![]() |
- у [1]: весь проект
- у [2,3]: властивості файлу [DataImpot.txt] [2]. Властивість [Copy to Output Directory] [3] встановлено на «always». Це означає, що файл [DataImpot.txt] буде скопійовано до папки bin/Release (режим Release) або bin/Debug (режим Debug) під час кожного запуску. Саме там його шукає виконуваний файл.
- У [4]: те саме робиться з файлом [DataImpotInvalide.txt].
Вміст файлу [DataImpot.txt] такий:
4962:0:0
8382:0,068:291,09
14753:0,191:1322,92
23888:0,283:2668,39
38868:0,374:4846,98
47932:0,426:6883,66
0:0,481:9505,54
Зміст файлу [DataImpotInvalide.txt] такий:
Тестова програма [Program.cs] не зазнала змін: це програма з версії 2, параграф 4.10, за винятком наступної відмінності:
using System;
namespace Chap3 {
class Program {
static void Main() {
...
// створення об’єкта IImpot
IImpot impot = null;
try {
// створення об’єкта IImpot
impot = new FileImpot("DataImpot.txt");
} catch (FileImpotException e) {
// відображення помилки
string msg = e.InnerException == null ? null : String.Format(", Exception d'origine : {0}", e.InnerException.Message);
Console.WriteLine("L'erreur suivante s'est produite : [Code={0},Message={1}{2}]", e.Code, e.Message, msg == null ? "" : msg);
// зупинка програми
Environment.Exit(1);
}
// нескінченний цикл
while (true) {
...
}//while
}
}
}
- рядок 8: об’єкт impot типу інтерфейсу IImpot
- рядок 11: створення екземпляра об’єкта impot з об’єктом типу FileImpot. Це може спричинити виняток, який обробляється за допомогою блоків try / catch у рядках 9, 12 та 18.
Ось приклади виконання:
З файлом [DataImpot.txt]
З неіснуючим файлом [xx]
З файлом [DataImpotInvalide.txt]






























