Comparer<T> Comparer
Dictionary<K.T> HashTable
LinkedList<T> -
List<T> ArrayList
Queue<T> Queue
SortedDictionary<K.T> SortedList
Stack<T> Stack
У коллекций, описанных в библиотеке NET версий 1.0 и 1.1, есть два основных недостатка, обусловленных тем, что в них хранятся ссылки на тип object:
□ в одной и той же коллекции можно хранить элементы любого типа, следовательно, ошибки при помещении в коллекцию невозможно проконтролировать на этапе компиляции, а при извлечении элемента требуется его явное преобразование;
□ при храпении в коллекции элементов значимых типов выполняется большой
объем действий по упаковке и распаковке элементов, что в значительной степени снижает эффективность работы.
Параметром класса-прототипа является тип данных, с которым он работает. Это избавляет от перечисленных недостатков. В качестве примера рассмотрим применение универсального «двойника» класса ArrayList - класса List<T> - для хранения коллекции объектов классов Monster и Daemon, которые разрабатывались в главах 5 и 8, а также для хранения целых чисел1.
|
|
Листинг 13.2. Использование универсальной коллекции List<T>
using System;
using System.ColIections.Geneneriс;
using System.Text;
namespace ConsoleApplication1
{
using MonsterLib;
class Program
{
static void Main()
{
List<Monster> stado = new List<Monster>();
stado.Add(new Monster("Monia"));
stado.Add(new Monster("Monk"));
stado.Add(new Daemon("Dimon", 3));
foreach (Monster x in stado)
x.Passport();
List<int> lint = new List<int>();
lint.Add(5);
lint.Add(1);
lint.Add(3);
lint.Sort();
int a = lint[2];
Console.Writel_ine(a);
foreach (int x in lint)
Console.Write(x + " ");
}
}
}
Результат работы программы:
Monster Monia health = 100 ammo - 100
Monster Monk health - 100 ammo = 100
Daemon Dimon health = 100 ammo = 100 brain - 3
1 3 5
В листинге 13.2 две коллекции. Первая (stado) содержит элементы пользовательских классов, которые находятся в библиотеке MonsterLib.dll, созданной в предыдущей главе. В коллекции, для которой объявлен тип элементов Monster, благодаря полиморфизму можно хранить элементы любого производного класса, но не элементы других типов.
Казалось бы, по сравнению с обычными коллекциями это ограничение, а не универсальность, однако на практике коллекции, в которых действительно требуется хранить значения различных, не связанных межу собой типов, почти не используются. Достоинством же такого ограничения является то, что компилятор может выполнить контроль типов во время компиляции, а не выполнения программы, что повышает ее надежность и упрощает поиск ошибок. Коллекция lint состоит из целых чисел, причем для работы с ними не требуются ни операции упаковки и распаковки, ни явные преобразования типа при получении элемента из коллекции, как это было в обычных коллекциях (см. листинг 13.1). Классы-прототипы называют также родовыми или шаблонными, поскольку они представляют собой образцы, по которым во время выполнения программы строятся конкретные классы. При этом сведения о классах, которые являются параметрами классов-прототипов, извлекаются из метаданных.
|
|
ВНИМАНИЕ
Использование стандартных параметризованных коллекций для хранения и обработки данных является хорошим стилем программирования, поскольку позволяет сократить сроки разработки программ и повысить их надежность. Рекомендуется тщательно изучить по документации свойства и методы этих классов и выбирать наиболее подходящие в зависимости от решаемой задачи.
В листинге 13.3 приведен еще один пример применения параметризованных коллекций. Программа считывает содержимое текстового файла, разбивает его на слова и подсчитывает количество повторений каждого слова в тексте. Для хранения слов и числа их повторений используется словарь Dictionary<T.K>. У этого класса два параметра: тип ключей и тип значений, хранимых в словаре. В качестве ключей используются слова, считанные из файла, а значения представляют собой счетчики целого типа, которые увеличиваются на единицу, когда слово встречается в очередной раз.
Листинг 13.3. Формирование частотного словаря
using System;
using System.Collections.Generiс;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
StreamReader f = new StreamReader(@"d:\C#\text.txt"); // 1
string s = f.ReadToEnd(); // 2
char[] separators = { '.', ' ', '.', '!' }; //3
List<string> words = new List<string>(s.Split(separators)); // 4
Dictionary<string, int> map = new Dictionary<string, int>(); // 5
foreach (string w in words) // 6
{
if (map.ContainsKey(w)) map[w]++;
else map[w] = 1;
}
foreach (string w in map.Keys) // 7
Console.WriteLine("{0}\t{1}", w.map[w]);
}
}
}
Пусть исходный файл text.txt содержит строки
Ехал Грека через реку. Видит Грека, в реке рак.
Сунул Грека в реку руку, рак за руку Греку цап!
Тогда результат работы программы будет выглядеть так:
Ехал 1
Грека 3
через 1
реку 2
4
Видит 1
в 2
реке 1
рак 2
Сунул 1
руку 2
за 1
Греку 1
цап 1
Несколько пояснений к программе. В операторе 1 открывается текстовый файл, длина которого не должна превышать 32 767 символов, потому что в операторе 2 все его содержимое считывается в отдельную строку.
ПРИМЕЧАНИЕ
Конечно, для реальной работы такой способ не рекомендуется. Кроме того, для файлов, открываемых для чтения, программа обязательно должна обрабатывать исключение FileNotFoundException (см. главу 11).
В операторе 3 задается массив разделителей, передаваемый в качествепараметра методу Split, формирующему массив строк, каждая из которых содержит отдельное слово исходного файла. Этот массив используется для инициализации экземпляра words класса List<string>. Применение стандартного класса позволяет не заботиться о выделении места под массив слов.
Оператор 5 описывает словарь, а в цикле 6 выполняется его заполнение путем просмотра списка слов words. Если слово встречается впервые, в значение, соответствующее слову как ключу, заносится единица. Если слово уже встречалось, значение увеличивается на единицу.
В цикле 7 выполняется вывод словаря путем просмотра всех его ключей (для этого используется свойство словаря Keys, возвращающее коллекцию ключей) и выборки соответствующих значений.
Метод Split не очень интеллектуален: он рассматривает пробел, расположенный после знака препинания, как отдельное слово. Для более точного разбиения на слова используются регулярные выражения, которые рассматриваются в главе 15.
ПРИМЕЧАНИЕ-
Обратите внимание на то, насколько использование стандартных коллекцийсокращает исходный текст программы. Конечно, на тщательное изучение их возможностей требуется много времени, однако это окупается многократно.
Для полноты картины следует добавить, что наряду с параметризованными классами в пространстве имен System.Collections.Generic описаны параметризованные интерфейсы, перечисленные в табл. 13.6.
|
|
Таблица 13.6. Параметризованные интерфейсы библиотеки.NET версии 2.0
Параметризованный интерфейс (версия 2.0) Обычный интерфейс
ICollection<T> ICollection
IComparable<T> IComparable
IDictionary<K.T> IDictionary
IEnumerable<T> IEnumerable
IEnumerator<T> IEnumerator
IList<T> IList