
Объектно-ориентированное программирование.-6
.pdf
a[i].Address.Index, a[i].Address.City);
}
}
static int Main()
{
Person[] m1 = {
new Person("Иван", 18, new Address("Россия",
"Томск", 634050)),
new Person("Петр", 25, new Address("Россия",
"Новосибирск", 630011)),
new Person("Анна", 22, new Address("Россия",
"Кемерово", 650028))
};
BinaryFormatter bf = new BinaryFormatter(); SoapFormatter sf = new SoapFormatter(); Person[] m2;
ConsoleKeyInfo action;
Out(m1);
using (FileStream fs = File.Create("person.bin"))
bf.Serialize(fs, m1);
using (FileStream fs = File.Create("person.xml"))
sf.Serialize(fs, m1);
Console.WriteLine();
Console.WriteLine("1. Десериализация из двоичного
потока");
Console.WriteLine("2. Десериализация из потока SOAP");
do
{
action = Console.ReadKey(true);
} while (action.KeyChar != '1' && action.KeyChar !=
'2');
Console.WriteLine(action.KeyChar);
Console.WriteLine();
if (action.KeyChar == '1') using (FileStream fs = File.OpenRead("person.bin")) m2 = (Person[])bf.Deserialize(fs);
else using (FileStream fs = File.OpenRead("person.xml")) m2 = (Person[])sf.Deserialize(fs);
Out(m2);
Console.ReadKey(true); return 0;
}
}
}
В данном примере первое поле структуры Person (Count) не подлежит сериализации. Остальные поля экземпляров этой структуры сохраняются в файлы в обоих доступных форматах. Экземпляры агрегированной структуры Address также сериализуются. Если здесь структуры заменить классами (т.е. от типов по значению перейти к ссылочным типам), то ничего не изменится. В любом случае будет сериализован полный граф объектов.
231

Далее можно на выбор произвести десериализацию из двоичного файла или из файла в формате SOAP. Как видно по примеру, здесь сериализуется и десериализуется не один экземпляр структуры Person, а сразу массив экземпляров структуры.
В результате выполнения данного кода на консоли отобразится следующая информация:
Количество записей: 3
Name[0] = Иван Age[0] = 18
Address[0] = Россия, 634050, Томск
Name[1] = Петр Age[1] = 25
Address[1] = Россия, 630011, Новосибирск
Name[2] = Анна Age[2] = 22
Address[2] = Россия, 650028, Кемерово
1.Десериализация из двоичного потока
2.Десериализация из потока SOAP
2
Количество записей: 3 Name[0] = Иван
Age[0] = 18
Address[0] = Россия, 634050, Томск
Name[1] = Петр Age[1] = 25
Address[1] = Россия, 630011, Новосибирск
Name[2] = Анна Age[2] = 22
Address[2] = Россия, 650028, Кемерово
Пример: Samples\3.5\3_5_4_serial.
Здесь можно отметить одну особенность. Хотя были созданы три новых экземпляра структуры Person, счетчик остался равен трем. Это означает, что при десериализации вызов конструктора объекта не производится. Поэтому, если в конструкторе происходит инициализация каких-то полей объекта, не подлежащих сериализации, или вызываются какие-то методы, то эти действия после десериализации придется выполнять самостоятельно, либо реализовать интерфейс System.Runtime.Serialization.ISerializable и описать специальный конструктор:
using System.Runtime.Serialization.ISerializable;
...
class|struct <имя_класса_или_структуры> : ISerializable
{
...
private <имя_класса_или_структуры> (SerializationInfo si, StreamingContext ctx)
232

{
...
}
public void GetObjectData(SerializationInfo si, StreamingContext
ctx)
{
...
}
...
}
Но в этом случае нам придется вручную управлять сериализацией (реализовав метод интерфейса ISerializable.GetObjectData) и десериализацией.
233

§ 3.6. Директивы препроцессора
Как и в языке C++, в языке C# перед компиляцией код обрабатывается препроцессором – программой, которая ищет в коде специальные директивы
исогласно им изменяет код. Некоторые директивы можно определять как извне (с помощью командной строки компилятора или настроек проекта), так
ив исходном коде.
3.6.1. Директивы объявлений
Директивы объявления имеют следующий синтаксис:
<директива объявления> :: #define <идентификатор> <директива объявления> :: #undef <идентификатор>
Идентификатор должен располагаться на той же строке, что и директива. Можно использовать любой корректный идентификатор, в том числе и ключевые слова языка C#, кроме true и false. По сравнению с языком C++, на данные директивы налагаются следующие ограничения:
1)Данные директивы могут располагаться только в самом начале файла, до первой лексемы языка;
2)Директива #define может объявить только условный символ, но не его значение. Эти символы не могут использоваться для подстановки в текст программы. Их использование ограничено директивами условной компиля-
ции (см. п. 3.6.2).
Для определения условных символов для всего проекта зайдите в его свойства («Проект» → «Свойства…») и найдите на закладке «Построение» поле «Символы условной компиляции» (рис. 3.16).
Вводя в это поле через пробел идентификаторы, мы тем самым делаем новые объявления условных символов. Того же можно добиться, используя опцию компилятора /define (краткая форма /d) в командной строке:
csc.exe |
.../define:<идентификатор1>[;<идентификатор2> |
] ... |
|
csc.exe |
/d:<идентификатор1>[;<идентификатор2>... |
] ... |
|
|
|
|
|
Если введенный идентификатор будет недопустимым (как «111» на рис. 3.16), компилятор генерирует предупреждение: «Предупреждение: недопустимый параметр для компилятора. "/define:111" будет пропущен».
234

