Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

class PointRef // <= Renamed and retyped.

{

public int x;

public int y;

public override string ToString => $"({x}, {y})";

}

Как вам известно, если в вызывающем коде объявляется переменная типа

Point
, то память для нее выделяется в куче, поддерживающей сборку мусора. И тут возникает животрепещущий вопрос: а что если небезопасный контекст пожелает взаимодействовать с этим объектом (или любым другим объектом из кучи)? Учитывая, что сборка мусора может произойти в любое время, вы
только вообразите, какие проблемы возникнут при обращении к членам
Point
именно в тот момент, когда происходит реорганизация кучи! Теоретически может случиться так, что небезопасный контекст попытается взаимодействовать с членом, который больше не доступен или был перемещен в другое место кучи после ее очистки с учетом поколений (что является очевидной проблемой).

Для фиксации переменной ссылочного типа в памяти из небезопасного контекста язык C# предлагает ключевое слово

fixed
. Оператор
fixed
устанавливает указатель на управляемый тип и "закрепляет" такую переменную на время выполнения кода. Без
fixed
от указателей на управляемые переменные было бы мало толку, поскольку сборщик мусора может перемещать переменные в памяти непредсказуемым образом. (На самом деле компилятор C# даже не позволит установить указатель на управляемую переменную, если оператор
fixed
отсутствует.)

Таким образом, если вы создали объект

Point
и хотите взаимодействовать с его членами, тогда должны написать следующий код (либо иначе получить ошибку на этапе компиляции):

unsafe static void UseAndPinPoint

{

PointRef pt = new PointRef

{

x = 5,

y = 6

};

// Закрепить указатель pt на месте, чтобы он не мог

// быть перемещен или уничтожен сборщиком мусора.

fixed (int* p = &pt.x)

{

// Использовать здесь переменную int*!

}

// Указатель pt теперь не закреплен и готов

// к сборке мусора после завершения метода.

Console.WriteLine ("Point is: {0}", pt);

}

Выражаясь кратко, ключевое слово

fixed
позволяет строить оператор, который фиксирует ссылочную переменную в памяти, чтобы ее адрес оставался постоянным на протяжении работы оператора (или блока операторов). Каждый раз, когда вы взаимодействуете со ссылочным типом из контекста небезопасного кода, закрепление ссылки обязательно.

Ключевое слово sizeof

Последнее ключевое слово С#, связанное с небезопасным кодом —

sizeof
. Как и в C++, ключевое слово
sizeof
в C# используется для получения размера в байтах встроенного типа данных, но не специального типа, разве только в небезопасном контексте. Например, показанный ниже метод не нуждается в объявлении "небезопасным", т.к. все аргументы ключевого слова
sizeof
относятся к встроенным типам:

static void UseSizeOfOperator

{

Console.WriteLine("The size of short is {0}.", sizeof(short));

Console.WriteLine("The size of int is {0}.", sizeof(int));

Console.WriteLine("The size of long is {0}.", sizeof(long));

}

Тем

не менее, если вы хотите получить размер специальной структуры
Point
, то метод
UseSizeOfOperator
придется модифицировать (обратите внимание на добавление ключевого слова
unsafe
):

unsafe static void UseSizeOfOperator

{

...

unsafe {

Console.WriteLine("The size of Point is {0}.", sizeof(Point));

}

}

Итак, обзор нескольких более сложных средств языка программирования C# завершен. Напоследок снова необходимо отметить, что в большинстве проектов .NET эти средства могут вообще не понадобиться (особенно указатели). Тем не менее, как будет показано в последующих главах, некоторые средства действительно полезны (и даже обязательны) при работе с API-интерфейсами LINQ, в частности расширяющие методы и анонимные типы.

Резюме

Целью главы было углубление знаний языка программирования С#. Первым делом мы исследовали разнообразные более сложные конструкции в типах (индексаторные методы, перегруженные операции и специальные процедуры преобразования).

Затем мы рассмотрели роль расширяющих методов и анонимных типов. Как вы увидите в главе 13, эти средства удобны при работе с API-интерфейсами LINQ (хотя при желании их можно применять в коде повсеместно). Вспомните, что анонимные методы позволяют быстро моделировать "форму" типа, в то время как расширяющие методы дают возможность добавлять новую функциональность к типам без необходимости в определении подклассов.

Финальная часть главы была посвящена небольшому набору менее известных ключевых слов (

sizeof
,
unsafe
и т.п.); наряду с ними рассматривалась работа с низкоуровневыми типами указателей. Как было установлено в процессе исследования типов указателей, в большинстве приложений C# их никогда не придется использовать.

Глава 12

Делегаты, события и лямбда-выражения

Вплоть до настоящего момента в большинстве разработанных приложений к операторам верхнего уровня внутри файла

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

В рамках платформы .NET Core предпочтительным средством определения и реагирования на обратные вызовы в приложении является тип делегата. По существу тип делегата .NET Core — это безопасный в отношении типов объект, "указывающий" на метод или список методов, которые могут быть вызваны позднее. Тем не менее, в отличие от традиционного указателя на функцию C++ делегаты представляют собой классы, которые обладают встроенной поддержкой группового и асинхронного вызова методов.

На заметку! В предшествующих версиях .NET делегаты обеспечивали вызов асинхронных методов с помощью

BeginInvoke/EndInvoke
. Хотя компилятор по-прежнему генерирует методы
BeginInvoke/EndInvoke
, в .NET Core они не поддерживаются. Причина в том, что шаблон с
IAsyncResult
и
BeginInvoke/EndInvoke
, используемый делегатами, был заменен асинхронным шаблоном на основе задач. Асинхронное выполнение подробно обсуждается в главе 15.

Поделиться с друзьями: