
- •Введение
- •Сравнение языков С++ и C#
- •Логические выражения
- •Функции для ввода и вывода в языке C#
- •Управление форматом числовых данных:
- •Обработка исключительных ситуаций
- •Методы и модификаторы параметров
- •Неявно типизированные переменные
- •Понятие класса
- •Свойства
- •Индексаторы
- •Одномерные индексаторы
- •Многомерные индексаторы
- •Перегрузка методов
- •Перегрузка знаков операций
- •Наследование
- •Виртуальные функции
- •Работа с файлами
- •Работа с каталогами
- •Абстрактный класс FileSystemInfo
- •Класс DirectoryInfo
- •Сериализация
- •FileSystemWatcher – отслеживание событий, связанных с файлами
- •Обобщения (шаблоны)
- •Интерфейсы
- •Коллекции
- •LINQ
- •Грамматика выражений запросов
- •Синтаксис запросов
- •Проекция и фильтрация
- •Упорядочение
- •Агрегирующие запросы
- •Операции с коллекциями
- •Операция Concat
- •Операция Union
- •Преобразование
- •Объединение последовательностей
- •FirstOrDefault
- •Группировка
- •Групповая адресация
- •Обработка событий
- •Групповое преобразование делегируемых методов
- •Применение методов экземпляра в качестве делегатов
- •Групповая адресация
- •Ковариантность и контравариантность
- •Класс System. Delegate
- •Назначение делегатов
- •Анонимные функции
- •Анонимные методы
- •Передача аргументов анонимному методу
- •Возврат значения из анонимного метода
- •Применение внешних переменных в анонимных методах
- •Лямбда-выражения
- •Лямбда-оператор
- •Одиночные лямбда-выражения
- •Блочные лямбда-выражения
- •События
- •Пример групповой адресации события
- •Применение аксессоров событий
- •Разнообразные возможности событий
- •Применение анонимных методов и лямбда-выражений вместе с событиями
- •Рекомендации по обработке событий в среде .NET Framework
- •Применение делегатов EventHandler<TEventArgs> и EventHandler
- •Практический пример обработки событий

Чернов Э. А. |
- 88 - |
Лекции по языку C# v 2.3 |
Результат выполнения программы |
|
|
Обратите внимание, для изменения порядка сортировки был добавлен класс, в котором было задано другое поле для сортировки.
Для выполнения сортировки по разным полям достаточно одного интерфейса IComparable <T>, а изменения порядка сортировки достаточно вставить классы, наследующие интерфейс IComparer<T>, каждый из которых будет задавать свой порядок сортировки.
Интерфейсы IComparer<T> и IComparable<T> являются точными аналогами интерфейсов IEqualityComparer<T> и IEquatable<T>, но эти интерфейсы применяются для проверки на равенство, а не для установки порядка следования элементов.
Коллекции
Коллекция или контейнер это объект программы, который содержит экземпляры наборов данных и который позволяет управлять этой коллекцией. Под управлением коллекцией понимается выполнение добавления в коллекцию нового экземпляра, исключения экземпляра из коллекции, просмотр содержимого экземпляров, получение информации о количестве экземпляров в коллекции и т. д.
Перечисленные операции не зависят от типа данных, используемого в контейнере, но позволяют сократить затраты труда на написание программ для реализации этих операций. Для доступа к контейнеру достаточно добавить сборку System.Collections.Generic, содержащую методы для работы с коллекциями.
Контейнеры можно разделить на две группы:
Последовательные контейнеры.
Ассоциативные множества (словари).
Ниже указаны основные последовательные контейнеры:
Чернов Э. А. |
- 89 - |
Лекции по языку C# v 2.3 |
Список ArrayList необобщенный список. Объявляется без лексемы обобщения
(<T>).
Список List -обобщенный вариант класса ArrayList и
Связный список (класс Linkedlist).
Последовательные контейнеры различаются по способу вставки и исключения экземпляров наборов данных. Контейнер типа списка соответствует понятию одномерного массива в языке программирования, в котором элементы расположены в памяти компьютера строго последовательно, и обращение к элементам выполняется по индексу. Для вставки элемента в середину такого массива потребуется освободить место, занятое уже существующим элементом, а для этого надо переместить все элементы, которые должны следовать после вставляемого элемента на одну позицию по направлению к концу массива. При больших размерах массивов такая операция занимает значительное время. Аналогично для исключения элемента надо на его место переписать элемент, следующий за ним и т. д. С другой стороны, время доступа к элементу минимально, поскольку по индексу можно сразу вычислить адрес элемента в памяти.
В связных списках каждый элемент имеет указатель на следующий элемент, поэтому элементы в памяти могут размещаться в произвольном порядке, а, следовательно, для вставки элемента в середину списка требуется выделить для нового элемента память и настроить указатели так, чтобы новый элемент оказался в требуемом месте. Поэтому в связных списках время вставки (и исключения) элементов минимально (не зависит ни от размера списка, ни от места вставки), но для доступа к элементу данных требуется последовательный проход элементов, предшествующих искомому, что требует времени.
Независимо от типа и вида контейнера интерфейс для работы с контейнером стандартизован в том смысле, что одинаковые по назначению операции и параметры являются одинаковыми для всех контейнеров.
Для всех коллекций вводится понятие перечисляемого типа данных, который поддерживается как в необобщенных интерфейсах IEnumerator и IEnumerable, так и в обобщенных интерфейсах IEnumerator<T> и IEnumerable<T>. Перечисляемый тип данных предоставляет возможность доступа к элементам коллекции. В каждой коллекции должен быть реализован обобщенный или необобщенный интерфейс IEnumerable, поэтому элементы любого класса коллекции должны быть доступны посредством методов, определенных в интерфейсе IEnumerator или IEnumerator<T>. Это означает, что, внеся минимальные изменения в код циклического обращения к коллекции одного типа, его можно использовать для аналогичного обращения к коллекции другого типа.
Для поочередного обращения к содержимому коллекции в цикле foreach используется перечисляемый тип данных. С перечисляемым типом данных непосредственно связано другое средство, называемое итератором. Это средство упрощает процесс создания классов коллекций, например специальных, поочередное обращение к которым организуется в цикле foreach.
Ниже приведен пример программы для обработки обобщенного списка List. Элементом списка является класс List, содержащий 4 поля. В программе рассмотрено:
Создание списка.
Чернов Э. А. |
- 90 - |
Лекции по языку C# v 2.3 |
Просмотр списка (вывод на экран).
Сортировка списка (по умолчанию).
Сортировка списка по заданному полю.
Поиск элемента с заданным значением поля.
Подсчет количества элементов с заданным значением поля.
Фильтрация элементов списка.
Список создается из файла, в одной строке значения для одного элемента списка.
class Program
{
class Block: IComparable<Block> // Подключение интерфейса
{ // Описание класса
public string prof { get; set; } public string name { get; set; } public int skil;
public double wage;
public Block() { } // Пустой конструктор
public Block(string p, string s, int k, double w) // Конструктор с параметрами
{
prof = p; name = s; skil = k; wage = w;
}
public int CompareTo(Block obj)
{
Block blk;
blk = (Block) obj;
int n = this.prof.CompareTo(blk.prof); // Сравнение по полю prof return n;
}
public class sortOnSkill : IComparer<Block> // Для переключения поля
{ // Имя sortOnSkil произвольное, для каждого поля сравнения свое имя public int Compare(Block a, Block b)
{
if (a.skil > b.skil) return 1; // Сравнение по полю skil else if (a.skil < b.skil) return -1;
else return 0;
}
}
}
static void Main(string[] args)
{ // Файл поместить в папку Debug, либо прописывать полный путь
StreamReader file =
new StreamReader("C:\\Temp\\blocks.txt", Encoding.Default);
string s; int sk; double wg; string[ ] str;
Чернов Э. А. |
- 91 - |
Лекции по языку C# v 2.3 |
Block blk = new Block(); List<Block> lst = new List<Block>() ; // Формирование списка из файла while ((s = file.ReadLine()) != null)
{
str = s.Split(' '); // Расщепление строки по пробелам (str - массив) sk = int.Parse(str[2]); // Преобразование строки в число
wg = double.Parse(str[3]);
lst.Add(new Block(str[0],str[1], sk, wg)); // Добавить в блок
}
Console.WriteLine("Начальный список");
foreach (Block bl in lst) // Вывод начального списка Console.WriteLine("{0,-12}\t{1,-8}\t{2}\t{3}", bl.prof, bl.name, bl.skil, bl.wage); Console.WriteLine("\n Сортировка по профессии");
lst.Sort(); |
// Сортировка по умолчанию |
foreach (Block bl in lst) |
// Вывод сортированного списка |
Console.WriteLine("{0,-12}\t{1,-8}\t{2}\t{3}",
bl.prof, bl.name, bl.skil,bl.wage); Console.WriteLine("\nСортировка по квалификации");
// Вызов класса, изменяющего поле для сортировки
IComparer<Block> comp = new ArrayLst.Program.Block.sortOnSkill(); lst.Sort(comp);
foreach (Block bl in lst) // Вывод сортированного списка
Console.WriteLine("{0,-12}\t{1,-8}\t{2}\t{3}", bl.prof, bl.name, bl.skil, bl.wage); Console.WriteLine("\nПоиск");
blk = lst.Find(le => le.prof == "токарь" && le.name == "Иванов"); Console.WriteLine("{0,-12}\t{1,-8}\t{2}\t{3}",
blk.prof, blk.name, blk.skil, blk.wage); Console.WriteLine("\n Подсчет количества выбранных элементов"); int count = 0;
string fstr = blk.prof;// Блок blk был найден при поиске foreach (Block bl in lst)
{
if (bl.prof == fstr) count++;// Количество блоков с заданным элементом
}
Console.WriteLine("Количество элементов \"{0}\" = {1}",fstr, count); Console.WriteLine("\n Выборка с помощью фильтра");
// Список flst для размещения отфильтрованных блоков
List<Block> flst = new List<Block>();
double fwg = lst[1].wage;// Выбрана зарплата foreach (Block bl in lst)
{ // Фильтрация
if (bl.wage == fwg) flst.Add(new Block(bl.prof, bl.name, bl.skil, bl.wage));
}
foreach (Block bl in flst)
{ // Просмотр результатов фильтрации
Console.WriteLine("{0,-12}\t{1,-8}\t{2}\t{3}",
bl.prof, bl.name, bl.skil, bl.wage);
}
Console.ReadKey();

Чернов Э. А. |
- 92 - |
Лекции по языку C# v 2.3 |
}
Ниже представлен результат выполнения программы.
Классы Queue и Stack также являются последовательными контейнерами, но изменяют доступ к элементам контейнера для получения специализированных списков (очередей и стеков).
Словари
Язык C# позволяет работать со словарями, содержащих в качестве элементов пары <Ключ> → <Значение>. Где «Ключ» жестко связан за следующим за ним значением. Примером может служить любой словарь иностранного языка.
Ассоциативные множества (словари) состоят из следующих типов:
Класс HashTable. (это словарь, для доступа к элементам которого выполняется преобразование ключа, и доступ к элементам выполняется с помощью этого преобразованного ключа). Обобщенный вариант класса HashTable называется
Dictionary. Имеется также класс SortedList и SortedDictionary.
Множества HashSet<T> и SortedSet<T>, содержат списки, обычно строк, например, списки для реализации IntelliSense (Подсказка для выбора компонентов при разработке программ).
Для реализации словарей существует универсальный класс Dictionary<TKey, TValue>. При реализации словаря ключ не может быть пустым. Нельзя добавить в
Чернов Э. А. |
- 93 - |
Лекции по языку C# v 2.3 |
словарь значение, не задав ключ. Но для одного ключа может быть несколько значений (если оно является ссылочным значением), например, у одного абонента может быть несколько телефонов. Для разных ключей могут быть одинаковые значения.
Ключи в словаре должны быть уникальны. Однако, понятие уникальности требует уточнений, поскольку понятие равенства не однозначно. Так слова могут отличаться строчными и заглавными буквами. Поэтому имеются интерфейсы IEqualityComparer<T> и IEquatable<T>, которые позволяют уточнить условие равенства.
Если создан пустой объект класса Dictionary<TKey, TValue> для добавления элементов в словарь применяется метод Add. При добавлении часто требуется проверка отсутствия нового ключа. Это можно выполнить несколькими способами. Пусть объявлен пустой словарь:
Dictionary<string, string> Dic = new Dictionary<string, string>();
В этот словарь добавляются строки:
Dic.Add("кошка", "ловит мышей"); Dic.Add("собака", "сторожит дом");
Тогда проверку отсутствия нового ключа при добавлении элемента требуется:
Проверить добавление с помощью блока try-catch и обработать исключительную ситуацию ArgumentException. (в блок вставить соответствующий оператор)
Использовать индексатор и с помощью блока try-catch обработать исключи-
тельную ситуацию KeyNotFoundException. Пример добавления try
{
Dic.AddT [T"мышь"T] = T"вредитель".
}
catch (KeyNotFoundException) { действие}
Использовать метод TryGetValue для проверки существования ключа. Пример
if (Dic.TryGetValue("кошка", out value)) // Найден
Использовать метод ContainsKey для проверки существования ключа
if (!Dic.ContainsKey("корка")) |
// Не найден |
В примере ниже показано, как выполнить перечисление ключей и значений в словаре, используя свойства Keys и Values класса KeyValuePair. Ключами являются фамилии, причем в файле нет дублирования фамилий. (Если фамилия будет продублирована, будет выдана исключительная ситуация ArgumentException)
class Program
{
class Block
{
public string sl;

Чернов Э. А. |
- 94 - |
Лекции по языку C# v 2.3 |
public int skil; public double wage;
public Block(string s, int k, double w)
{
sl = s; skil = k; wage = w;
}
}
static void Main(string[] args)
{
StreamReader file = new StreamReader
("C:\\Temp\\blockd.txt", Encoding.Default);
string s; int sk; double wg; string[ ] str;
Dictionary<string, Block> proflst = new Dictionary<string, Block>(); while ((s = file.ReadLine()) != null)
{
str = s.Split(' ');
sk = int.Parse(str[2]);
wg = double.Parse(str[3]);
proflst.Add(str[0], new Block( str[1], sk, wg));
}
foreach (KeyValuePair<string, Block> kvp in proflst) Console.WriteLine("{0,-12}\t{1,-8}\t{2}\t{3}",
kvp.Key, kvp.Value.sl, kvp.Value.skil, kvp.Value.wage);
Console.ReadKey();
}
При объявлении словаря типа Dictionary словарь не сортируется по ключу, и вывод будет иметь вид (сохранено порядок следования элементов в файле).
Если заменить строку объявления, записав вместо Dictionary сортированный спи-
сок SortedList
SortedList<string, Block> proflst = new SortedList<string, Block>();
То вывод будет иметь вид. Список отсортирован по фамилиям.
Если объявлена переменная типа KeyValuePair (в примере kvp), с ее помощью можно вывести на экран только ключи, либо только значения. В нашем примере «значением» является объект класса Block, поэтому для обращения к полям этого
Чернов Э. А. |
- 95 - |
Лекции по языку C# v 2.3 |
класса применяется форма kvp.Value.skil. (первая точка для обращения к свойству Value объекта kvp, вторая точка к полю класса Block).
Инициализаторы коллекций
При создании коллекций можно всегда вызывать метод Add (), например, как показано выше в примере реализации интерфейсов сравнения. Но при создании коллекции можно сократить запись, указав список инициализации, для которого компилятор сформирует автоматические вызовы метода Add (), подставляя в качестве параметров значения из этого списка. Синтаксис аналогичен инициализации массивов: после вызова пустого конструктора в фигурных скобках через запятую перечисляют значения для инициализации списка. Числа перечисляются без изменений, символы заключаются в апострофы, а строки в кавычки. Если коллекция содержит объекты класса, то для инициализации применяется список, каждый элемент которого состоит из операции new, имени_класса и в круглых скобках перечень параметров для конструктора класса. Ниже перечислены основные варианты инициализации списков (и словаря)
List <int> iLst = new List<int>() { 1, 2, 3 };
List <char> cLst = new List<char>() { 'a', 'd', 'h' };
List<string> sLst = new List<string>() {"Понедельник", "Вторник", "Среда" }; List<Product> pLst = new List<Product>() {new Product{Name="Пицца", Price=83},
new Product{Name="Багет",Price=65} };
Dictionary<string, string> d = new Dictionary<string, string>()
{
{"cat", "кошка"},{"dog", "собака"},
};
Итераторы
Для обращения к элементам списка можно реализовать интерфейсы IEnumerator и IEnumerable. В последних версиях Visual Studio эти интерфейсы, часто, реализуются автоматически компилятором. Но можно использовать итератор, который представляет собой метод, оператор или аксессор, возвращающий по очереди члены совокупности объектов от ее начала и до конца. Отличие итератора от индекса заключается в том, что итератор не связан с типом списка и позволяет обращаться к элементам связного списка так же, как и к элементам обычного массива. Более того, при создании коллекций с применением итераторов вся коллекция сразу не создается, формируется только текущий элемент.
Итератором является метод, который формирует перечисляемые последовательности, используя специальное ключевое слово yield. В примере ниже показан простой итератор и пример кода, в котором он используется. Выводятся числа от 1 до 5.
Простой итератор
public static IEnumerable<int> Numbers(int start, int count)
{
for (int i = 0; i < count; ++i)
{
yield return start + i;
}
}
Чернов Э. А. |
- 96 - |
Лекции по языку C# v 2.3 |
static void Main(string[] args)
{
foreach (int i in Numbers(1, 5))
{
Console.WriteLine(i);
}
}
Итератор похож на обычный метод, но различен способ возврата значения. Итератор в примере имеет код возврата IEnumerable<int>, и не видно, чтобы он что-либо возвращал этого типа. Он не содержит обычного оператора возврата, и содержит только оператор yield return, и он возвращает одиночное число типа int, а не коллекцию. Итераторы формируют значения по одному за раз с помощью оператора yield return, и в отличие от обычного возврата метод продолжает выполняться, пока либо достигнет конца, либо будет остановлен преждевременно с помощью оператора yield break, либо возникнет исключительная операция. Каждый оператор формирует значение, которое вставляется в последовательность. В следующем примере это показано более четко, где формируются числа 1 ÷ 3.
Очень простой итератор
public static IEnumerable<int> ThreeNumbers()
{
yield return 1; yield return 2; yield return 3;
}
Хотя это простая концепция, способ реализации в чем-то отличен, поскольку итераторы не выполняются таким же способом, как и другой код. Напомним, что с помощью IEnumerable<T> вызывающая программа занята при извлечении следующего значения. Цикл foreach получает перечислитель и повторяет вызов метода MoveNext() до тех пор, пока не будет возвращено значение false и не будет обращения к свойству Current для получения текущего значения. Однако значения перечислителя не хранятся в списке типа List<T>
Ниже приведен простой пример итератора, в котором вместо явной реализации интерфейсов IEnumerator и IEnumerable применяется итератор.
// Простой пример применения итератора. class MyClass
{
string[ ] strs = { "Anchor", "Broadcast", "City", "Danger" };
// Этот итератор возвращает строки из массива strs в классе MyClass. public IEnumerator<string> GetEnumerator()
{
foreach (string st in strs) yield return st;
}
}
class Iterator
{