Рис. 3.16 – Директивы объявления в свойствах проекта
Пример:
//Ошибка: Требуется идентификатор
//#define 111
//Ошибка: Требуется идентификатор
//#define true
#define for // ОК #define xyz // ОК #undef xyz // ОК #undef abc // ОК
using System;
//Ошибка: Невозможно определить символы препроцессора или
//отменить их определение где-либо, кроме начала файла
//#define abc
Пример: Samples\3.6\3_6_1_def.
3.6.2. Директивы условной компиляции
Директивы условной компиляции имеют следующий синтаксис:
235

<директива условной компиляции> :: #if <выражение>
[<лексемы языка и другие директивы>] [#elif <выражение>
[<лексемы языка и другие директивы>]]
...
[#elif <выражение>
[<лексемы языка и другие директивы>]]
[#else
[<лексемы языка и другие директивы>]]
#endif
Данные директивы, как и в языке C++, позволяют исключить некоторые участки кода из компиляции. Если условие директивы #if или #elif истинно, либо есть директива #else, то лексемы, расположенные ниже вплоть до соответствующей директивы #elif, #else или #endif, включаются в компиляцию, иначе исключаются из нее.
Выражение – это логическое выражение, содержащее символы условной компиляции, константы true и false, а также операции «==», «!=», «&&» и «||». Символ условной компиляции считается истинным, если он определен, и ложным в противном случае. Выражение должно располагаться на одной строке с директивой.
Пример:
#define eng #define rus #undef eng
using System;
namespace IfSample
{
class Program
{
static int Main()
{
#if eng // то же, что eng == true
Console.WriteLine("Hello, World!");
#endif
#if rus // то же, что rus == true Console.WriteLine("Здравствуй, мир!");
#endif
#if rus == eng // либо два языка, либо ни одного
#if rus && eng
Console.WriteLine("Это многоязычное приложение");
Console.WriteLine("This is multilingual application");
#else
Console.WriteLine("???");
#endif
#else
#if eng
Console.WriteLine("This is monolingual application");
#elif rus
236

Console.WriteLine("Это одноязычное приложение");
#endif
#endif
return 0;
}
}
}
Комбинируя в начале данного кода объявления символов условной компиляции, мы добиваемся изменений в функционировании программы.
Пример: Samples\3.6\3_6_2_if.
3.6.3. Директивы диагностики
Синтаксис:
<директива диагностики> :: #error <сообщение> <директива диагностики> :: #warning <сообщение> <директива диагностики> :: #line <индикатор строки> <индикатор строки> :: <номер строки> "<имя файла>" <индикатор строки> :: <номер строки>
<индикатор строки> :: default <индикатор строки> :: hidden
Также заимствованные из языка C++ директивы. Первая генерирует ошибку компиляции с указанным сообщением, вторая – предупреждение. Директива #line позволяет переопределить имя файла и номер строки, которые будут выводиться в сообщениях компилятора. Опция default в этой директиве восстанавливает оригинальную нумерацию, а hidden скрывает информацию о номерах строк. При отладке такие строки будут пропущены.
Пример:
//#undef DEBUG using System;
namespace DiagSample
{
class Program
{
static int Main()
{
#line 555 "Главный файл"
#if TRACE
#warning При запуске данной программы трассировку лучше отключить
#endif
throw new Exception();
#line default
#if DEBUG == false
#error Данная программа должна запускаться в режиме отладки
#endif
return 0;
}
237

}
}
При компиляции мы увидим предупреждение:
#warning: 'При запуске данной программы трассировку лучше отключить'
Файл c:\C# Samples\3.6\3_6_3_diag\Главный файл Строка 556
Как видим, директива #line повлияла на информацию, выводимую компилятором. Будьте осторожны – при трассировке компилятор попытается найти файл с именем «Главный файл». При запуске программы увидим следующее:
Необработанное исключение: System.Exception: Выдано исключение типа
"System.Exception".
в DiagSample.Program.Main() в c:\C# Samples\3.6\3_6_3_diag\Главный файл:строка 558
Т.е. директива #line влияет также на генерируемые для исключительной ситуации сведения. Если убрать комментарий из первой строки кода, программа не будет компилироваться:
#error: 'Данная программа должна запускаться в режиме отладки'
Файл C:\C# Samples\3.6\3_6_3_diag\Program.cs 15
Строка 9
Видим, что здесь директивой #line default восстановлена оригинальная информация о строках.
Пример: Samples\3.6\3_6_3_diag.
3.6.4. Директивы регионов
Синтаксис:
<директива региона> :: #region [<сообщение>]
[<лексемы языка и другие директивы>]
#endregion [<сообщение>]
С помощью данных директив можно выделить регион в исходном тексте, Они удобны, так как редактор Visual Studio позволяет сворачивать их. Редактор поддерживает некоторые типы регионов по умолчанию. Регион, который можно свернуть, помечается значком «». Если на него нажать, он свернется в одну строку с многоточием в конце (если на него навести указатель мыши, во всплывающем окне отобразится содержимое региона), а значок изменит свой вид на «
». Если на него нажать еще раз, регион снова развернется. К таким регионам по умолчанию относится блок операторов using,
238

