В отличие от директив препроцессора, которыми требуется обрамлять каждый вызов метода, атрибут Conditional достаточно указать в одном месте.
Атрибут Obsolete используется для того, чтобы пометить элемент как устаревший. При записи атрибута может указывается строка-сообщение, выводимая компилятором, если устаревший элемент применяется в коде. Рассмотрим следующий пример:
using System;
class MainClass {
[Obsolete(@"Old Function – Don't use!")]
public static void SomeFunc() {
Console.WriteLine("SomeFunc");
}
public static void Main() {
SomeFunc();
Console.WriteLine("Hello!");
}
}
При компиляции данного проекта будет выведено предупреждение:
'MainClass.SomeFunc()' is obsolete:'Old Function – Don't use!'
Перегруженная версия конструктора атрибута Obsolete может кроме строки принимать булевый параметр. Если значение параметра true, то при компиляции кода с использованием устаревшего элемента генерируется не просто предупреждение, а ошибка:
[Obsolete("Old Function - Do not use it!", true)]
public static void SomeFunc() {Console.WriteLine("SomeFunc");}
public static void Main() {
//Теперь эта строка просто не компилируется!
SomeFunc();
Console.WriteLine("Hello!");
}
Для пользовательского проекта некоторые среды программирования (MS Visual Studio .NET, SharpDevelop) генерируют специальный файл AssemblyInfo.cs. Этот файл содержит код, автоматически встраиваемый в сборку. Фактически, файл представляет собой набор атрибутов сборки. Вот пример данного файла (с удаленными комментариями):
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("AlexV")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]
Автор проекта может записать собственные значения для параметров атрибутов сборки. В этом случае при просмотре свойств файла в Windows эти значения будут отображаться в виде строк на соответствующей закладке.
2.3. ПРОСТРАНСТВО ИМЕН SYSTEM.COLLECTIONS
Пространство имен System.Collections содержит классы и интерфейсы, которые служат для поддержки наборов (или коллекций) данных, организованных в виде стандартных структур данных – список, хэш-таблица, стек и т. д. Кроме этого, некоторые интерфейсы из описываемого пространства имен удобны для использования при сортировках, перечислении элементов массивов и структур. В данном параграфе будут описаны базовые приемы работы с элементами из пространства имен System.Collectons, а также пространства имен System.Collections.Specialized и System.Collectons.Generics.
Рассмотрим интерфейсы из пространства имен System.Collections и дадим их краткое описание.
Рис. 4. Интерфейсы из System.Collections
Интерфейс IEnumerator представляет поддержку для набора объектов возможности перебора в цикле foreach.
Интерфейс ICollection служит для описания некоторого набора объектов. Этот интерфейс имеет свойство Count для количества элементов в наборе, а также метод CopyTo() для копирования набора в массив Array (CopyTo).
Интерфейс IList описывает набор данных, которые проецируются на массив. Приведем описание данного интерфейса, назначение его методов и свойств очевидно:
interface IList : ICollection, IEnumerable {
bool IsFixedSize { get; }
bool IsReadOnly { get; }
object this[int index] { get; set; }
int Add(object value);
void Clear();
bool Contains(object value);
int IndexOf(object value);
void Insert(int index, object value);
void Remove(object value);
void RemoveAt(int index);
}
Интерфейс IDictionary служит для описания коллекций, представляющих словари, то есть таких наборов данных, где доступ осуществляется с использованием произвольного ключа.
Интерфейс IEnumerator предназначен для организации перебора элементов коллекции, а интерфейс IDictionaryEnumerator кроме перебора элементов позволяет обратиться к ключу и значению текущего элемента. Назначение интерфейсов IHashCodeProvider и IComparer разъясняется далее по тексту.
На протяжении параграфа будем использовать следующий вспомогательный класс, хранящий информацию о студенте:
class CStudent {
public string name;
public int course;
public double ball;
public CStudent(string name, int course, double ball) {
Имея в своем распоряжении класс CStudent, мы можем организовать массив из объектов данного класса. Однако методы сортировки из класса Array для такого массива работать не будут. Для того чтобы выполнялась сортировка, необходимо, чтобы класс реализовывал интерфейс System.IComparable.
Интерфейс IComparable определен следующим образом:
interface IComparable {
int CompareTo(object o);
}
Метод CompareTo() сравнивает текущий объект с объектом o. Метод должен возвращать ноль, если объекты «равны», любое число больше нуля, если текущий объект «больше», и любое число меньше нуля, если текущий объект «меньше» сравниваемого.
Пусть мы хотим сортировать массив студентов по убыванию среднего балла. Внесем поддержку IComparable в класс CStudent:
class CStudent : IComparable {
. . .
public int CompareTo(object o) {
CStudent tmp = (CStudent) o;
if(ball > tmp.ball) return 1;
if(ball < tmp.ball) return -1;
return 0;
}
}
Класс CStudent теперь можно использовать таким образом:
CStudent[] Group = {new CStudent("Alex", 1, 7.0),
new CStudent("Ivan", 1, 9.0),
new CStudent("Anna", 1, 8.0),
new CStudent("Peter", 1, 5.0)};
Console.WriteLine("Unsorted group");
foreach (CStudent s in Group) {
Console.WriteLine(s);
}
Array.Sort(Group);
Array.Reverse(Group);
Console.WriteLine("Sorted group");
foreach (CStudent s in Group) {
Console.WriteLine(s);
}
Данная программа выводит следующее:
Unsorted group
Name = Alex; Course = 1; Ball = 7
Name = Ivan; Course = 1; Ball = 9
Name = Anna; Course = 1; Ball = 8
Name = Peter; Course = 1; Ball = 5
Sorted group
Name = Ivan; Course = 1; Ball = 9
Name = Anna; Course = 1; Ball = 8
Name = Alex; Course = 1; Ball = 7
Name = Peter; Course = 1; Ball = 5
Интерфейс IComparer из пространства имен System.Collections предоставляет стандартизированный способ сравнения любых двух объектов:
interface IComparer {
int Compare(object o1, object o2);
}
Использование IComparer позволяет осуществить сортировку по нескольким критериям. Для этого каждый критерий описывается вспомогательным классом, реализующим IComparer и определенный способ сравнения.
Возьмем за основу первоначальный вариант класса CStudent и опишем два вспомогательных класса следующим образом:
class SortStudentsByBall : IComparer {
public int Compare(object o1, object o2) {
CStudent t1 = (CStudent) o1;
CStudent t2 = (CStudent) o2;
if(t1.ball > t2.ball) return 1;
if(t1.ball < t2.ball) return -1;
return 0;
}
}
class SortStudentsByName : IComparer {
public int Compare(object o1, object o2) {
CStudent t1 = (CStudent) o1;
CStudent t2 = (CStudent) o2;
return String.Compare(t1.name, t2.name);
}
}
Теперь для сортировки по разным критериям можно использовать перегруженную версию метода Array.Sort(), принимающую в качестве второго параметра объект класса, реализующего интерфейс IComparer.
// Сортировка массива студентов по баллу
Array.Sort(Group, new SortStudentsByBall());
// Сортировка массива студентов по имени
Array.Sort(Group, new SortStudentsByName());
Язык C# имеет команду для перебора элементов коллекций – foreach. Пусть мы создали класс, хранящий некий набор элементов:
class Room {
private CStudent[] H;
// Создаем в конструкторе "комнату" - не лучшее решение!
public Room() {
H = new CStudent[3];
H[0] = new CStudent("Alex", 1, 7.0);
H[1] = new CStudent("Ivan", 1, 9.0);
H[2] = new CStudent("Peter", 1, 5.0);
}
}
Было бы удобно для перебора элементов в объектах нашего класса использовать команду foreach. Для этого необходимо организовать в классе поддержку интерфейса IEnumerable[4]. Интерфейс IEnumerable устроен очень просто. Он содержит единственный метод, задача которого – вернуть интерфейс IEnumerator.
interface IEnumerable {
IEnumerator GetEnumerator();
}
В свою очередь, интерфейс IEnumerator имеет следующее описание:
interface IEnumerator {
// Передвинуть курсор данных на следующую позицию
bool MoveNext();
// Свойство для чтения – текущий элемент
object Current { get; }
// Установить курсор перед началом набора данных
void Reset();
}
Очень часто интерфейсы IEnumerable и IEnumerator поддерживает один и тот же класс. Добавим поддержку данных интерфейсов в класс Room:
class Room : IEnumerable, IEnumerator {
private CStudent[] H;
// Внутренне поле для курсора данных
private int pos = -1;
// Конструктор – описан выше
public Room(){
. . .
}
public IEnumerator GetEnumerator() {
// Класс сам реализует IEnumerator
return (IEnumerator)this;
}
public bool MoveNext() {
if(pos < H.Length - 1) {
pos++;
return true;
}
else return false;
}
public void Reset() {
pos = -1;
}
public object Current {
get { return H[pos]; }
}
}
Теперь для перебора элементов класса Room мы можем использовать такой код:
Room R_1003 = new Room();
foreach(CStudent S in R_1003) {
Console.WriteLine(S);
}
В таблице 11 перечислен набор основных классов, размещенных в пространстве имен System.Collections.
Таблица 11
Классы из System.Collections
Имя класса
Класс представляет…
Класс реализует
интерфейсы…
ArrayList
Массив изменяемого размера с целочисленным индексом
IList, ICollection, IEnumerable,ICloneable
BitArray
Компактный массив бит с поддержкой битовых операций
ICollection, IEnumerable,ICloneable
CaseInsensitiveComparer
Класс с реализацией интерфейса IComparer, которая игнорирует регистр в случае сравнения строк
IComparer
CaseInsensitive-
HashCodeProvider
Класс с методом генерации хэш-кодов, игнорирующим регистр в случае генерации кодов для строк
IHashCodeProvider
CollectionBase
Абстрактный базовый класс для построения пользовательских типизированных коллекций
IList, ICollection, IEnumerable
Comparer
Класс с реализацией интерфейса IComparer, основанной на вызове методов CompareTo() сравниваемых объектов
IComparer
DictionaryBase
Абстрактный класс для построения пользовательских типизированных хэш-таблиц
IDictionary, ICollection, IEnumerable
Hashtable
Таблицу объектных пар «ключ-значение», оптимизированную для быстрого поиска по ключу
Абстрактный класс для построения пользовательских типизированных коллекций с доступом только для чтения
ICollection, IEnumerable
SortedList
Таблицу отсортированных по ключу пар «ключ-значение» (доступ к элементу – по ключу или по индексу)
IDictionary, ICollection, IEnumerable,ICloneable
Stack
Стек (LIFO)
ICollection, IEnumerable,ICloneable
Особенностью классов-коллекций является их слабая типизация. Элементами любой коллекции являются переменные типа object. Это позволяет коллекциям хранить данные любых типов (так как тип object – базовый для любого типа), однако вынуждает выполнять приведение типов при изъятии элемента из коллекции. Кроме этого, при помещении в коллекцию элемента структурного типа выполняется операция упаковки, что ведет к снижению производительности.
Класс ArrayList предназначен для представления динамических массивов, то есть массивов, размер которых изменяется при необходимости. Создание динамического массива и добавление в него элементов выполняется просто:
ArrayList list = new ArrayList();
list.Add("John");
list.Add("Paul");
list.Add("George");
list.Add("Ringo");
Метод Add() добавляет элементы в конец массива, автоматически увеличивая память, занимаемую объектом класса ArrayList (если это необходимо). Метод Insert() вставляет элементы в массив, сдвигая исходные элементы. Методы AddRange() и InsertRange() добавляют или вставляют в динамический массив произвольную коллекцию.
Для получения элементов динамического массива используется индексатор. Индексы – целые числа, начинающиеся с нуля. С помощью индексатора можно изменить значения существующего элемента[5]:
string st = (string)list[0];
list[0] = 999;
Свойство Count позволяет узнать количество элементов динамического массива. Для перебора элементов массива можно использовать цикл foreach:
for(int i=0; i<list.Count; i++)
Console.WriteLine(list[i]);
// Можно так (правда, это примерно на 25% медленнее)
foreach(int i in list)
Console.WriteLine(i);
Количество элементов динамического массива, которое он способен содержать без увеличения своего размера, называется емкостью массива. Емкость массива доступна через свойство Capacity, причем это свойство может быть как считано, так и установлено. По умолчанию создается массив емкостью 16 элементов. При необходимости ArrayList увеличивает емкость, удваивая ее. Если приблизительная емкость динамического массива известна, ее можно указать как параметр конструктора ArrayList. Это увеличит скорость вставки элементов.
Для удаления элементов массива предназначены методы Remove(), RemoveAt(), Clear(), RemoveRange(). Удаление элемента автоматически изменяет индексы оставшихся элементов. При удалении элементов емкость массива не уменьшается. Для того чтобы память массива соответствовала количеству элементов в нем, требуется использовать метод TrimToSize():
// Создаем массив и добавляем элементы
ArrayList list = new ArrayList(1000);
for(int i=0; i<1000; i++)
list.Add(i);
// Удаляем первые 500 элементов
list.RemoveRange(0, 500);
// Емкость массива по-прежнему 1000
Console.WriteLine(Lst.Capacity);
// Подгоняем размер массива под количество элементов
list.TrimToSize();
// Теперь его емкость 500
Console.WriteLine(Lst.Capacity)
Класс Hashtable является классом, реализующим хэш-таблицу. Следующий пример кода показывает использование этого класса для организации простого англо-русского словаря. В паре «ключ-значение» ключом является английское слово, а значением – русское:
Hashtable table = new Hashtable();
table.Add("Sunday", "Воскресенье");
table.Add("Monday", "Понедельник");
table.Add("Tuesday", "Вторник");
table.Add("Wednesday", "Среда");
table.Add("Thursday", "Четверг");
table.Add("Friday", "Пятница");
table.Add("Saturday", "Суббота");
Если хэш-таблица инициализирована указанным образом, то поиск русского эквивалента английского слова может быть сделан так (обратите внимание на приведение типов):
string s;
s = (string)table["Friday"];
Элементы могут быть добавлены в хэш-таблицу, используя индексатор:
Hashtable table = new Hashtable();
table["Sunday"] = "Воскресенье";
table["Monday"] = "Понедельник";
Если указываемый при добавлении ключ уже существует в таблице, метод Add() генерирует исключительную ситуацию. При использовании индексатора старое значение просто заменяется новым.
Элементы хэш-таблицы являются объектами класса DictionaryEntry. Этот класс имеет свойства для доступа к ключу (Key) и значению (Value). Класс Hashtable поддерживает интерфейс IEnumerable, что делает возможным перебор элементов с использованием foreach:
foreach (DictionaryEntry entry in table)
Console.WriteLine("Key = {0}, Value = {1}",
entry.Key, entry.Value);
Класс Hashtable имеет методы для удаления элементов (Remove()), удаления всех элементов (Clear()), проверки существования элемента (ContainsKey() и ContainsValue()) и другие. Свойство Count содержит количество элементов хэш-таблицы, свойства Keys и Values позволяют перечислить все ключи и значения таблицы соответственно.
На скорость работы с элементам хэш-таблицы влияют два фактора: размер таблицы и уникальность хэш-кодов, продуцируемых ключами таблицы. Размер хэш-таблицы изменяется динамически, однако изменение размера требует затрат ресурсов. Класс Hashtable имеет конструктор, который в качестве параметра принимает предполагаемый размер таблицы в элементах.
Хэш-таблица захватывает дополнительную память, если ее заполненность превышает определенный предел, который по умолчанию равен 72% от емкости. Перегруженный конструктор класса Hashtable позволяет указать множитель загрузки. Это число в диапазоне от 0.1 до 1.0, на которое умножается 0.72, определяя тем самым новую максимальную заполненность:
// Будем захватывать память при заполнении 58% (72*0.8)
Hashtable tbl = new Hashtable(1000, 0.8);
По умолчанию хэш-таблица генерирует хэш-коды ключей, применяя метод GetHashCode() ключа. Все объекты наследуют данный метод от System.Object. Если объекты-ключи производят повторяющиеся хэши, это приводит к коллизиям в таблице, а, следовательно, к снижению производительности. В этом случае можно
· переписать метод GetHashCode() для класса-ключа с целью усиления уникальности;
· создать тип, реализующий интерфейс IHashCodeProvider и передавать экземпляр такого типа конструктору таблицы. Интерфейс IHashCodeProvider содержит метод GetHashCode(), генерирующий хэши по входным объектам.
Для сравнения ключей таблица по умолчанию применяет метод Equals() объектов. Если сравнение реализовано не эффективно, требуется переписать метод Equals() или передать конструктору таблицы экземпляр класса, реализующего интерфейс IComparer.
В пространстве имен System.Collections определены два класса, которые можно использовать как основу для создания собственных типизированных коллекций-списков и коллекций-словарей. Это классы DictionaryBase и CollectionBase. Каждый из этих классов предоставляет ряд виртуальных методов, вызываемых при модификации коллекции. Например, OnClean() вызывается, когда коллекция очищается; OnInsert() – перед добавлением элемента; OnInsertComplete() – после добавления; OnRemove() – перед удалением элемента и т. п. Переопределяя эти методы можно создать дополнительные проверки и вызвать исключение в случае ошибок типизации. Например, следующий класс представляет собой коллекцию, которая допускает хранение только строк из четырех символов:
using System;
using System.Collections;
class MyStringCollection : CollectionBase
{
// Этот метод будет добавлять элементы
// Он просто вызывает метод Add() внутреннего списка List
if ((value is string) && ((string)value).Length == 4))
{
base.OnInsert(index, value);
}
else throw new ArgumentException("Length 4 allowed!");
}
// Метод для получения элементов
// В нем выполняем приведение типов
string GetItem(int index) {
return (string)List[index];
}
}
class MainClass {
static void Main() {
MyStringCollection msc = new MyStringCollection();
msc.Add("Alex");
msc.Add("QWER");
string s = msc.GetItem(1);
Console.WriteLine(s);
// Любая из следующих строк генерирует исключение
msc.Add("asldalda");
msc.Add(5);
}
}
Для иллюстрации использования коллекций приведем пример приложения, подсчитывающего уникальные слова в текстовом файле. Имя текстового файла передается консольному приложению в качестве параметра. Вопросы, связанные с обработкой файлов, будут рассмотрены далее.
using System;
using System.IO;
using System.Collections;
class MyApp {
static void Main (string[] args) {
// Проверка параметров командной строки
if (args.Length == 0) {
Console.WriteLine ("Ошибка: нет имени файла");
return;
}
// Создаем объект для доступа к строкам файла
// и объект для хранения уникальных слов
StreamReader reader = null;
Hashtable table = new Hashtable();
try {
reader = new StreamReader(args[0]);
// Перебираем все строки файла
for (string line = reader.ReadLine();
ine != null; line = reader.ReadLine())
{
// Этот массив хранит слова строки
string[] words = GetWords(line);
// Перебираем все слова
foreach(string word in words) {
string iword = word.ToLower();
if(table.ContainsKey(iword))
// Если слово уже есть, счетчик + 1
table[iword] = (int)table[iword] + 1;
else
// Иначе помещаем слово в таблицу
table[iword] = 1;
}
}
// Сортируем хэш-таблицу, используя SortedList
SortedList list = new SortedList(table);
// Выводим результат работы
Console.WriteLine("Уникальных слов {0}",
table.Count);
foreach(DictionaryEntry ent in list)
Console.WriteLine("{0} ({1})",
ent.Key, ent.Value);
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
finally {
if (reader != null)
reader.Close ();
}
}
// Метод для выделения слов в строке
static string[] GetWords(string line) {
// Создадим ArrayList для промежуточных результатов
ArrayList al = new ArrayList();
// Разбираем строку на слова
int i = 0;
string word;
char[] chars = line.ToCharArray();
while ((word=GetNextWord(line, chars, ref i)) != null)
Наряду с пространством имен System.Collections, .NET Framework предоставляет пространство имен System.Collections.Specialized. Оно содержит классы коллекций, которые подходят для решения специальных задач.
Таблица 12
Классы и структуры из System.Collections.Specialized
Имя класса
(структуры)
Для чего служит
Реализует интерфейсы
BitVector32
Структура фиксированного размера (4 байта) для хранения массива булевых значений
CollectionsUtil
Класс, разделяемые методы которого позволяют создавать коллекции, в которых игнорируется регистр строк
HybridDictionary
Реализует интерфейс IDictionary с использованием ListDictionary для малых коллекций; автоматический переключается на использование Hashtable, когда размер коллекции увеличивается
IDictionary, ICollection, IEnumerable
ListDictionary
Реализует интерфейс IDictionary с использованием односвязного списка. Рекомендуется для коллекций с количеством элементов меньше 10
IDictionary, ICollection, IEnumerable
NameObjectCollectionBase
Абстрактный базовый класс для сортированных таблиц «ключ-значение», ключом которых является строка (доступ к элементу – по ключу или по индексу)
NameValueCollection
Класс для сортированных таблиц «ключ-значение», ключом и значением в которых является строка (доступ к элементу – по ключу или по индексу)
StringCollection
Представляет коллекцию строк
StringDictionary
Класс для хэш-таблиц, ключом которых является строка
IEnumerable
StringEnumerator
Итератор для StringCollection
Рассмотрим некоторые из классов, упомянутых в таблице. Класс StringCollection представляет реализацию класса ArrayList, которая может хранить только строки. Это обеспечивает дополнительный контроль при помещении объекта в коллекцию и избавляет от необходимости выполнять приведения типов. Класс StringDictionary представляет реализацию хэш-таблицы, в которой ключ и значение всегда имеют тип sring. Кроме этого, ключ таблицы не зависит от регистра символов.
Появление во второй версии .NET Framework обобщенных типов позволило создать классы для представления коллекций с их использованием. Данные классы (а также сопутствующие им типы) сосредоточены в пространстве имен System.Collectons.Generics. В таблице 13 описаны некоторые типы из этого пространства имен.