Рейтинговые книги
Читем онлайн ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 83 84 85 86 87 88 89 90 91 ... 259

Условная компиляция

Другой пакет директив препроцессора (#if, #elif, #else, #endif) позволяет выполнить компиляцию блока программного кода по условию, базируясь на предварительно заданных символах. Классическим вариантом использования этих директив является идентификация блока программного кода, который компилируется только при отладке (а не при окончательной компоновке).

class Program

 static void Main(string[] args) {

  // Этот программный код выполняется только при отладочной

  // компиляции проекта.

#if DEBUG

  Console.WriteLine("Каталог приложения: {0}", Environment.CurrentDirectory);

  Console.WriteLine("Блок: {0}", Environment.MachineName);

  Console.WriteLine("ОС: {0}", Environment.OSVersion);

  Console.WriteLine("Версия .NET: {0}", Environment.Version);

#endif

 }

}

Здесь выполняется проверка на символ DEBUG. Если он присутствует, выводится ряд данных состояния, для чего используются соответствующие статические члены класса System.Environment. Если символ DEBUG не обнаружен, то программный код, размещенный между #if и #endif, компилироваться не будет и в результирующий компоновочный блок не войдет, т.е. будет фактически проигнорирован.

По умолчанию Visual Studio 2005 всегда определяет символ DEBUG, однако такое поведение можно отменить путем снятия отметки флажка Define DEBUG constant (Определить константу DEBUG) на вкладке Build (Сборка), размещенной на странице Properties (Свойства) вашего проекта. В предположении о том, что этот обычно генерируемый символ DEBUG отключен, можно определить этот символ для каждого файла в отдельности, используя директиву препроцессора #define.

#define DEBUG using System;

namespace Preprocessor {

 class ProcessMe {

  static void Main(string[] args) {

   // Программный код, подобный показанному выше…

}

 }

}

Замечание. Директивы #define в файле с программным кодом C# должны быть указаны до всех остальных.

Можно также определять свои собственные символы препроцессора. Предположим, например, что у нас есть класс C#, которой должен компилироваться немного иначе в рамках дистрибутива Mono.NET (см. главу 1). Используя #define, можно определить символ MONO_BUILD для каждого файла.

#define DEBUG

#define MONO_BUILD

using System;

namespace Preprocessor {

 class Program {

  static void Main (string[] args) {

#if MONO_BUILD

   Console.WriteLine("Компиляция для Mono!");

#else

   Consоlе.WriteLine("Компиляция для Microsoft .NET");

#endif

  }

 }

}

