Занятие №64
Понятие коллекции. Классы, возможности, правила работы, встроенные методы
Коллекции предоставляют гибкий способ работы с группами объектов. В отличие от массивов, коллекция, с которой вы работаете, может расти или уменьшаться динамически при необходимости. Некоторые коллекции допускают назначение ключа любому объекту, который добавляется в коллекцию, чтобы в дальнейшем можно было быстро извлечь связанный с ключом объект из коллекции.
Коллекция является классом, поэтому необходимо объявить экземпляр класса перед добавлением в коллекцию элементов.
Большая часть классов коллекций содержится в пространствах имен System.Collections (простые необобщенные_классы_коллекций), System.Collections.Generic (обобщенные или типизированные классы коллекций) и System.Collections.Specialized
(специальные классы коллекций).
Также для обеспечения параллельного выполнения задач и многопоточного доступа применяются классы коллекций из пространства имен System.Collections.Concurrent .
Коллекции общего назначения определены в пространстве имен System.Collection и реализуют такие структуры данных, как стеки, очереди, динамические массивы, словари (хеш-таблицы, предназначенные для хранения пар ключ/значение), отсортированный список для хранения пар ключ/значение.
Коллекции общего назначения работают с данными типа object , поэтому их можно использовать для хранения данных любого типа.
Коллекции специального назначения определены в пространстве имен System.Collection.Specialized и ориентированы на обработку данных конкретного типа или на обработку данных уникальным способом. Например, существуют специализированные коллекции, предназначенные только для обработки строк.
В пространстве имен System.Collection определена единственная коллекция, ориентированная на побитовую организацию данных, которая служит для хранения групп битов и поддерживает такой набор операций, который не характерен для коллекций других типов.
Коллекции общего назначения
Классы коллекций общего назначения:
Список List
Класс List из пространства имен System.Collections.Generic представляет простейший список однотипных объектов. Список называется однонаправленным , если каждый элемент списка содержит ссылку на следующий элемент. Среди его методов можно выделить следующие:
- void Add(T item): добавление нового элемента в список
- void AddRange(ICollection collection): добавление в список коллекции или массива
- int BinarySearch(T item): бинарный поиск элемента в списке. Если элемент найден, то метод возвращает индекс этого элемента в коллекции. При этом список должен быть отсортирован.
- int IndexOf(T item): возвращает индекс первого вхождения элемента в списке
- void Insert(int index, T item): вставляет элемент item в списке на позицию index
- bool Remove(T item): удаляет элемент item из списка, и если удаление прошло успешно, то возвращает true
Реализация списка на примере:
using System;
using System.Collections.Generic;
namespace Collections
{
class Program
{
static void Main(string[] args)
{
List numbers = new List() { 1, 2, 3, 45 };
numbers.Add(6); // добавление элемента
numbers.AddRange(new int[] { 7, 8, 9 });
numbers.Insert(0, 666); // вставляем на первое место // в списке число 666
numbers.RemoveAt(1); // удаляем второй элемент
foreach (int i in numbers)
{
Console.WriteLine(i);
}
List people = new List(3);
people.Add(new Person() { Name = " Том " });
people.Add(new Person() { Name = " Билл " });
foreach (Person p in people)
{
Console.WriteLine(p.Name);
}
Console.ReadLine();
}
}
class Person
{
public string Name { get; set; }
}
}
В примере создаются два списка: один для объектов типа int, а другой - для объектов Person. В первом случае мы выполняем начальную инициализацию списка: List numbers = new List() { 1, 2, 3, 45 };
Во втором случае мы используем другой конструктор, в который передаем начальную емкость списка: List people = new List(3);. Указание начальной емкости списка (capacity) позволяет в будущем увеличить производительность и уменьшить издержки на выделение памяти при добавлении элементов. Также начальную емкость можно установить с помощью свойства Capacity, которое имеется у класса List.
Двухсвязный список LinkedList
Класс LinkedList представляет двухсвязный список, в котором каждый элемент хранит ссылку одновременно на следующий и на предыдущий элемент.
Если в простом списке List каждый элемент представляет объект типа T, то в LinkedList каждый узел представляет объект класса LinkedListNode. Этот класс имеет следующие свойства:
Value: само значение узла, представленное типом T
Next: ссылка на следующий элемент типа LinkedListNode в списке. Если следующий элемент отсутствует, то имеет значение null
Previous: ссылка на предыдущий элемент типа LinkedListNode в списке. Если предыдущий элемент отсутствует, то имеет значение null.
Используя методы класса LinkedList, можно обращаться к различным элементам, как в конце, так и в начале списка:
- AddAfter(LinkedListNode node, LinkedListNode newNode ) : вставляет узел newNode в список после узла node.
- AddAfter(LinkedListNode node, T value) : вставляет в список новый узел со значением value после узла node.
- AddBefore(LinkedListNode node, LinkedListNode newNode) : вставляет в список узел newNode перед узлом node.
- AddBefore(LinkedListNode node, T value) : вставляет в список новый узел со значением value перед узлом node.
- AddFirst(LinkedListNode node) : вставляет новый узел в начало списка
- AddFirst(T value) : вставляет новый узел со значением value в начало списка
- AddLast(LinkedListNode node) : вставляет новый узел в конец списка
- AddLast(T value) : вставляет новый узел со значением value в конец списка
- RemoveFirst() : удаляет первый узел из списка. После этого новым первым узлом становится узел, следующий за удаленным
- RemoveLast() : удаляет последний узел из списк
Пример:
using System;
using System.Collections.Generic;
namespace Collections
{
class Program
{
static void Main(string[] args)
{
LinkedList numbers = new LinkedList();
numbers.AddLast(1); // вставляем узел со значением 1 на
//последнее место
// так как в списке нет узлов, то последнее будет также и первым
numbers.AddFirst(2); // вставляем узел со значением 2 на
// первое место
numbers.AddAfter(numbers.Last, 3); // вставляем после последнего
// узла новый узел со значением 3
// теперь у нас список имеет следующую последовательность:
//2, 1, 3
foreach (int i in numbers)
{
Console.WriteLine(i);
}
LinkedList persons =
new LinkedList();
// добавляем person в список и получим объект LinkedListNode,
// в котором хранится имя Tom
LinkedListNode tom = persons.AddLast(new Person() { Name = "Tom" });
persons.AddLast(new Person() { Name = "John" });
persons.AddFirst(new Person() { Name = "Bill" });
Console.WriteLine(tom.Previous.Value.Name);
// получаем узел перед Том и его значение
Console.WriteLine(tom.Next.Value.Name); // получаем узел // после тома и его значение
Console.ReadLine();
}
}
class Person
{
public string Name { get; set; }
}
}
Здесь создаются и используются два списка: для чисел и для объектов класса Person. Методы вставки (AddLast, AddFirst) при добавлении в список возвращают ссылку на добавленный элемент LinkedListNode (в нашем случае LinkedListNode). Затем управляя свойствами Previous и Next , можно получить ссылки на предыдущий и следующий узлы в списке.
Очередь Queue
Класс Queue представляет обычную очередь, работающую по алгоритму FIFO ("первый вошел - первый вышел").
У класса Queue можно отметить следующие методы:
- Dequeue: извлекает и возвращает первый элемент очереди.
- Enqueue: добавляет элемент в конец очереди.
- Peek: просто возвращает первый элемент из начала очереди без его удаления.
Пример:
using System;
using System.Collections.Generic;
namespace Collections
{
class Program
{
static void Main(string[] args)
{
Queue numbers = new Queue();
numbers.Enqueue(3); // очередь 3
numbers.Enqueue(5); // очередь 3, 5
numbers.Enqueue(8); // очередь 3, 5, 8
// получаем первый элемент очереди
int queueElement = numbers.Dequeue();
// теперь очередь 5, 8
Console.WriteLine(queueElement);
Queue persons = new Queue();
persons.Enqueue(new Person() { Name = "Tom" });
persons.Enqueue(new Person() { Name = "Bill" });
persons.Enqueue(new Person() { Name = "John" });
// получаем первый элемент без его извлечения
Person pp = persons.Peek();
Console.WriteLine(pp.Name);
Console.WriteLine(" Сейчас в очереди {0} человек ",
- Console.WriteLine(" Сейчас в очереди {0} человек ",
persons.Count);
- persons.Count);
// теперь в очереди Tom, Bill, John
foreach (Person p in persons)
{
Console.WriteLine(p.Name);
}
// Извлекаем первый элемент в очереди - Tom
Person person = persons.Dequeue();
// теперь в очереди Bill, John
Console.WriteLine(person.Name);
Console.ReadLine();
}
}
class Person
{
public string Name { get; set; }
}
}
Коллекция Stack
Класс Stack представляет коллекцию, которая использует алгоритм LIFO ("последний вошел - первый вышел"). При такой организации каждый следующий добавленный элемент помещается поверх предыдущего. Извлечение из коллекции происходит в обратном порядке - извлекается тот элемент, который находится выше всех в стеке.
В классе Stack можно выделить два основных метода, которые позволяют управлять элементами:
- Push : добавляет элемент в стек на первое место
- Pop : извлекает и возвращает первый элемент из стека
- Peek : просто возвращает первый элемент из стека без его удаления
Пример:
using System;
using System.Collections.Generic;
namespace Collections
{
class Program
{
static void Main(string[] args)
{
Stack numbers = new Stack();
numbers.Push(3); // в стеке 3
numbers.Push(5); // в стеке 5, 3
numbers.Push(8); // в стеке 8, 5, 3
// так как вверху стека будет находиться число 8, то оно и
// извлекается
int stackElement = numbers.Pop(); // в стеке 5, 3
Console.WriteLine(stackElement);
Stack persons = new Stack();
persons.Push(new Person() { Name = "Tom" });
persons.Push(new Person() { Name = "Bill" });
persons.Push(new Person() { Name = "John" });
foreach (Person p in persons)
{
Console.WriteLine(p.Name);
}
// Первый элемент в стеке
Person person = persons.Pop(); // теперь в стеке Bill, Tom
Console.WriteLine(person.Name);
Console.ReadLine();
}
}
class Person
{
public string Name { get; set; }
}
}
Работу стека можно представить следующей иллюстрацией:
Коллекция Dictionary
Еще один распространенный тип коллекции - это словари. Словарь хранит объекты, которые представляют пару ключ-значение. Каждый такой объект является объектом структуры KeyValuePair . Благодаря свойствам Key и Value, которые есть у данной структуры, мы можем получить ключ и значение элемента в словаре.
Пример:
Dictionary countries = new Dictionary(5);
countries.Add(1, "Russia");
countries.Add(3, "Great Britain");
countries.Add(2, "USA");
countries.Add(4, "France");
countries.Add(5, "China");
foreach (KeyValuePair keyValue in countries)
{
Console.WriteLine(keyValue.Key + " - " + keyValue.Value);
}
// получение элемента по ключу
string country = countries[4];
// изменение объекта
countries[4] = "Spain";
// удаление по ключу
countries.Remove(2);
Класс словарей предоставляет методы Add и Remove для добавления и удаления элементов. В метод Add передаются два параметра: ключ и значение. А метод Remove удаляет не по индексу, а по ключу.
В примере ключами является объекты типа int, а значениями - объекты типа string, то словарь в нашем случае будет хранить объекты KeyValuePair. В цикле foreach их можно получить и извлечь из них ключ и значение.
Кроме того, можно получить отдельно коллекции ключей и значений словаря:
Dictionary people = new Dictionary();
people.Add('b', new Person() { Name = "Bill" });
people.Add('t', new Person() { Name = "Tom" });
people.Add('j', new Person() { Name = "John" });
foreach (KeyValuePair keyValue in people)
{
// keyValue.Value представляет класс Person
Console.WriteLine(keyValue.Key + " - " + keyValue.Value.Name);
}
// перебор ключей
foreach (char c in people.Keys)
{
Console.WriteLine(c);
}
// перебор по значениям
foreach (Person p in people.Values)
{
Console.WriteLine(p.Name);
}
Здесь в качестве ключей выступают объекты типа char, а значениями - объекты Person. Используя свойство Keys, мы можем получить ключи словаря, а свойство Values соответственно хранит все значения в словаре.
Для добавления можно использовать сокращенный вариант:
Dictionary people = new Dictionary
Person();
people.Add('b', new Person() { Name = "Bill" });
people['a'] = new Person() { Name = "Alice" };
Изначально в словаре нет ключа 'a' и соответствующего ему элемента, то он все равно будет установлен. Если же он есть, то элемент по ключу 'a' будет заменен на новый объект new Person() { Name = "Alice" }
Инициализация словарей
В C# до версии 5.0 включительно можно было инициализировать словари следующим образом:
Dictionary countries = new Dictionary
{
{"Франция", "Париж"},
{"Германия", "Берлин"},
{"Великобритания", "Лондон"}
};
foreach(var pair in countries)
Console.WriteLine("{0} - {1}", pair.Key, pair.Value);
Начиная с C# версии 6.0 доступен еще один способ инициализации:
Dictionary countries = new Dictionary
{
["Франция"]= "Париж",
["Германия"]= "Берлин",
["Великобритания"]= "Лондон"
};
Класс ObservableCollection
Кроме стандартных классов коллекций типа списков, очередей, словарей, стеков .NET также предоставляет специальный класс ObservableCollection . Он по функциональности похож на список List за тем исключением, что позволяет известить внешние объекты о том, что коллекция была изменена.
Класс ObservableCollection находится в пространстве имен System.Collections.ObjectModel , кроме того понадобятся ряд объектов из пространства System.Collections.Specialized , поэтому в начале подключаются эти пространства имен.
Класс ObservableCollection определяет событие CollectionChanged , подписавшись на которое, можно обработать любые изменения коллекции.
В обработчике события Users_CollectionChanged для получения всей информации о событии используется объект NotifyCollectionChangedEventArgs e . Его свойство Action позволяет узнать характер изменений. Оно хранит одно из значений из перечисления NotifyCollectionChangedAction .
Свойства NewItems и OldItems позволяют получить соответственно добавленные и удаленные объекты. Таким образом, можно получить полный контроль над обработкой добавления, удаления и замены объектов в коллекции.
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace HelloApp
{
class Program
{
static void Main(string[] args)
{
ObservableCollection users = new ObservableCollection
{
new User { Name = "Bill"},
new User { Name = "Tom"},
new User { Name = "Alice"}
};
users.CollectionChanged += Users_CollectionChanged;
users.Add(new User { Name = "Bob" });
users.RemoveAt(1);
users[0] = new User{ Name = "Anders" };
foreach(User user in users)
{
Console.WriteLine(user.Name);
}
Console.Read();
}
private static void Users_CollectionChanged(object sender,
NotifyCollectionChangedEventArgs e)
{
switch(e.Action)
{
case NotifyCollectionChangedAction.Add :
// если добавление
User newUser = e.NewItems[0] as User;
Console.WriteLine($" Добавлен новый объект : {newUser.Name}");
break;
case NotifyCollectionChangedAction.Remove:
// если удаление
User oldUser = e.OldItems[0] as User;
Console.WriteLine($" Удален объект : {oldUser.Name}");
break;
case NotifyCollectionChangedAction.Replace: // если замена
User replacedUser = e.OldItems[0] as User;
User replacingUser = e.NewItems[0] as User;
Console.WriteLine($" Объект {replacedUser.Name} заменен объектом {replacingUser.Name}");
break;
}
}
}
class User
{
public string Name { get; set; }
}
}
ArrayList
Класс ArrayList представляет коллекцию объектов. Если надо сохранить вместе разнотипные объекты - строки, числа и т.д., то данный класс для этого подходит.
using System;
using System.Collections;
namespace Collections
{
class Program
{
static void Main(string[] args)
{
ArrayList list = new ArrayList();
list.Add(2.3); // заносим в список объект типа double
list.Add(55); // заносим в список объект типа int
list.AddRange(new string[] { "Hello", "world" });
// заносим в список строковый массив
// перебор значений
foreach (object o in list)
{
Console.WriteLine(o);
}
// удаляем первый элемент
list.RemoveAt(0);
// переворачиваем список
list.Reverse();
// получение элемента по индексу
Console.WriteLine(list[0]);
// перебор значений
for (int i = 0; i
{
Console.WriteLine(list[i]);
}
Console.ReadLine();
}
}
}
Класс ArrayList находится в пространстве имен System.Collections, поэтому подключаем его (using System.Collections;).
Создаем объект коллекции через конструктор как объект любого другого класса: ArrayList list = new ArrayList();. При необходимости можно выполнить начальную инициализацию коллекции, например, ArrayList list = new ArrayList(){1, 2, 5, "string", 7.7};
Далее последовательно добавляем разные значения. Данный класс коллекции, как и большинство других коллекций, имеет два способа добавления: одиночного объекта через метод Add и набора объектов, например, массива или другой коллекции через метод AddRange.
Через цикл foreach мы можем пройтись по всем объектам списка. И поскольку данная коллекция хранит разнородные объекты, а не только числа или строки, то в качестве типа перебираемых объектов выбран тип object: foreach (object o in list)
Многие коллекции, в том числе и ArrayList , реализуют удаление с помощью методов Remove/RemoveAt . В данном случае мы удаляем первый элемент, передавая в метод RemoveAt индекс удаляемого элемента.
В завершении мы опять же выводим элементы коллекции на экран только уже через цикл for. В данном случае с перебором коллекций дело обстоит также, как и с массивами. А число элементов коллекции мы можем получить через свойство Count
С помощью индексатора мы можем получить по индексу элемент коллекции так же, как и в массивах: object firstObj = list[0];