пространства имен, классы, методы, комментарии и т.д.
Директивы регионов позволяют создать регион в любом месте. При сворачивании региона будет отображаться сообщение, заданное в директиве #region, если оно задано. Сообщение в директиве #endregion ни на что не влияет, оно указывается только для повышения читабельности кода.
Пример:
for (int i = 0; i < 10; i++)
{
#region Вывод текста
Console.WriteLine("!!!");
Console.WriteLine("!!!");
Console.WriteLine("!!!");
Console.WriteLine("!!!");
Console.WriteLine("!!!");
Console.WriteLine("!!!");
Console.WriteLine("!!!");
Console.WriteLine("!!!");
Console.WriteLine("!!!");
#endregion
}
Пример: Samples\3.6\3_6_4_reg.
3.6.5. Директивы дополнительных опций
Для указания дополнительных опций компилятору используется единственная директива:
<дополнительные опции> :: #pragma <тело директивы>
Она не влияет на семантику программы. Если тело директивы не распознано, это влечет предупреждение во время компиляции. В настоящий момент компилятор Microsoft поддерживает две дополнительные опции – warning (управление предупреждениями) и checksum (управление контрольными суммами). Мы рассмотрим только первую из них:
<управление предупреждениями> :: #pragma warning <тело предупреждения>
<тело предупреждения> :: disable [<список предупреждений>] <тело предупреждения> :: restore [<список предупреждений>]
Опция disable отключает проверку предупреждений компилятора с указанными номерами (или всех, если список не указан). Опция restore восстанавливает проверку всех или указанных предупреждений до предыдущих значений.
Считается, что хорошая программа должна компилироваться без пре-
239

дупреждений. Если предупреждения по какой-то причине все же возникают (программа просто еще не дописана и т.п.), от них можно избавиться при помощи этих директив. Например, для следующего кода
int i = 0;
if (false && (i++) > 100) i = 0;
будет генерироваться следующее предупреждение:
Предупреждение: Обнаружен недостижимый код в выражении Файл C:\C# Samples\3.6\3_6_5_pragma\Program.cs
Строка 11
Связано это с тем, что в выражении «false && (i++) > 100» код «i++» никогда не будет выполнен (см. особенности работы оператора «&&»). Хотя, возможно, константа false просто заглушка, и в будущем будет заменена другим выражением. Выбираем в окне ошибок это предупреждение и вызываем справочную систему MSDN. Видим описание:
Предупреждение компилятора (уровень 4) CS0429
...
Итак, мы узнали номер этого предупреждения – 0429 и уровень – 4 (наиболее низкий). Чтобы его отключить, можно использовать опции компилятора. На рис. 3.16 мы видим, что в проекте по умолчанию включен 4-й уровень выдачи предупреждений, самый строгий. Если его понизить хотя бы до 3-го, то предупреждение перестанет появляться. Но тогда мы не увидим всех других предупреждений 4-го уровня во всем проекте! А ведь некоторые из них сигнализируют о потенциальных ошибках. Поэтому можно уровень выдачи предупреждений не изменять, а в поле «Отключить предупреждения» перечислить через пробел номера отключаемых предупреждений. Введем в это поле 0429 – и предупреждение также перестанет генерироваться. Однако, проблема остается прежней – возможно, в других местах программы отсутствие такой проверки приведет к семантической ошибке.
Поэтому используем директиву #pragma warning. С ее помощью легко отключить предупреждение 0429 для этой строки, а затем вернуть настройку по умолчанию (номер можно не указывать, т.к. другие предупреждения мы не отключали):
//Предупреждение: Нераспознанная директива #pragma
//#pragma Такой директивы нет
class Program
{
static int Main()
240