Чтобы создать символ, применимый для всего проекта, используйте текстовый блок Conditional compilation symbols (Символы условной компиляции, размещенный на вкладке Build (Сборка) страницы свойств проекта (рис. 9.6).

Рис. 9.6. Определение символа препроцессора для применения в рамках всего проекта

Резюме

Целью этой главы является более глубокое изучение возможностей языка программирования C#. Глава началась с обсуждения ряда достаточно сложных конструкций программирования (методов индексатора, перегруженных операций и пользовательских подпрограмм преобразования). Затем был рассмотрен небольшой набор не слишком широко известных ключевых слов (таких, как sizeof, checked, unsafe и т.д.), обсуждение которых естественно привело к рассмотрению вопросов непосредственной работы с типами указателя. При исследовании типов указателя было показано, что в подавляющем большинстве приложений C# для использования типов указателя нет никакой необходимости.

ГЛАВА 10. Обобщения

С появлением .NET 2.0 язык программирования C# стал поддерживать новую возможность CTS (Common Type System – общая система типов), названную обобщениями (generics). Упрощенно говоря, обобщения обеспечивают программисту возможность определения "заполнителей" (формально называемых параметрами типа) для аргументов методов и определений типов, которые будут конкретизированы во время вызова обобщенного метода или при создании обобщенного типа.

С целью иллюстрации этой новой возможности языка мы начнем главу с рассмотрения пространства имен System.Collections.Generic. Рассмотрев примеры поддержки обобщений в библиотеках базовых классов, в остальной части этой главы мы попытаемся выяснить, как строить свои собственные обобщения членов, классов, структур, интерфейсов и делегатов.

Снова о создании объектных образов, восстановлении значений и System.Object

Чтобы понять, в чем заключаются преимущества использования обобщений, следует выяснить, какие проблемы возникают у программиста без их использования. Вы должны помнить из главы 3, что платформа .NET поддерживает автоматическое преобразование объектов стека и динамической памяти с помощью операций создания объектного образа и восстановления из объектного образа. На первый взгляд, эта возможность кажется не слишком полезной и имеющей, скорее, теоретический, чем практический интерес. Но на самом деле возможность создания объектного образа и восстановления из объектного образа очень полезна тем, что она позволяет интерпретировать все, как System.Object, оставив все заботы о распределении памяти на усмотрение CLR.

Чтобы рассмотреть особенности процесса создания объектного образа, предположим, что мы создали System.Collections.ArrayList для хранения числовых (т.е. размещаемых в стеке) данных. Напомним, что все члены ArrayList обладают прототипами для получения и возвращения типов System.Object. Но вместо того, чтобы заставлять программиста вручную вкладывать размещенное в стеке целое число в соответствующую объектную оболочку, среда выполнения делает это автоматически с помощью операции создания объектного образа.

static void Main(string[] args) {

 // При передаче данных члену, требующему объект, для

 // характеризуемых значениями типов автоматически создается

 // объектный образ.

 ArrayList myInts = new ArrayList();

 myInts.Add(10);

 Console.ReadLine();

}

Чтобы восстановить значение из объекта ArrayList, используя индексатор типа, вы должны превратить размещенный в динамической памяти объект в размещенное в стеке целое число, используя операцию преобразования.

static void Main(string[] args) {

 …

 // Значение восстанавливается… и снова становится объектом!

 Console.WriteLine("Значение вашего int: {0}", (int)myInts[0]);

 Console.ReadLine();

}

Для представления операции создания объектного образа в терминах CIL компилятор C# использует блок box. Точно так же операция восстановления из объектного образа преобразуется в CIL-блок unbox. Вот соответствующий CIL-код для показанного выше метода Main() (этот код можно увидеть с помощью ildasm.exe).

.method private hidebysig static void Main(string[] args) cil managed {

 …

 box [mscorlib]System.Int32

 callvirt instance int32 [mscorlib] System.Collections.ArrayList::Add(object)

 pop

 ldstr "Значение вашего int: {0}"

 ldloc.0

 ldc.i4.0

 callvirt instance object [mscorlib] System.Collections.ArrayList::get_Item(int32)

 unbox [mscorlib]System.Int32

ldind.i4

 box [mscorlib]System.Int32

call void [mscorlib]System.Console::WriteLine(string, object)

 …

}

Обратите внимание на то. что перед обращением к ArrayList.Add() размещенное в стеке значение System.Int32 преобразуется в объект, чтобы передать требуемый System.Object. Также заметьте, что при чтении из ArrayList с помощью индексатора типа (что отображается в скрытый метод get_Item()) объект System.Object восстанавливается в System.Int32 только для того, чтобы снова стать объектным образом при передаче методу Console.WriteLine().

Проблемы создания объектных образов и восстановления значений

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

1. Новый объект нужно разместить в управляемой динамической памяти.

2. Значение размещенных в стеке данных нужно записать в соответствующее место в памяти.

3. При восстановлении значений, сохраненного в объекте, размещенном в динамической памяти, это значение нужно снова вернуть в стек.

4. Неиспользуемый объект в управляемой динамической памяти (в конце концов) должен быть уничтожен сборщиком мусора.

1 ... 83 84 85 86 87 88 89 90 91 ... 259
На этой странице вы можете бесплатно читать книгу ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание - Эндрю Троелсен бесплатно.
Похожие на ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание - Эндрю Троелсен книги

Оставить комментарий