Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Framework »

Обсудить на форуме Обсудить на форуме

Расширение набора базовых классов .NET Framework
AcedUtils.zip (172,37 Kb)

В статье описываются классы, которые могут использоваться разработчиком на платформе .NET в дополнение к основным, таким как ArrayList, для повышения эффективности приложений.
К статье прилагается исходный код сборки AcedUtils на языке C# и ее скомпилированная версия в каталоге AcedUtils\bin\Release. Код предоставляется на условиях freeware. Автор будет благодарен за любую информацию об ошибках в коде AcedUtils.

Рассмотрим некоторые проблемы, возникающие при разработке прикладных приложений, которые могут успешно решаться путем использования AcedUtils.

Работа с реестром Windows

Для сохранения параметров конфигурации во многих случаях целесообразно использование реестра Windows. В этом случае можно воспользоваться классом AcedConfiguration.

 

Например, если в ключе реестра HKEY_CURRENT_USER\Software\Company\Data необходимо хранить наименование организации ((OrganizationName типа String) и номер подразделения (DepartmentID типа Int32), это можно сделать методами SaveConfig(), LoadConfig():

private const string
DataRegistryKey = "Software\\Company\\Data";
OrganizationNameValue = "OrganizationName";
DepartmentIDValue = "DepartmentID";

internal static string OrganizationName = "";
internal static int DepartmentID = 0;

internal static void LoadConfig()
{
using (Aced.AcedConfiguration config =
new Aced.AcedConfiguration(true, DataRegistryKey, false))
	{
		config.Get(OrganizationNameValue, ref OrganizationName);
		config.Get(DepartmentIDValue, ref DepartmentID);
	}
}

internal static void SaveConfig()
{
using (Aced.AcedConfiguration config =
new Aced.AcedConfiguration(true, DataRegistryKey, true))
	{
		config.Put(OrganizationNameValue, OrganizationName);
		config.Put(DepartmentIDValue, DepartmentID);
	}
}

Конструктор класса AcedConfiguration принимает три параметра: первый - выбирает ветвь реестра: HKEY_CURRENT_USER (если True) или HKEY_LOCAL_MACHINE (если False), второй - задает наименование ключа реестра, третий - определяет режим работы, т.е. чтение или запись в реестр. Методы Get(), Put() класса AcedConfiguration позволяют помещать и считывать из реестра данные в различном виде: строки, числа, даты, и т.д.

Представление первичных/внешних ключей базы данных

При разработке многоуровневых приложений баз данных возникает проблема генерации первичных ключей. Например, в таблице ORDERS есть поле CustomerID, ссылающееся на запись в таблице CUSTOMERS. Предположим, пользователь добавил нового покупателя в таблицу CUSTOMERS и заказ этого покупателя в таблицу ORDERS. Внесенные изменения кэшировались в DataSet на стороне клиента. После этого вызываются методы customersDataAdapter.Update(customersTable) и ordersDataAdapter.Update(ordersTable) для сохранения изменений на сервере. Проблема заключается в том, как выбрать значение первичного ключа для таблицы CUSTOMERS, чтобы при сохранении изменений на сервере не нарушилась ссылочная целостность между таблицами ORDERS и CUSTOMERS.

 

Одним из решений может быть использование автоинкрементного поля для первичного ключа. Причем, в базе данных значение первичного ключа инкрементируется с шагом +1, а в наборе данных на стороне клиента с шагом -1 (свойство DataColumn.AutoIncrement устанавливается в True, свойство DataColumn.AutoIncrementStep устанавливается в -1). Тогда после выполнения предложения INSERT для каждого клиента в процессе работы метода customersDataAdapter.Update(customersTable) нужно получить значение первичного ключа только что вставленной записи (например, выполнив "SELECT @@IDENTITY" для SQL Server), а затем заменить значение первичного ключа соответствующей строки в таблице CUSTOMERS и значение внешнего ключа всех строк в таблице ORDERS, которые ссылаются на данного покупателя. Этот метод довольно сложен и не отличается надежностью.

 

Альтернативным решением может быть использование GUID качестве значения первичного ключа, т.е. первичный ключ для новой строки в таблице Customers может получаться вызовом метода Guid.NewGuid() на стороне клиента. Вероятность того, что две записи в таблице CUSTOMERS получат одно и то же значение GUID, очень мала. Однако, значения GUID не очень удобны в работе. В .NET Framework Guid представляет собой структуру, а не класс, т.е. относится к value-, а не к reference- типу. Это приводит к тому, что почти всякая операция с типом Guid приводит к копированию 16 байт из одного места памяти в другое. Использование Guid в качестве первичного ключа на практике выливается в большой объем используемой памяти и низкое быстродействие.

 

Для решения этой проблемы в AcedUtils введен класс AcedKey, который представляет собой эквивалент Guid, но обрабатывается как класс, а не как структура. Экземпляры класса AcedKey являются неизменяемыми. В них есть свойства: A, B, C, D типа System.UInt32, которые представляют, соответственно, старшую, среднуюю-старшую, среднюю-младшую и младшую части 128-битного значения. По аналогии с классом System.String в классе AcedKey используется пул идентификаторов, т.е. при запросе на создание нового ключа выполняется поиск во внутреннем хэше ключей. Если такой ключ присутствует в хэше, возвращается ссылка на этот экземпляр, иначе создается новый экземпляр класса AcedKey и помещается в хэш. При использовании SQL Server в качестве сервера базы данных столбцы первичных и внешних ключей должны иметь тип uniqueidentifier. На других серверах экземпляры класса AcedKey могут храниться в виде строк.

 

С помощью метода ToBase85() класса AcedKey может быть получено строковое представление ключа в кодировке Base85 длиной до 20 символов, а с помощью метода FromBase85() - восстановлен исходный экземпляр AcedKey из строки в кодировке Base85. Эта кодировка используется для более компактного чем в Base64 представления бинарных значений. Каждые 4 байта бинарных данных представляются в виде 5 символов в кодировке Base85. Основные методы для работы с кодировками находятся в классе Aced.G.

 

Теперь рассмотрим, как сгенерировать ключ AcedKey. Проще всего получить новый ключ из значения Guid вызовом: AcedKey.FromGuid(Guid.NewGuid()). Однако, теоретически, все же существует вероятность генерации двух одинаковых значений Guid. Кроме того, метод Guid.NewGuid() не отличается высоким быстродействием. В данном случае нам не нужен глобальный уникальный идентификатор, достаточно получить идентификатор, уникальный в пределах базы данных. Тогда можно воспользоваться следующим приемом.

 

В базе данных создаем таблицу с одним столбцом и одной строкой, где хранится значение типа Int64. В SQL Server это будет столбец типа bigint. При подключении к базе данных каждого нового клиента увеличиваем этот счетчик на 1 и возвращаем полученное значение в клиенту. В клиентском приложении в некотором классе есть статические поля: _nA, _nB, _nC, _nD типа UInt32. Значение x типа Int64, которое пришло с сервера, сохраняется в полях _nC и _nD:

 

public void UpdateKeyBase(long x)
{
_nA = 0;
_nB = 0;
	ulong ux = (ulong)x;
	_nC = (uint)ux;
	_nD = (uint)(ux >> 32);
}

Тогда новое значение идентификатора для первичного ключа может быть сгенерировано следующим методом:

public AcedKey NewKey()
{
	if (_nA != 0xFFFFFFFFu)
		_nA++;
	else
	{
		_nA = 0;
		if (_nB != 0xFFFFFFFFu)
			_nB++;
		else
throw new Exception("There are no more available keys.");
}
	return AcedKey.FromABCD(_nA, _nB, _nC, _nD);
}

 

В классе AcedKey есть статические поля Empty и Full, которые служат, соответственно, для представления значений NULL и NOT NULL в полях внешних ключей. Есть еще методы для сравнения ключей, расчета комбинированного значения, т.е. своего рода контрольной суммы для набора ключей, чтения и сохранения ключа в массиве байт и т.д.

Списки, хэши, битовые массивы

В .NET Framework есть готовые классы для организации списков, стеков, очередей, хэшей, словарей. В AcedUtils введены классы, дополняющие этот набор. Они предназначены для повышения производительности и расширения функциональности при работе со списками. Перечень классов:

 

AcedBitList - эквивалент класса System.Collections.BitArray с большей функциональностью.

 

AcedList - список объектов, аналогичный ArrayList. Обладает специфической функциональностью, например, реализует интерфейс IAcedStack.

 

AcedInt32List - упорядоченный список значений типа Int32. При обращении к элементам не выполняется boxing/unboxing, как, например, для набора ArrayList.

 

AcedKeyList - упорядоченный список ключей типа AcedKey.

 

AcedSortedList - отсортированный список объектов, реализующих интерфейс IComparable.

 

AcedSortedInt32List - отсортированный список значений типа Int32.

 

AcedSortedKeyList - отсортированный список ключей типа AcedKey.

 

AcedInt32Hashtable - хэш-таблица с ключами типа Int32 и значениями типа Object.

 

AcedKeyHashtable - хэш-таблица с ключами типа AcedKey и значениями типа Object.

 

AcedStringHashtable - хэш-таблица с ключами типа String и значениями типа Object.

 

AcedNamedItems - упорядоченный список наименований с идентификаторами.

 

Экземпляры классов AcedList, AcedInt32List, AcedKeyList могут использоваться в качестве стека. Для этого они реализуют интерфейсы: IAcedStack, IAcedInt32Stack, IAcedKeyStack, соответственно. Например, интерфейс IAcedInt32Stack описывается следующим образом:

public interface IAcedInt32Stack
{
	void Clear();
	bool IsEmpty();
	int Peek();
	int Pop();
	void Push(int value);
}

Классы AcedSortedList, AcedSortedInt32List, AcedSortedKeyList реализуют групповые операции над наборами данных: объединение (Merge), пересечение (Intersect), вычитание (Subtract), проверка вхождения (ContainedIn), проверка пересечения (HasIntersection).

 

Подробное описание свойств и методов всех этих классов и интерфейсов можно найти в соответствующих файлах исходного кода.

Использование сжатого бинарного потока

Одной из часто возникающих задач является сохранение данных различного типа в бинарном потоке. В .NET Framework для этого используются классы: BinaryReader и BinaryWriter из пространства имен System.IO. В AcedUtils для этой цели предназначены классы AcedBinaryReader и AcedBinaryWriter, которые позволяют не только перенести информацию в бинарный массив, но также сжать ее методом, подобным используемому в популярной библиотеке ZLib, и защитить контрольной суммой Адлера. При необходимости, упакованный бинарный массив может быть зашифрован методом CAST5, используемым в программе PGP, и защищен односторонней хэш-функцией RipeMD-160. Пример:

private string _message;
private byte[] _binaryData;
private DateTime _time;

private byte[] SaveData(Guid guid)
{
	AcedBinaryWriter bw = new AcedBinaryWriter();
	bw.WriteString(_message);
	bw.WriteByteArray(_binaryData);
	bw.WriteDateTime(_time);
	return bw.GetBytes(guid, true);
}

private void LoadData(Guid guid, byte[] bytes)
{
	AcedBinaryReader br = new AcedBinaryReader(guid, bytes, 0, bytes.Length);
	_message = br.ReadString();
	_binaryData = br.ReadByteArray();
	_time = br.ReadDateTime();
}

private void TestBinaryStream(Guid guid)
{
	_message = "Hello world!";
	_binaryData = new byte[1000000];
	for (int i = 0; i < 1000000; i++)
		_binaryData[i] = (byte)i;
	_time = DateTime.Now;

	byte[] m = SaveData(guid);

	MessageBox.Show(m.Length.ToString());

	_message = "";
	_binaryData = null;
	_time = new DateTime(0);

	LoadData(guid, m);

	MessageBox.Show(_message + " - " + _time.ToString());
}

Метод SaveData() помещает значения полей _message, _binaryData и _time в бинарный поток. Вызов bw.GetBytes() возвращает массив байт, содержащий данные потока. Позже, в методе LoadData(), когда надо прочитать данные из потока, этот массив передается как параметр в конструктор класса AcedBinaryReader. Потом значения соответствующих полей считываются из потока методами этого класса. Метод TestBinaryStream() инициализирует сохраняемые поля, в частности, в _binaryData помещается массив длиной 1000000 байт, состоящий из последовательных значений 0, 1, 2, …, 254, 255, 0, 1, … Затем этот метод вызывает SaveData() для сохранения полей в потоке. На экране показывается длина полученного массива байт. На всякий случай, поля обнуляются. Затем вызывается метод LoadData() для восстановления значений полей. В том, что значения считались правильно, можно убедиться по сообщению, выводимому на экран.

 

Метод TestBinaryStream() принимает параметр типа Guid. Этот параметр передается в методы SaveData() и LoadData(), а затем в метод AcedBinaryWriter.GetBytes() и в конструктор класса AcedBinaryReader. Guid здесь используется в качестве ключа шифрования. Если передается значение Guid.Empty, то шифрование и расчет односторонней хэш-функции не выполняется. Если Guid отличен от Guid.Empty, данные закрываются блочным шифром CAST5 в режиме CFB, а затем к ним добавляется 20-байтная сигнатура, рассчитанная методом RipeMD-160. Длина выходного массива, возвращаемого вызовом SaveData(Guid.Empty) может быть, например, 389 байт, а возвращаемого вызовом SaveData(Guid.NewGuid()) за счет сигнатуры будет на 20 байт больше, т.е. 409 байт.

 

Второй параметр метода AcedBinaryWriter.GetBytes() определяет, нужно ли сжимать данные, помещенные в выходной поток. Если в приведенном выше примере в методе SaveData() поменять значение параметра deflate с True на False, то длина выходного массива составит 1000044 или 1000064 байт. Зато, при этом повысится быстродействие, т.к. не нужно будет тратить время на упаковку/распаковку данных. Сжатие выполняется экземпляром класса AcedDeflator, распаковка - зкземпляром класса AcedInflator.

Изменяемые строки

Как известно, строки в .NET являются неизменяемыми объектам. Каждый раз, когда что-либо добавляется или изменяется в строке, на самом деле создается новый экземпляр класса System.String. Для интенсивной работы со строками предназначен класс StringBuilder из пространства имен System.Text. В AcedUtils вводится класс AcedStringBuilder, который можно использовать вместо или в дополнение к StringBuilder. От стандартного он отличается большей функциональностью и производительностью. Пример:

 

private void Test()
{
	AcedStringBuilder sb = new AcedStringBuilder("Next is the E");

	// "Next is the E"

	sb.Paste(0, 4, "First");

	// "First is the E"

	sb.Append(" Last");

	// "First is the E Last"

	sb.Move(12, 5, 2);

	// "First E is the Last"

	sb.Exchange(0, 5, 15, 4);

	// "Last E is the First"

	sb.Reverse(7, 2);

	// "Last E si the First"

	sb.AppendNewLine().Append(257, 8, true);

	// "Last E si the First"
// "00000101"

	MessageBox.Show(sb.ToString());

	if (sb.Equals(7, "si", 0, 2))
{
MessageBox.Show("OK");
}
}

По комментариям видно, как изменяется значение строки sb. Вызов sb.Append(257, 8, true) добавляет к строке число 257, записанное минимум 8 знаками в шестнадцатеричной кодировке. Если бы последний параметр при вызове был равен False, добавилась бы строка: "00000257". Метод AcedStringBuilder.Equals() предназначен для быстрой проверки равенства строк или их фрагментов без учета регистра символов.

Другие возможности

В классе Aced.G собраны функции различного назначения. Здесь есть методы для расчета контрольной суммы CRC32, округления денежных сумм до рублей и тысяч, преобразования значения из одного типа в другой, перевода значений в шестнадцатеричную кодировку или в Base85 и обратно. Кроме того, в этом классе находятся оптимизированные методы для работы с массивами значений типа Int32, UInt32, Char.

 

Еще одним полезным классом является AcedUI. Свойство AcedUI.UseXpTheme позволяет определить версию библиотеки comctl32.dll, используемой данным приложением. Если свойство возвращает True, загружена версия 6.0 или выше этой библиотеки. Это означает, что в приложении могут использоваться темы Windows XP. Если свойство возвращает False, используется более ранняя версия comctl32.dll и темы Windows XP недоступны.

Заключение

В статье кратко рассмотрены классы, входящие в состав сборки AcedUtils, дополняющей стандартный набор классов .NET Framework. В исходном коде, прилагаемом к статье, в начале каждого файла есть информация о назначении, свойствах и методах классов, описанных в данном файле. Эта информация может использоваться для более подробного ознакомления с возможностями, реализованными в AcedUtils.


Может пригодится:


Автор: andreyd
Прочитано: 4801
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру