Классы каркаса .NET Framework

Невозможно описать в одной главе, или даже в одной книге, все классы каркаса .NET Framework. Хотя и не полностью, классы .NET охватывают большую часть интерфейса 32-разрядных Windows-приложений (Win32 API), так же как и много чего другого. Несмотря на то, что основное внимание было уделено функциональным возможностям, связанным с Internet, однако изменилась и модель разработки приложений в среде Windows.
В этой главе мы сосредоточим наше внимание на классах, которые иллюстрируют ключевые концепции и модели, проявляющиеся повсюду в каркасе .NET Framework. Такой подход представляется нам более плодотворным, чем просто попытаться немного рассказать о каждом классе, который когда-либо мог бы понадобиться, без того, чтобы дать читателю общее представление о классах .NET. В других главах глубже рассматриваются иные части каркаса, такие как Windows Forms (Формы Windows), ASP.NET, безопасность ADO.NET, и сетевые службы (Web Services).
Мы начинаем с рассмотрения концепций отражения и метаданных. Метаданные появляются всюду в .NET и важно понимать, как общеязыковая среда времени выполнения CLR (Common Language Runtime) предоставляет разные услуги (службы, сервисы) прикладным программам. Затем по нескольким причинам мы исследуем файловый ввод/вывод. Во-первых, при этом вводится важное понятие сериализации (преобразования в последовательную форму). Во-вторых, класс Path (Путь) позволяет проиллюстрировать, как отдельные классы каркаса реализуют некоторые или все свои функциональные возможности с помощью статических методов. В-третьих, рассматриваемые в применении к файловому вводу/выводу классы используются для форматирования во многих местах .NET.
Разобравшись в понятии сериализации, читатель получит конкретную идею относительно того, как каркас может управлять объектами прозрачным для нас способом. Ведь сериализация появляется во вспомогательной роли в любом месте, где объекты должны будут сохраняться или транспортироваться. Обсуждение интерфейса ISerializable снова демонстрирует, насколько проще реализовать интерфейс, используя .NET, чем с помощью модели компонентных объектов Microsoft (COM).
С целью более углубленного понимания моделей .NET для приложений, мы рассматриваем программирование с помощью потоков в .NET, а также несколько методов синхронизации и разрешения конфликтов, связанных с многопоточностью. Различные методы синхронизации иллюстрируют компромисс использования свойств, предоставленных каркасом, по сравнению со свойствами, реализованными самостоятельно.
Для лучшего понимания модели программирования .NET, мы введем понятие контекста, а также рассмотрим применение заместителей и заглушек для реализации системных сервисов. Мы также рассмотрим использование прикладных областей, благодаря которым достигается более эффективная изоляция приложений, чем с помощью процессов Win32.
Асинхронные шаблоны проектирования появляются в .NET повсюду и обсуждаются довольно подробно. В книге продемонстрированы некоторые примеры удаленного доступа, поскольку это ключевая технология, включающая в себя большинство концепций, рассматриваемых в данной главе. В примерах, разбираемых здесь, используется несколько атрибутов, предоставляемых каркасом .NET Framework, а также демонстрируется реализация и использование выборочных, самостоятельно созданных атрибутов. Мы обсуждаем операцию завершения, чтобы читатель понял, как можно удостовериться в том, что приложения правильно освобождают ресурсы.


Метаданные и отражение


Пример Serialization (Сериализация) из главы 2 "Основы технологии .NET" демонстрирует, как благодаря метаданным общеязыковая среда времени выполнения CLR поддерживает работу сервисов. Многие из технологий, которые мы рассматриваем в других главах книги, основаны на метаданных, хотя мы не всегда будем акцентировать на этом внимание.
Метаданные — это информация о сборках, модулях и типах, составляющих программы в .NET. Тот, кому когда-либо приходилось создавать язык описания интерфейса (IDL) с целью сгенерировать библиотеку типов так, чтобы созданные с помощью C++ СОМ-объекты могли вызываться из Visual Basic, или создавать заместители и заглушки, оценит, насколько полезными являются метаданные, и будет благодарен, что распространяется это все "бесплатно".
Компиляторы генерируют метаданные, а общеязыковая среда времени выполнения CLR, каркас .NET Framework и наши собственные программы могут их использовать. Чтобы лучше разобраться в том, как работают метаданные, мы сосредоточимся на обсуждении их использования, а не на их создании. Метаданные можно прочитать с помо-. шью классов из пространства имен System: : Reflection (Система::Отражение).
Когда загружаются сборка и связанные с ней модули и типы, метаданные подгружаются вместе с ними. Затем можно сделать запрос к сборке, чтобы получить связанные с ней типы. Можно также вызвать метод GetType для любого из типов общеязыковой среды времени выполнения CLR и получить его метаданные. GetType — это метод класса System: :Object (Система::Объект), производным от которого является каждый тип общеязыковой среды времени выполнения CLR. После того, как получен Туре (Тип), связанный с объектом, можно использовать методы отражения, чтобы получить соответствующие метаданные.
Программа-пример Reflection (Отражение) берет изучаемую сборку Customer (Клиент) и распечатывает некоторые из доступных метаданных. В следующих разделах книги вы изучите распечатку и исходный код. Особенно важно сравнить вывод программы с исходным кодом в файле customer. h.
Программа ясно показывает, что из сборки можно извлечь все типы и восстановить структуры, интерфейсы, свойства, события и методы, связанные с этими типами. Сначала мы загружаем сборку в память и выводим ее имя.
Assembly *a = Assembly::Load(assemblyName); // Загрузка
Console::WriteLine (
"Assembly {0} found.", a->FullName);
Вывод для этого оператора соответствует неподписанной сборке:
Assembly Customer, Version=l.О.643.18973, Culture=neutral,
PublicKeyToken=null found.
CodeBase — одно из свойств класса Assembly; оно обсуждалось в главе 7 "Сборки и развертывание". Подтверждение защиты, связанное с этой сборкой, — это еще одно свойство. Следующий код пробует определить точку входа сборки:
Methodlnfo *entryMethod!nfo = a->EntryPoint;
Так как это типичный написанный на C++ компонент сборки, то его точка входа — _DllMainCRTStartup@12. Если бы он был исполняемой программой, мы могли бы использовать метод Invoke (Вызвать) класса Methodlnfo, чтобы выполнить код инициализации сборки.
В данном примере для поиска связанных с этой сборкой модулей используется метод GetModules сборки. В данном случае мы имеем только один модуль, customer.dll. Затем мы могли бы найти типы, связанные с этим модулем. Вместо этого мы используем метод GetTypes сборки, чтобы возвратить массив типов сборки.

Класс туре (Тип)


Абстрактный класс Туре (Тип) в пространстве имен System (Система) определяет типы .NET. Поскольку в .NET нет никаких функций вне классов или глобальных переменных, то получив все типы в сборке, мы получим все метаданные о коде в этой сборке. Туре (Тип) представляет все типы, имеющиеся в .NET: классы, структуры, интерфейсы, значения, массивы и перечисления.
Класс Туре (Тип) возвращается также методом GetType класса System::0bject (Система::Объект) и статическим методом GetType самого класса Туре (Тип). Последний метод может использоваться только с типами, которые могут быть разрешены статически.
Одно из свойств класса Туре (Тип) — сборка, к которой он принадлежит. Можно получить все типы, содержащиеся в сборке, как только будет определен Туре (Тип) одного объекта. Туре (Тип) — абстрактный класс, и во время выполнения возвращается экземпляр System::RuntimeType.
В выдаче программы найден каждый тип в сборке, — CustomerListltem, ICustomer, Customer (Клиент) и Customers (Клиенты),— причем распечатаны его метаданные. Чтобы для каждого типа выяснить стандартные атрибуты и тип, производным от которого является класс, нужно воспользоваться свойствами Attributes (Атрибуты) и BaseType.
Методы, связанные с классом Туре (Тип), дают возможность получить ассоциированные поля, свойства, интерфейсы, события, и методы. Например, тип Customer (Клиент) не имеет никаких интерфейсов, свойств и событий, но имеет четыре поля, три конструктора и методы, унаследованные от его базового класса BaseType из пространства имен System::Object (Система::Объект):
Можно также загрузить и выполнить сборку с AppDomain, — мы обсудим этот вариант в данной главе позже.
Interfaces: Fields:
Customerld
FirstName
LastName
EmailAddress Properties:
Events:
Constructors:
public .ctor(System.String first, System.String last,
System.String email)
public .ctor()
public .ctor(System.Int32 id) Methods:
public Int32 GetHashCodeO
public Boolean Equals(System.Object obj)
public String ToStringO
public Type GetType()
Перевод такой:
Интерфейсы: Поля:
Customerld
FirstName
LastName
EmailAddress Свойства:
События:
Конструкторы:
public .ctor(System.String first, System.String last,
System.String email)
// электронная почта
public.ctor ()
public .ctor(System.Int32 id) Методы:
public Int32 GetHashCodeO
public Boolean Equals(System.Object obj)
// Равняется
public String ToString()
public Type GetType()
Тип Customers (Клиенты) наследуется от одного интерфейса и содержит один конструктор и четыре своих собственных метода в дополнение к четырем уже унаследованным от его базового класса BaseType из пространства имен System: :Object (Система-Объект):
Interfaces:
ICustomer
Fields:
Properties:
Events:
Constructors:
public .ctor()
Methods:
public Void ChangeEmailAddress(System.Int32 id,
System.String emailAddress)
public ArrayList GetCustomer(System.Int32 id)
public Void UnregisterCustomer(System.Int32 id)
public Int32 RegisterCustomer(System.String firstName,
System.String lastName, System.String emailAddress)
public Int32 GetHashCode()
public Boolean Equals(System.Object obj )
public String ToString ()
public Type GetTypeO()
Перевод такой:
Интерфейсы:
ICustomer
Поля:
Свойства:
События:
Конструкторы:
public .ctor() Методы:
public Void ChangeEmailAddress(System.Int32 id,
System.String emailAddress)
public ArrayList GetCustomer(System.Int32 id)
public Void UnregisterCustomer(System.Int32 id)
public Int32 RegisterCustomer(System.String firstName,
System.String lastName, System.String emailAddress)
public Int32 GetHashCode()
public Boolean Equals(System.Object obj )
public String ToString()
public Type GetType()
Вся эта информация была получена с помощью методов Getlnterfaces, GetFields, GetProperties, GetEvents, GetConstructors И GetMethods класса Type (Тип). Поскольку интерфейс — тип, Getlnterfaces возвращает массив объектов Туре (Тип), представляющий интерфейсы, унаследованные или реализованные запрошенным типом Туре (Тип). А так как поля, свойства, события, и методы — не типы, их методы средств доступа не возвращают объекты Туре (Тип). Каждый из их методов доступа возвращает соответствующий класс: Fieldlnfo, Propertylnfo, Eventlnfo, Constructorlnfо и Methodlnf о. Все эти классы, а также класс Туре (Тип), — производные от класса Memberlnfo, который является абстрактным базовым классом для элементов метаданных.
Давайте рассмотрим некоторые из метаданных, связанные с методом класса. Используя методы отражения, мы можем восстановить сигнатуры для всех классов и интерфейсов в сборке Customer (Клиент). Вот распечатка для методов класса Customer (Клиент):
public Void ChangeEmailAddress(System.Int32 id,
System.String emailAddress)
public ArrayList GetCustomer(System.Int32 id)
public Void UnregisterCustomer(System.Int32 id)
public Int32 RegisterCustomer(System.String firstName,
System.String lastName, System.String emailAddress)
public Int32 GetHashCode()
public Boolean Equals(System.Object obj)
public String ToString()
public Type GetType()
Вот код, с помощью которого была получена эта распечатка:
for (int j = 0; j < methodlnfо.Length; j++)
{
if (methodlnfo[j]->IsStatic)
Console::Write (" static "); // статический
if (methodlnfo[j]->IsPublic)
Console::Write(" public ");
if (methodlnfo[j]->IsFamily)
Console::Write(" protected "}; // защищенный
if (methodlnfo[j]->IsAssembly)
Console::Write(" internal "); // внутренний
if (methodlnfо[j]->IsPrivate)
Console::Write(" private "); // частный
Console::Write(
"{0} ", methodlnfo[j]->ReturnType->Name); // Имя
Console::Write( // Запись
"{0}(", methodlnfo[j]->Name); // Имя
Parameterlnfo *paramlnfo [] =
methodlnfo[j]->GetParameters(};
long last = paramlnfo->Length - 1;
for (int k = 0; k<param!nfo->Length; k++)
{
Console::Write( // Запись
"{0} {1}",
paramlnfо[k]->ParameterType,
paramlnfо[k]->Name); // Имя
if (k != last) // если не последний
Console::Write(", "); // Запись
}
Console::WriteLine(")");
}
За исключением того, что конструктор не возвращает переменную какого-нибудь типа, тот же самый код воспроизводит вызывающие последовательности для конструкторов класса.
Класс Methodlnfo содержит свойства, с помощью которых можно определить, является ли метод статическим, приватным, защищенным или внутренним, а также возвращаемый тип (тип результата) и название метода. Параметры метода сохраняются в массиве свойств типа (класса) Parameterlnfo.
Этот пример показывает, что типы относятся к сборкам. Два типа, имеющие одинаковые имена, но размещенные в двух различных сборках, трактуются во время выполнения как два разных типа. Нужно быть внимательным при смешивании различных версий типов или тех же самых типов в двух различных сборках.
Все эти метаданные позволяют общеязыковой среде времени выполнения CLR и каркасу предоставлять услуги прикладным программам, потому что они могут понять структуру созданных нами типов.

