Команда .NET занялась серьёзным повышением безопасности памяти в C#. Ключевое слово unsafe полностью перерабатывают. В C# 16 (релиз в .NET 12, предпросмотр в .NET 11) оно перестанет быть просто маркером для работы с указателями. Теперь это контракт: unsafe на члене класса означает, что у вызывающего кода появляются обязательства, которые компилятор проверить не может — их должен осознать разработчик.
Раньше unsafe в C# 1.0 просто включал режим работы с указателями. Метод можно было вызвать без всяких оговорок. Новая модель заимствует подход Rust: внешний unsafe на методе — это сигнал для вызывающей стороны, а внутренний блок unsafe { } оборачивает конкретные опасные операции (например, разыменование), и их наличие внутри метода без блока — ошибка компиляции.
Главное изменение: указатели в сигнатуре (byte*) больше не делают вызов небезопасным автоматически. Небезопасно только разыменование. Типы вроде IntPtr теперь считаются такими же, как byte* — их можно передавать, но для чтения данных по адресу нужен блок unsafe. Это закрывает лазейку, через которую Marshal.ReadByte с IntPtr раньше работал из безопасного кода. В C# 16 у него появится обязательный контракт (/// <safety>), а вызов потребует unsafe-блока.
Нововведение работает на двух свойствах проекта. Первое — новый флаг, включающий саму модель (проверку контрактов). Второе — существующий <AllowUnsafeBlocks>. По умолчанию он выключен, и теперь под запрет попадает гораздо больше операций. Если вы не включите AllowUnsafeBlocks, компилятор просто не даст написать ни строчки потенциально опасного кода — эффективнее любой ревью.
Для миграции обещают dotnet format fixer: он механически расставит unsafe { } вокруг вызовов и перенесёт модификаторы с типов на методы. Но писать документацию к контрактам придётся разработчику. В итоге unsafe превращается из набора синтаксических трюков в формальную, заметную и проверяемую компилятором дисциплину, что особенно важно на фоне роста AI-сгенерированного кода.