ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Перед рассмотрением деталей позвольте заметить, что необходимость в использовании типов указателя возникает очень редко, если она возникает вообще. Хотя C# и позволяет "спуститься" на уровень манипуляций с указателями, следует понимать, что среда выполнения .NET не имеет никакого представления о ваших намерениях. Поэтому если вы ошибетесь в направлении указателя, то за последствия будете отвечать сами. Если учитывать это, то когда же на самом деле возникает необходимость использования типов указателя? Есть две стандартные ситуации.
• Вы стремитесь оптимизировать работу определенных частей своего приложения путем непосредственной манипуляции памятью вне пределов управления CLR.
• Вы хотите использовать методы C-библиотеки *.dll или COM-сервера, требующие ввода указателей в виде параметров.
Если вы решите использовать указанную возможность языка C#, необходимо информировать csc.exe об этих намерениях, указав разрешение для проекта поддерживать "небезопасный программный код". Чтобы сделать это с командной строки компилятора C# (csc.exe), просто укажите в качестве аргумента флаг /unsafe. В Visual Studio 2005 вы должны перейти на страницу свойств проекта и активизировать опцию Allow Unsafe Code (Разрешать использование небезопасного программного кода) на вкладке Build (рис. 9.4).
Рис. 9.4. Разрешение небезопасного программного кода Visual Studio 2005
Ключевое слово unsafeВ следующих примерах предполагается, что вы имеете опыт работы с указателями в C(++). Если это не так, не слишком отчаивайтесь. Подчеркнем еще раз, что создание небезопасного программного кода не является типичной задачей в большинстве приложений .NET. Если вы хотите работать с указателями в C#, то должны специально объявить блок программного кода "небезопасным", используя для этого ключевое слово unsafe (как вы можете догадаться сами, весь программный код, не обозначенный ключевым unsafe, автоматически считается "безопасным").
unsafe {
// Операторы для работы с указателями.
}
Кроме объявления контекста небезопасного программного кода, вы можете строить "небезопасные" структуры, классы, члены типов и параметры. Вот несколько примеров, на которые следует обратить внимание.
// Вся эта структура является 'небезопасной'
// и может использоваться только в небезопасном контексте.
public unsafe struct Node {
public int Value;
public Node* Left;
public Node* Right;
}
// Эта структура является безопасной, но члены Node* – нет.
// Строго говоря, получить доступ к 'Value' извне небезопасного
// контекста можно, а к 'Left' и 'Right' - нет.
public struct Node {
public int Value;
// К этим элементам можно получить доступ только
// в небезопасном контексте!
public unsafe Node* Left;
public unsafe Node* Right;
}
Методы (как статические, так и уровня экземпляра) тоже можно обозначать, как небезопасные. Предположим, например, вы знаете, что некоторый статический метод использует логику указателей. Чтобы гарантировать вызов данного метода только в небезопасном контексте, можно определить метод так, как показано ниже.
unsafe public static void SomeUnsafeCode() {
// Операторы для работы о указателями.
}
В такой конфигурации требуется, чтобы вызывающая сторона обращалась к SomeUnsafeCode() так.
static void Main(string[] args) {
unsafe {
SomeUnsafeCode();
}
}
Если же не обязательно, чтобы вызывающая сторона делала вызов в небезопасном контексте, то можно не указывать ключевое слово unsafe в методе SomeUnsafeCode() и записать следующее:
public static void SomeUnsafeCode() {
unsafe {
// Операторы для работы с указателями.
}
}
что должно упростить вызов:
static void Main(string[] args) {
SomeUnsafeCode();
}
Работа с операциями * и &После создания небезопасного контекста вы можете строить указатели на типы с помощью операции * и получать адреса заданных указателей с помощью операции &. В C# операция * применяется только к соответствующему типу, а не как префикс ко всем именам переменных указателя. Например, в следующем фрагменте программного кода объявляются две переменные типа int* (указатель на целое).
// Нет! В C# это некорректно!
int *pi, *pj;
// Да! Это в C# правильно.
int* pi, pj;
Рассмотрим следующий пример.
unsafe {
int myInt;
// Определения указателя типа int
// и присваивание ему адреса myInt.
int* ptrToMyInt = &myInt;
// Присваивание значения myInt
// с помощью разыменования указателя.
*ptrToMyInt = 123;
// Печать статистики.
Console.WriteLine("Значение myInt {0}", myInt);
Console.WriteLine("Адрес myInt {0:X}", (int)&ptrToMyInt);
}
Небезопасная (и безопасная) функция SwapКонечно, объявление указателей на локальные переменные с помощью просто-то присваивания им значений (как в предыдущем примере) никогда не требуется. Чтобы привести более полезный пример небезопасного программного кода, предположим, что вы хотите создать функцию обмена, используя арифметику указателей.
unsafe public static void UnsafeSwap(int* i, int* j) {
int temp = *i;
*i = *j;
*j = temp;
}
Очень похоже на C, не так ли? Однако с учетом знаний, полученных из главы 3, вы должны знать, что можно записать следующую безопасную версию алгоритма обмена, используя ключевое слово C# ref.
public static void SafeSwap(ref int i, ref int j)
int temp = i;
i = j;
j = temp;
}
Функциональные возможности каждого из этих методов идентичны, и это еще раз подтверждает, что работа напрямую с указателями в C# требуется редко. Ниже показана логика вызова,
static void Main(string[] args) {
Console.WriteLine(*** Вызов метода с небезопасным кодом ***");
// Значения для обмена.
int i = 10, i = 20;
// 'Безопасный' обмен значениями.
Console.WriteLine("n***** Безопасный обмен *****");
Cоnsоle.WriteLine("Значения до обмена: i = {0}, j = {1}", i, j);
SafeSwap(ref 1, ref j);
Console.WriteLine("Значения после обмена: i = {0}, j = {l}", i, j);
// 'Небезопасный' обмен значениями.
Console.WriteLine("n***** Небезопасный обмен *****");
Console.WriteLine("Значения до обмена: i = {0}, j = {1}", i, j);
unsafe { UnsafeSwap(&i, &j); }
Console.WriteLine("Значения после обмена: i = {0}, j = {1}", i, j);
Console.ReadLine();
}
Доступ к полям через указатели (операция -›)Теперь предположим, что у нас определена структура Point и мы хотим объявить указатель на тип Point. Как и в C(++), для вызова методов или получения доступа к полям типа указателя необходимо использовать операцию доступа к полю указателя (-›). Как уже упоминалось в табл. 9.3, это небезопасная версия стандартной (безопасной) операции, обозначаемой точкой (.). Фактически, используя операцию разыменования указателя (*). можно снять косвенность указателя, чтобы (снова) вернуться к применению нотации, обозначаемой точкой. Рассмотрите следующий программный код.
struct Point {
public int x;
public int y;
public override string ToString() { return string.Format ("({0}, {1})", x, y); }
}
static void Main(string[] args) {
// Доступ к членам через указатели.
unsafe {
Point point;
Point* p =&point;
p-›x = 100;
p-›y = 200;
Console.WriteLine(p-›ToString());
}
// Доступ к членам через разыменование указателей.
unsafe {
Point point;
Point* p =&point;
(*p).x = 100;
(*p).y = 200;
Console.WriteLine((*p).ToString());
}
}
Ключевое слово stackallocВ небезопасном контексте может понадобиться объявление локальной переменной, размещаемой непосредственно в памяти стека вызовов (и таким образом не подлежащей "утилизации" при сборке мусора .NET). Чтобы сделать такое объявление, в C# предлагается ключевое слово stackalloc являющееся C#-эквивалентом функции alloca из библиотеки времени выполнения C. Вот простой пример.
unsafe {
char* p = stackalloc char[256];