Динамическое связывание


Отражение может также использоваться для реализации динамического связывания. Динамическое связывание состоит в том, что метод, который нужно вызвать, определяется в процессе выполнения, а не на этапе компиляции. Это один из примеров того, как метаданные используются для предоставления функциональных возможностей. Предыдущий пример демонстрирует, как получить сигнатуру метода, связанного с типом. Объект Methodlnf о содержит все необходимые метаданные для метода класса. Пример Dynamic (Динамический) демонстрирует очень простой случай динамического связывания.
Мы динамически загружаем сборку и получаем метаданные для метода определенного типа:
// Загрузить (Load) сборку Customer (Клиент)
Assembly *a = Assembly::Load("Customer") ;
// Загрузка ("Клиент")
// Получить метаданных для класса Customers (Клиенты)
// и одного метода
Type *t = a->GetType("01.NetCpp.Acme.Customers"); // Клиенты
Methodlnfо *mi = t->GetMethod("GetCustomer");
Вот что следует помнить программистам при программировании на C++. Работая со строками, которые содержат пространства имен или классы, необходимо использовать должным образом отформатированные строки, которые понятны методам класса отражения. Например, полностью определенное имя класса в вышеописанном коде — OI.NetCpp.Acme.Customers, а не OI::NetCpp::Acme::Customers, отформатированное в стиле C++. Таким образом, используемый формат подобен формату в С#, а не в C++.
Применяя классы отражения, мы могли бы сделать все это полностью динамически, произвольно выбирая типы, методы, и конструкторы из сборки Customer (Клиент), используя методы последнего примера, но мы хотели сохранить пример Dynamic (Динамический) простым. Более честолюбивая программа могла бы делать что-нибудь гораздо более интересное, вроде реализации декомпилятора сборки, который непосредственно из откомпилированной сборки генерирует исходный текст на управляемом C++, С#илиУВ.НЕТ.
Пространство имен System (Система) содержит класс Activator (Активатор, Модуль активизации), в котором перегружается метод Createlnstance, предназначенный для создания экземпляров любого типа .NET с помощью подходящего конструктора. Класс Activator (Активатор, Модуль активизации) рассматривается в этой главе в разделе, посвященном удаленному доступу. Чтобы создать экземпляр объекта Customers (Клиенты), мы вызываем конструктор без параметров.
Type *t = a->GetType("01.NetCpp.Acme.Customers"); // Клиенты
Object *customerlnstance = // Объект
Activator::Createlnstance(t); // Активатор
Затем, чтобы вызвать метод GetCustomer, формируем список параметров и используем метод Invoke (Вызвать) экземпляра Methodlnfо.
Dynamic\Customer\Debug в папку DynamicXDebug перед выполнение Dynamic . ехе.
// вызвать метод
Object *arguments [] = new Object*[1]; // новый Объект
int customerld = -1;
arguments[0] = _box(customerld); // параметры
Object *returnType = mi->Invoke( // Вызвать
customerlnstance, arguments); // параметры
Используя методы отражения, мы получаем информацию о типе для каждого поля в возвращаемой структуре. Обратите внимание, что метод GetValue, принадлежащий Fieldlnf о, возвращает данные для конкретного поля в объекте.
if (returnType->GetType() ==
Type::GetType("System.Collections.ArrayList"))
// ("Система.Коллекции.Список массивов")
{
ArrayList *arrayList =
dynamic_cast<ArrayList *>(returnType);
for (int i = 0; i<arrayList->Count; i++) // Счет
{
Type *itemType =
arrayList->get_Item(i)->GetType();
Fieldlnfo *fi [] = itemType->GetFields();
for (int j = 0; j < fi->Length; j++)
{
Object *fieldValue = // Объект
fi[j]->GetValue(arrayList->get_Item(i));
Console::Write( // Запись
"{0, -10} = {1, -15}",
fi[j]->Name, fieldValue); // Имя
}
Console::WriteLine();
}
}
Снова обращаем внимание на то, что в строке System.Collections.ArrayList (Система.Коллекции.Список массивов) для отделения имен использованы точки, а не "двойные двоеточия.
В этом коде не использованы никакие определенные объекты или типы из сборки Customer (Клиент). С целью проиллюстрировать главные принципы, мы применили некоторые знания о сборке, чтобы код был простым. Однако должно быть понятно, как сделать его полностью общим.
Можно сделать шаг вперед и использовать классы, которые генерируют метаданные (в System: :Reflection: :Emit (Система-Отражение-Генерация)). Можно даже динамически создавать сборку в памяти, а затем загружать и выполнять ее!

Ввод и вывод в .NЕТ


Грубо обобщая, можно разделить функции ввода/вывода в каркасе .NET Framework на две широких категории, не зависящих от устройства хранения данных (диск, память, и т.д.): это запись и чтение.
Данные могут рассматриваться как поток байтов или символов. Например, мы могли прочитать 500 байтов из файла, и записать их в буфер памяти. Данные также можно рассматривать как набор объектов. Чтение и запись объектов называются соответственно преобразованием из последовательной формы в параллельную и преобразованием в последовательную форму (сериализацией). Мы можем сериализировать (записать) список объектов типа Customer (Клиент) на диск. Затем мы можем список объектов Customer (Клиент) преобразовать из последовательной формы в параллельную, т.е. прочитать этот список обратно в память.
В пространстве имен System::IO есть несколько классов для чтения и записи, позволяющих использовать различные устройства хранения, если только данные можно трактовать как байты или символы. Функциональные возможности сериализации могут проявляться в различных местах каркаса .NET. Пространство имен System::Runtime::Serialization (Система-Время выполнения::Сериализация) используется для сериализации в общей системе типов (Common Type System). Пространство имен System::Xml: Serialization (Система::Хml::Сериализация) используется для сериализации XML-документов.

Потоковые классы


Stream (Поток, Абстрактный последовательный файл) — абстрактный класс, который является базовым для чтения и записи байтов в некоторое хранилище данных типа файла. Этот класс поддерживает синхронные и асинхронные чтение и запись. Асинхронные методы обсуждаются позже в данной главе. Класс Stream (Поток, Абстрактный последовательный файл) содержит типичные и вполне ожидаемые от такого класса методы: Read (Чтение), Write (Запись), Seek (Поиск), Flush (Дозапись) и Close (Закрыть).
Класс FileStream, который является производным от класса Stream (Поток, Абстрактный последовательный файл), предоставляет операции чтения и записи последовательности байтов в файл. Конструктор FileStream создает экземпляр потока. Перегруженные методы класса Stream (Поток, Абстрактный последовательный файл) осуществляют чтение и запись в файл.
У класса Stream (Поток, Абстрактный последовательный файл) есть и другие производные классы: MemoryStream, BufferedStream и NetworkStream (в System: :Net: : Sockets (Система::Сеть::Сокеты)).
Пример FileStream (в папке FilelO с примерами ввода/вывода) иллюстрирует, как использовать потоковый класс Stream (Поток, Абстрактный последовательный файл). Если файл не существует, создается новый, а потом в этот файл записываются числа от 0 до 9. Если файл уже существует, программа читает 5 байтов в конце файла и затем выводит их на консоль. (Пример нужно выполнить дважды. Первый раз программа создаст файл и запишет в него числа, а во второй раз прочитает и распечатает часть файла).
unsigned char data _gc[] =
// сборщик мусора - данные - символы без знака
new unsigned char _gc [10];
// сборщик мусора - новый символ без знака
FileStream *fs = new FileStream(
"FileStreamTest.txt",
FileMode::OpenOrCreate);
if (fs->Length == 0)
// если Длина == 0
{
Console::WriteLine("Writing Data...");
// Запись данных
for (short i = 0; i < 10; i++) data[i] = (unsigned char)i;
// данные = символ без знака
fs->Write(data, 0, 10);
// Запись данных
}
else
{
fs->Seek(-5, SeekOrigin::End);
// Ищем конец
int count = fs->Read(data, 0, 10);
// Чтение данных
for (int i = 0; i < count; i++)
// счет
{
Console::WriteLine(data[i]);
// данные
}
}
fs->Close();

Примитивные типы данных и потоки


Классы, производные от Stream (Поток, Абстрактный последовательный файл), целесообразно использовать тогда, когда нужно читать или писать байты данных блоками. Если необходимо прочитать в поток или записать из потока простой тип, такой как Boolean (булев, логический), String (Строка) или Int32, следует использовать классы BinaryReader и BinaryWriter. Пример Binary (Двоичный) в папке FilelO показывает, как использовать эти классы. Нужно создать соответствующий поток (FileStream в примере) и передать его в качестве параметра в конструктор BinaryReader или BinaryWriter. Потом можно использовать один из перегруженных методов Read (Чтение) или Write (Запись) для чтения данных из потока или записи данных в поток. (Причем пример опять нужно выполнить дважды. Сначала файл создается и в него записываются данные. Во второй разданные читаются из файла.)
FileStream *fs = new FileStream(
"BinaryTest.bin", FileMode::OpenOrCreate);
if (fs->Length == 0) // если Длина == 0
{
Console::WriteLine("Writing Data...");
// Запись данных
BinaryWriter *w = new BinaryWriter(fs);
for (short i = 0; i < 10; i++) w->Write(i);
// Запись
w->Close () ;
}
else
{
BinaryReader *r = new BinaryReader(fs);
for (int i = 0; i < 10; i++)
Console::WriteLine(r->ReadInt16());
r->Close();
}
fs->Close();

TextReader И TextWriter


В абстрактных классах TextReader и TextWriter данные рассматриваются как последовательный поток символов (то есть, просто как текст). TextReader имеет следующие методы: Close (Закрыть). Peek (Считывание элемента данных), Read (Чтение), ReadBlock, ReadLine и ReadToEnd. TextWriter содержит методы типа Close (Закрыть), Flush (Дозапись), Write (Запись) и WriteLine. Перегруженные методы Read (Чтение) читают символы из потока. Перегруженные методы Write (Запись) и WriteLine записывают данные различных типов в поток. Если объект записывается в поток, то используется метод ToString объекта.
StringReader и StringWriter являются производными классами от классов TextReader и TextWriter соответственно. StringReader и StringWriter читают и записывают данные в символьную строку, которая сохраняется в базовом объекте StringBuilder. Конструктор StringWriter может принимать объект StringBuilder. Класс StringBuilder обсуждался в главе 3 "Программирование на управляемом C++".
StreamReader и StreamWriter также являются производными классами от классов TextReader и TextWriter. Они читают текст из объекта и записывают текст в объект Stream (Поток, Абстрактный последовательный файл). Так же как и в случае классов BinaryReader и BinaryWriter, можно создать объект Stream (Поток, Абстрактный последовательный файл) и передать его в конструктор StreamReader или StreamWriter. Следовательно, эти классы могут использовать любые унаследованные от Stream (Поток, Абстрактный последовательный файл) классы хранения данных. Пример Text (Текст) из папки File использует классы StreamReader и StreamWriter. Программу необходимо выполнить дважды: первый раз — чтобы создать файл, а затем второй раз — чтобы прочитать его.
FileStream *fs = new FileStream(
"TextTest.txt", FileMode::OpenOrCreate);
if (fs->Length == 0) // если Длина == О {
Console::WriteLine("Writing Data..."); // Запись данных
StreamWriter *sw = new StreamWriter(fs);
sw->Write(100); // Запись
sw->WriteLine(" One Hundred"); // Сто
sw->WriteLine("End of File"); // Конец Файла
sw->Close();
}
else
{
String *text; // Строка
StreamReader *sr = new StreamReader(fs) ;
text = sr->ReadLine(); // текст
while (text != 0)
// пока (текст!= О)
{
Console::WriteLine(text);
// текст
text = sr->ReadLine();
// текст
}
sr->Close ();
}
fs->Close ();

Обработка файлов


Каркас содержит два класса File (Файл) и File Info, которые очень полезны для работы с файлами. Класс File (Файл) предоставляет основные функциональные возможности для обработки файлов в дополнение к операциям чтения и записи. Поскольку класс File (Файл) содержит только статические члены, имя файла необходимо в качестве параметра. Класс Filelnfo имеет конструктор, который создает объект, представляющий файл. Затем используются методы для обработки этого определенного файла.
Методы класса File (Файл) всегда выполняют проверку защиты. Если необходимо непрерывно обращаться к определенному файлу, можно использовать класс Filelnfo, так как в этом классе проверка защиты производится только однажды — в конструкторе. Защита обсуждается более подробно в главе 13 "Защита".
Класс File (Файл)
Класс File (Файл) содержит методы для создания и открытия файлов, которые возвращают объекты FileStream, StreamWriter или StreamReader, производящие фактическое чтение и запись. Перегруженный метод Create (Создать) возвращает объект FileStream. Метод CreateText возвращает StreamWriter. Перегруженный метод Open (Открыть) в зависимости от передаваемых параметров может создавать новый файл или открывать существующий для чтения или записи. Возвращаемый объект — объект FileStream. Метод OpenText возвращает StreamReader. Метод OpenRead возвращает объект FileStream. Метод OpenWrite возвращает объект типа FileStream.
Класс File (Файл) содержит также методы копирования, удаления и перемещения файлов. К тому же, мы можем проверить существование файла. Нижеперечисленные атрибуты файла можно прочитать и изменить:
• время создания;
• время последнего обращения;
• последнее время записи;
• архивный, скрытый, обычный, системный или временный;
• сжатый, зашифрованный;
• только для чтения;
• файл — это каталог?
• Класс Path (Путь)
Очень часто имя файла, передаваемое в качестве входного параметра, должно быть полным путем к файлу. Но нередко удобнее было бы обрабатывать только части пути. Довольно просто это можно сделать с помощью статических методов класса Path (Путь). Класс Path (Путь) содержит статические поля, указывающие на различные зависимые от платформы части имени пути, такие как символы разделителя для каталогов, путей и томов, а также запрещенные символы в имени файла и пути доступа к нему.
Статические методы этого класса позволяют изменять расширение файла или находить папку с временными файлами. Особенно полезен метод GetFullPath. Ему можно передать относительный путь, например \foo.txt, и он возвратит полный путь файла. Это очень полезно для класса File (Файл) или классов защиты, для которых требуется указывать полный путь к файлу.
Класс Filelnfo
Конструктор Filelnfo создает объект, который представляет дисковый файл. Конструктор принимает один параметр — строку, содержащую имя файла. Объект будет иметь свойства, отражающие свойства файла, такие как время создания, размер и полный путь к файлу. Класс содержит методы для создания и открытия файла, которые аналогичны методам класса File (Файл), но работают с конкретным экземпляром файла и поэтому не нуждаются в таком параметре, как имя файла. Класс Filelnf о также содержит методы, позволяющие перемещать и копировать файлы.
Пример File (Файл)
Пример File (Файл) в папке FilelO иллюстрирует использование классов Filelnfo и File (Файл). В этом примере используется статический метод Delete (Удалить) класса File (Файл) для удаления указанного файла. Затем статический метод CreateText создает новый файл и возвращает экземпляр StreamWriter, который используется для записи текста в файл. Далее поток закрывается, и статический метод Move (Переслать) переименовывает файл. Потом создается экземпляр Filelnfo, который будет представлять этот переименованный файл. Полное имя файла, размер и дата его создания выводятся на консоль. Файл открывается как текстовый, после чего используется экземпляр streamReader, чтобы прочитать и вывести на консоль содержимое файла.
File::Delete("file2.txt"); // Удалить файл "file2.txt"
StreamWriter *sw =
System::IO::File::CreateText("file.txt");
sw->Write ("The time has come the Walrus said, "); // Поговорить...
sw->WriteLine("to talk of many things.");
sw->Write("Of shoes, and ships, and sealing wax, "); // о ботинках, и судах, и сургуче
sw->WriteLine("of cabbages and kings."); // капусте и о королях
sw->Write("And why the sea is boiling hot, "); // И почему
// море кипит
sw->WriteLine("and whether pigs have wings."); // и имеют ли
// свиньи крылья.
sw->Close();
File::Move("file.txt", "file2.txt");
//Файл:: Переслать ("file.txt", "file2.txt");
Filelnfo *filelnfo = new Filelnfo("file2.txt");
Console::WriteLine(
"File {0} is {1} bytes in length, created on {2}",
// "Файл {0} - {1} байтов, создан {2} ",
file!nfo->FullName,
_box(file!nfo->Length), // Длина
_box(file!nfo->CreationTime));
Console::WriteLine("");
StreamReader *sr = file!nfo->OpenText();
String *s = sr->ReadLine();
// Строка
while (s != 0)
// пока (s != 0)
{
Console::WriteLine(s);
s = sr->ReadLine();
}
sr->Close();
Console::WriteLine("");


Сериализация, или преобразование в последовательную форму


Сохранение сложной структуры данных со связанными объектами может стать довольно громоздким при использовании классов File (Файл) и Stream (Поток, Абстрактный последовательный файл). Необходимо сохранить все индивидуальные поля на диске. При этом нужно помнить, какое из полей какому объекту принадлежит, и какой экземпляр класса связан с другими экземплярами объектов. При восстановлении нужно воссоздать порядок расположения полей и объектные ссылки.
Каркас .NET Framework предоставляет технологию сериализации (преобразования в последовательную форму), которая выполняет все вышесказанное самостоятельно. Сериализация преобразовывает объекты, такие как классы, структуры и массивы в поток байтов. При преобразовании из последовательной формы в параллельную поток байтов преобразовывается обратно в объекты. Сериализация и преобразование из последовательной формы в параллельную могут быть проделаны на различных машинах, если на них работает общеязыковая среда времени выполнения CLR.
Объекты могут быть преобразованы в последовательную форму без применения специально написанного кода, потому что, как мы видели, во время выполнения можно запросить метаданные объекта, что дает возможность узнать распределение памяти, занятой этим объектом. Чтобы информировать каркас, что класс может быть преобразован в последовательную форму, нужно пометить класс атрибутом System: :Serializable (Система::Преобразуемый в последовательную форму). Любое поле или свойство, которые не должны быть преобразованы в последовательную форму, могут быть отмечены атрибутом System: :NonSerialized (Система::Непреобразуемый в последовательную форму). Например, поля, которые представляют собой кэшированные переменные, не должны преобразовываться в последовательную форму. Все, что нужно сделать — пометить класс атрибутом, указывающим, что класс может быть преобразован в последовательную форму. И тогда нет необходимости в любом другом коде, выполняющем преобразование в последовательную форму.
Пример Serialization (Сериализация) показывает, как применить преобразование в последовательную форму для изучения класса HotelBroker в сборке Hotel (Гостиница). Атрибут Serializable (Преобразуемый в последовательную форму) применяется в определении класса HotelBroker. Атрибут сериализации Serializable (Преобразуемый в последовательную форму) применяется также ко всем классам, которые используются классом HotelBroker или производными от базовых классов класса HotelBroker: Broker (Брокер), Hotel (Гостиница), HotelReservation, Reservable и Reservation (Резервирование), потому что при сериализации HotelBroker должны быть также преобразованы в последовательную форму и эти классы. Если бы любой из этих классов не был отмечен атрибутом, то во время выполнения могло бы произойти исключение при попытке сериализации объекта этого типа.
[Serializable]
// [Преобразуемый в последовательную форму]
public _gc class HotelBroker:
// класс сборщика мусора HotelBroker:
public Broker,
// общедоступный Брокер
public IHotellnfo,
public IHotelAdmin,
public IHotelReservation
{
private: // частный
const int MAXDAY; // константа
const int MAXUNIT; // константа
[NonSerialized] ArrayList *cities;
// [Непреобразуемый в последовательную форму]
};
[Serializable]
// [Преобразуемый в последовательную форму]
public _gc class Hotel :
public Reservable
// класс сборщика мусора Гостиница:
Reservable
{
};
[Serializable]
// [Преобразуемый в последовательную форму]
public _gc class HotelReservation :
public Reservation
// общедоступный класс сборщика мусора HotelReservation:
Резервирование
{
} ;
[Serializable]
// [Преобразуемый в последовательную форму]
public _gc _abstract class Reservable
// сборщик мусора - абстрактный класс Reservable
{
} ;
[Serializable]
// [Преобразуемый в последовательную форму]
public _gc _abstract class Reservation
// сборщик мусора - абстрактный класс Reservation
{
} ;
[Serializable]
// [Преобразуемый в последовательную форму]
public _gc _abstract class Broker
// сборщик мусора - абстрактный класс Broker
{
};
Поле cities (города) было помечено как NonSerialized (Непреобразуемый в последовательную форму), так как название города, где размещается гостиница, сохраняется вместе с преобразованными в последовательную форму названиями гостиниц. Поэтому оно может быть восстановлено с помощью модифицированного метода AddCity. Поле cities (города) было бы пусто после преобразования класса HotelBroker из последовательной формы в параллельную, потому что оно не было сохранено.
private: // частный
void AddCity(String *city) // (Строка *city)
{
if (cities == 0) // если (города == 0)
{
cities = new ArrayList; // города
lEnumerator *pEnum = units->GetEnumerator();
while (pEnum->MoveNext() )
{
Hotel *h = // Гостиница
dynamic_cast<Hotel *>(pEnum->Current);
AddCity(h->City); // Город
}
}
// check if city already on list, add if not
// проверить, есть ли город уже в списке, добавить если нет
if (!cities->Contains(city))
// если (! города-> Содержат (город))
cities->Add(city);
// города-> Добавить (город);
}

Объекты сериализации


Хотя каркас знает, как сохранять объект, помеченный атрибутом Serializable (Преобразуемый в последовательную форму), но все же необходимо определить формат, в котором будет сохранен объект, и носитель данных. Чтобы определить формат, в котором будет сохранен объект, нужно использовать экземпляр объекта, который поддерживает интерфейс IFormatter.
Каркас имеет два таких класса: System: :Runtime: :Serialization: : Formatters :: Binary: : BinaryFormatter (Система::Время выполнения:: Преобразование в последовательную форму::Форматеры::Двоичный::ВтагуРогтаиег) и System :: Runtime :: Serialization :: Formatters :: Soap :: Soар Formatter (Система :: Время выполнения :: Преобразование в последовательную форму :: Форматеры :: Sоар:: Soap-Formatter). BinaryFormatter использует двоичный, компактный формат для сериализации и преобразования из последовательной формы в параллельную на платформах, которые поддерживают общеязыковую среду времени выполнения CLR. SoapForrnatter использует промышленный стандарт протокола SOAP, который обсуждается в главе 11 "Web-службы". Так как он основан на XML, и, следовательно, является текстовым протоколом, он может использоваться для связи с платформой, не основанной на общеязыковой среде времени выполнения CLR. Двоичный формат быстрее при сериализации и преобразовании данных из последовательной формы в параллельную.
Можно, конечно, создать свои собственные классы форматирования. Это может понадобиться лишь в том случае, если при взаимодействии с внешней системой нужно учитывать ее собственный байтовый формат файловых объектов.
Пример Serialization (Сериализация) содержит код, демонстрирующий использование FileStream для сохранения и восстановления обоих форматов: двоичного и SOAP. Конечно, можно использовать любой класс, производный от Stream (Поток, Абстрактный последовательный файл), лишь бы он представлял некоторый носитель данных. Необходимо предпринять специальные меры, чтобы гарантировать, что метод Load (Загрузка) сможет изменять параметр, который указывает на HotelBroker. Для этого параметр объявляется как ссылка на указатель, указывающий на HotelBroker.
static void Save( // статический метод Сохранить
HotelBroker *broker, String *formatter)
{
FileStream *s;
if (String::Equals(formatter, "b"))
// если (Строка::Равняется (форматер, "b"))
{
s = new FileStream(
"hotels.bin", FileMode::Create); // Создать
BinaryFormatter *b = new BinaryFormatter;
b->Serialize (s, broker);
// Преобразовать в последовательную форму (s, брокер);
}
else
{
s = new FileStream(
"hotels.txt", FileMode::Create); // Создать
SoapFormatter *sf = new SoapFormatter;
sf->Serialize(s, broker);
// Преобразовать в последовательную форму (з, брокер);
}
s->Close ();
}
static void Load( // статический метод Загрузка
HotelBroker *&broker, /* ссылка на указатель */
String *formatter) // Строка
{
FileStream *s;
if (String::Equals(formatter, "b"))
// если (Строка::Равняется (форматер, "b"))
{
s = new FileStream("hotels.bin", FileMode::Open); // Открыть
BinaryFormatter *b = new BinaryFormatter; broker = // брокер
dynamic_cast<HotelBroker *>
(b->Deserialize (s) ) ;
}
else
{
s = new FileStream("hotels.txt", FileMode::Open);
// Открыть
SoapFormatter *sf = new SoapFormatter;
broker = // брокер
dynamic_cast<HotelBroker *>(sf->Deserialize(s));
}
s->Close();
ShowHotelList(broker->GetHotels());
}
Ниже приведен некоторый типовой вывод примера Serialization (Сериализация): сначала мы добавляем название гостиницы и сохраняем его с помощью форматера SOAP. Затем мы выходим из программы.
Enter command: cities
Atlanta
Boston
Commands: quit, cities, list, add, fetch, save
Enter command: list
City Name Rooms Rate
Atlanta Dixie 100 115
Atlanta Marriott 500 70
Boston Sheraton 250 95
Commands: quit, cities, list, add, fetch, save
Enter command: add
Hotel City: Philadelphia
Hotel Name: Franklin
Number Rooms: 100
Room Rate: 200
Commands: quit, cities, list, add, fetch, save
Enter command: save
Formatter: b(inary), s(oap)s
Commands: quit, cities, list, add, fetch, save
Enter command: cities
Atlanta
Boston
Philadelphia
Commands: quit, cities, list, add, fetch, save
Enter command: list
City Name Rooms Rate
Atlanta Dixie 100 115
Atlanta Harriot 500 70
Boston Sheraton 250 95
Philadelphia Franklin 100 200
Commands: quit, cities, list, add, fetch, save
Enter command: quit
Перевод такой:
Введите команду: города
Атланта
Бостон
Команды: выход, города, список, добавить, выборка, сохранить
Введите команду: список
Город Название Номера Цена
Атланта Дикси 100 115
Атланта Мэриот 500 70
Бостон Шератон 250 95
Команды: выход, города, список, добавить, выборка, сохранить
Введите команду: добавить
Город Гостиницы: Филадельфия
Название Гостиницы: Фрэнклин
Номера: 100
Цена: 200
Команды: выход, города, список, добавить, выборка, сохранить
Введите команду: сохранить
Форматер: b(inary), s(oap)s
Команды: выход, города, список, добавить, выборка, сохранить
Введите команду: города
Атланта
Бостон
Филадельфия
Команды: выход, города, список, добавить, выборка, сохранить
Введите команду: список
Город Название Номера Цена
Атланта Дикси 100 115
Атланта Мзриот 500 70
Бостон Шератон 250 95
Филадельфия Фрэнклин 100 200
Команды: выход, города, список, добавить, выборка, сохранить
Введите команду: выход
Затем выполняем программу снова и восстанавливаем данные, сохраненныепри первом запуске.
Enter command: cities
Atlanta
Boston
Commands: quit, cities, list, add, fetch, save
Enter command: list
City Name Rooms Rate
Atlanta Dixie 100 115
Atlanta Marriot 500 70
Boston Sheraton 250 95
Commands: quit, cities, list, add, fetch, save
Enter command: fetch Formatter: b(inary), s(oap)s
City Name Rooms Rate
Atlanta Dixie 100 115
Atlanta Marriot 500 70
Boston Sheraton 250 95
Philadelphia Franklin 100 200
Commands: quit, cities, list, add, fetch, save
Enter command: cities
Atlanta
Boston
Philadelphia
Пере вод такой:
Введите команду: города
Атланта
Бостон
Команды: выход, города, список, добавить, выборка, сохранить
Введите команду: список
Город Название Номера Цена
Атланта Дикси 100 115
Атланта Мэриот 500 70
Бостон Шератон 250 95
Команды: выход, города, список, добавить, выборка, сохранить
Введите команду: выборка
Форматер: b(inary), s(oap)s
Город Название Номера Цена
Атланта Дикси 100 115
Атланта Мэриот 500 70
Бостон Шератон 250 95
Филадельфия Фрэнклин 100 200
Команды: выход, города, список, добавить, выборка, сохранить
Введите команду: города
Атланта
Бостон
Филадельфия

