// и может использоваться только в небезопасном контексте.
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);
}
Конечно, объявление указателей на локальные переменные с помощью просто-то присваивания им значений (как в предыдущем примере) никогда не требуется. Чтобы привести более полезный пример небезопасного программного кода, предположим, что вы хотите создать функцию обмена, используя арифметику указателей.
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);