ISerializable
Иногда методов преобразования в последовательную форму (сериализации), предоставляемых каркасом, недостаточно. В таком случае можно предусмотреть специальную сериализацию класса. Для этого необходимо реализовать интерфейс ISerializable и добавить к классу конструктор, как это показано в проекте Serialization (Сериализация) в папке ISerializable. Интерфейс ISerializable имеет один член: GetObj ectData. Этот метод используется во время сериализации данных.
Пример ISerializable демонстрирует, как это можно сделать. Как и прежде, класс должен быть отмечен как Serializable (Преобразуемый в последовательную форму).
[Serializable]
// [Преобразуемый в последовательную форму]
public _gc class HotelBroker :
// класс сборщика мусора HotelBroker:
public Broker, // общедоступный Брокер
public iHotellnfo,
public IHotelAdmin,
public IHotelReservation,
public ISerializable
{
};
Чтобы сохранить все данные, которые должны быть сохранены в методе ISerializable: : GetObjectData, используется класс Serializationlnfo. Для управления сохранением различных типов данных, включая Object*, перегружается метод AddValue класса Serializationlnfo. Когда сохраняется тип, необходимо назначить ему имя так, чтобы можно было позже его восстановить. Класс StreamingContext предоставляет информацию о потоке, используемом при сериализации. Например, можно узнать, является ли используемый поток файлом или удаленным потоком, который посылается на другой компьютер.
public:
void GetObjectData(Serializationlnfo *info,
StreamingContext context) // контекст
{
long numberHotels = units->Count;
info->AddValue("NumberHotels", numberHotels) ;
info->AddValue("Hotels", units); // Гостиницы
}
Нужно также реализовать специальный конструктор, который используется каркасом, когда объект преобразуется из последовательной формы в параллельную. Он принимает те же параметры, что и GetObjectData. В нем для восстановления данных используются различные методы GetXXX класса Serializationlnfo. Обращаем внимание читателя на то, что, поскольку мы не сохраняли поле cities (города), то были вынуждены восстанавливать его вручную. Конструктор объявляется приватным, потому что использует его только каркас. Если забыть добавить конструктор, то при попытке восстановить объект будет запущено исключение SerializationException.
private: // частный
HotelBroker(Serializationlnfo *info,
StreamingContext context) // контекст
: Broker(366, 10), MAXDAY(366), MAXUNIT(IO) // Брокер
{
long numberHotels =
info->Get!nt32("NumberHotels");
units = dynamic_cast<ArrayList *>(
info->GetValue(
"Hotels", units->GetType())); // Гостиницы
if (numberHotels == units->Count)
Console::WriteLine("All hotels deserialized.");
// "Все гостиницы преобразованы из последовательной
// формы в параллельную. "
else
Console::WriteLine("Error in deserialization.");
// "Ошибка при преобразовании из последовательной
// формы в параллельную."
cities = new ArrayList;
// города
lEnumerator *pEnum = units->GetEnumerator();
while (pEnum->MoveNext())
{
Hotel *h = // Гостиница
dynamic_cast<Hotel *>(pEnum->Current); // Гостиница
AddCity(h->City); // Город
}
}
Необходимо помнить, что после внесения любых изменений и последующей компоновки проекта Hotel (Гостиница), нужно скопировать Hotel. dll в папку, где находится программа-клиент Serialization.exe. Это не происходит, как в С#, автоматически, если не добавить в проект специальный шаг компоновки.
В данном примере мы выполняли пользовательскую сериализацию только объекта HotelBroker. Для всех других объектов мы использовали сериализацию, предоставляемую каркасом. Этот пример работает так же, как и пример Serialization (Сериализация). Поэтому вывод программы выглядит аналогично.

Модель приложений .NET
Сериализация дала конкретный пример гибкости среды каркаса .NET Framework, используемой при написании кода. Теперь давайте рассмотрим модель, в которой выполняются .NET-приложения. Среда платформы Win32, в которой выполняется программа, называется ее процессом. Эта среда состоит из
• адресного пространства, в котором хранится код и данные программы;
• набора переменных среды, связанных с программой;
• текущего диска и папки;
• одного или более потоков.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика