Не путать с ключевым словом Unsafe в C#. Не путать с библиотекой от Unity "Unity.Collections.LowLevel.Unsafe». System.Runtime.CompilerServices.Unsafe - это часть стандартной библиотеки .NET (как часть пространства имен), которая предоставляет набор типов и методов для выполнения низкоуровневых, "небезопасных" операций в "безопасном" контексте C#. Это означает, что вы можете использовать эти функции без необходимости помечать свой код ключевым словом unsafe. Используется для низкоуровневых операций, такие как работа с указателями, преобразования типов и прямая работа с памятью. Широко используется в сценариях, где хочется достичь высокой производительности. Библиотека System.Runtime.CompilerServices.Unsafe не требует специальных разрешений для компиляции или выполнения, в отличие от кода, помеченного ключевым словом unsafe в C#. Использование библиотеки Unsafe может быть опасным, поскольку оно обходит некоторые механизмы безопасности .NET. <br> ### Как подключить к проекту Если проект - это чистый c#, то ничего подключать не нужно. Библиотека Unsafe работает из коробки. Если это Unity-проект, то в таком случае библиотека Unsafe является частью .NET, но он не является "встроенным" в язык C#. Его нужно отдельно установить и подключить к проекту. System.Runtime.CompilerServices.Unsafe доступна в Nuget по [ссылке ](https://www.nuget.org/packages/System.Runtime.CompilerServices.Unsafe) Так как мы используем наш код и в Unity-проекте (в качестве клиента), и C# проекте (в качестве сервера через DLL), то нам нужно библиотеку добавить вручную в Assets/Plugins (или куда угодна в рамках директории проекта) Как вручную добавить описано в заметке под названием:
 «Nuget» Там есть раздел:
 «Как добавить пакет из Nuget в проект в качестве DLL» Далее, необходимо добавить using. ```csharp using System.Runtime.CompilerServices; ``` Библиотека готова к использованию. <br> ### Библиотека unsafe VS Ключевое слово unsafe В общем случае, всё, что можно сделать с помощью библиотеки System.Runtime.CompilerServices.Unsafe, можно сделать и в unsafe блоке C#, поскольку оба подхода позволяют выполнять низкоуровневые операции с памятью. Библиотека Unsafe предоставляет набор удобных методов, которые облегчают выполнение некоторых из этих операций, но эти операции можно было бы выполнить и напрямую в unsafe блоке с использованием указателей. Однако стоит заметить, что использование библиотеки Unsafe может быть проще, безопаснее и оптимальнее, чем напрямую использование unsafe блоков, поскольку она предоставляет уровень абстракции, который скрывает некоторые из сложностей работы с указателями и низкоуровневой памятью, а также оптимально реализована. Библиотека Unsafe позволяет вам работать с памятью на низком уровне, не требуя от вас знания о тонкостях работы с указателями. Также важно отметить, что некоторые операции могут быть проще или более эффективно реализованы с помощью методов библиотеки Unsafe, чем с помощью обычного unsafe кода. Например, некоторые методы Unsafe могут выполнять операции копирования памяти или сравнения, которые оптимизированы на уровне реализации библиотеки и могут быть более эффективными, чем ручная реализация этих операций в unsafe коде. ##### Библиотека unsafe - Предоставляет методы, которые позволяют выполнять некоторые из тех же операций, что и unsafe код, но без необходимости использовать указатели напрямую. - Поскольку библиотека не требует непосредственного использования указателей, оно может быть немного безопаснее и проще для чтения и поддержки. - Библиотека Unsafe не требует специальных разрешений для компиляции или выполнения. Она может быть использована в любом коде C#, который может ссылаться на соответствующую библиотеку. ##### Ключевое слово unsafe - Необходимо использовать указатели напрямую. - Более опасно, поскольку оно требует непосредственного использования указателей. - Для компиляции и выполнения unsafe кода вам нужно включить поддержку unsafe кода в настройках вашего проекта. Пример с отсутствием необходимости использовать указатели напрямую в библиотеке unsafe: ```csharp using System; using System.Runtime.CompilerServices; public class Example { public static void Main() { int[] array = new int[5] { 10, 20, 30, 40, 50 }; ref int itemRef = ref Unsafe.Add(ref array[0], 3); Console.WriteLine(itemRef); // Выводит "40" } } ``` В этом примере используется метод Unsafe.Add\<T> из библиотеки System.Runtime.CompilerServices.Unsafe для получения ссылки на элемент массива по его индексу. Затем эта ссылка используется для чтения значения этого элемента. Обратите внимание, что здесь не используются указатели, но все равно происходит низкоуровневая работа с памятью. Все операции выполняются через ссылки и "небезопасные" методы из библиотеки Unsafe. Это демонстрирует, как библиотека Unsafe может быть использована для выполнения низкоуровневых операций без непосредственного использования указателей. <br> <br> ### Почему использование Unsafe дает высокую производительность ##### Уменьшение накладных расходов В низкоуровневых операциях нет дополнительных абстракций и проверок безопасности, которые могут присутствовать в более высокоуровневых абстракциях. Это позволяет снизить накладные расходы на выполнение кода, что особенно важно для высокоинтенсивных вычислений. ##### Прямой доступ к памяти Низкоуровневые операции позволяют программисту работать непосредственно с памятью, обращаться к ее адресам и выполнять чтение/запись данных без дополнительных слоев абстракции. Это особенно полезно в случаях, когда требуется быстрый доступ к большим объемам данных, таким как обработка массивов или буферов. ##### Оптимизация алгоритмов В некоторых случаях, оптимизированные алгоритмы могут быть реализованы с помощью низкоуровневых операций, таких как битовые манипуляции или прямой доступ к байтам данных. Это может привести к значительному увеличению производительности по сравнению с реализацией на более высоком уровне абстракции. ##### Обход ограничений безопасности В безопасных средствах языка C# и .NET некоторые операции ограничены с целью обеспечения безопасности. Однако в некоторых случаях, например, при работе с внешними ресурсами или сторонними библиотеками, использование низкоуровневых операций может обойти эти ограничения и предоставить более оптимальные решения. <br> ### Пример риска использования Unsafe В C# типы данных обычно строго проверяются на этапе компиляции для предотвращения ошибок, связанных с несовместимостью типов. Метод `Unsafe.As<TFrom, TTo>` позволяет обойти эту проверку и выполнить приведение типа во время выполнения. Если типы `TFrom` и `TTo` не совместимы, результат выполнения этого метода будет непредсказуем. Представьте, что у вас есть следующий код: ```csharp string str = "Hello, world!"; int strAsInt = Unsafe.As<string, int>(ref str); ``` В этом примере мы пытаемся преобразовать строку в число с помощью `Unsafe.As<string, int>`. Но строка и число - это абсолютно разные типы данных, и память, которую они занимают, интерпретируется по-разному. При попытке выполнить этот код, вы скорее всего получите неожиданное и бессмысленное значение в `strAsInt`, или даже исключение. Это может привести к ошибкам, которые очень сложно обнаружить и отлаживать, поскольку они могут не вызывать исключений во время выполнения и просто приводить к неправильному поведению программы. Поэтому важно быть очень осторожным при использовании `Unsafe.As<TFrom, TTo>` и других небезопасных операций. <br> ### Несколько часто используемых методов ##### Add\<T>(ref T source, int elementOffset) Позволяет добавить смещение в байтах к адресу заданной ссылки. Полезно для арифметики указателей. ##### As\<TFrom, TTo>(ref TFrom source) Приводит ссылку одного типа к другому. Это полезно при работе с "сырой" памятью. ##### SizeOf\<T>() Возвращает размер указанного типа в байтах. Полезно при работе с выделением памяти. ##### Read\<T>(void* source) Считывает значение типа T из указанного адреса памяти. ##### Write\<T>(void* destination, T value) Записывает значение типа T по указанному адресу памяти. ##### CopyBlock(void\* destination, void\* source, uint byteCount) Копирует блок памяти из одного места в другое. ##### InitBlock(void\* startAddress, byte value, uint byteCount) Инициализирует блок памяти указанным значением. ##### IsAddressGreaterThan\<T>(ref T left, ref T right) ##### IsAddressLessThan\<T>(ref T left, ref T right) Сравнивают адреса двух ссылок. <br> ### Примеры некоторых ключевых возможностей: ##### Преобразование типов `Unsafe` позволяет выполнять небезопасные преобразования между различными типами, что может быть полезно при работе с "сырой" памятью или бинарными данными. Пример использования небезопасного преобразования типов: ```csharp public class MyGeneric<T> { private T value; public int ConvertToIntUnsafe() { return Unsafe.As<T, int>(ref value); } } ``` ##### Работа с указателями `System.Runtime.CompilerServices.Unsafe` позволяет получить доступ к указателям и выполнять арифметику указателей, что может быть полезно для оптимизации некоторых алгоритмов и операций над массивами или структурами данных. Пример использования арифметики указателей: ```csharp int[] numbers = { 10, 20, 30, 40, 50 }; // Получаем указатель на первый элемент массива IntPtr ptr = Unsafe.AsPointer(ref numbers[0]); // Выполняем арифметику указателей: добавляем смещение на 1 элемент (4 байта для типа int) IntPtr secondElementPtr = new IntPtr(ptr.ToInt64() + sizeof(int)); // Получаем значение второго элемента массива через арифметический указатель int secondElementValue = Unsafe.Read<int>(secondElementPtr); Console.WriteLine(secondElementValue); // Выведет: 20 ``` Тот же пример, но если не использовать библиотеку Unsafe ```csharp unsafe { int[] numbers = { 10, 20, 30, 40, 50 }; // Получаем указатель на первый элемент массива int* ptr = &numbers[0]; // Выполняем арифметику указателей: добавляем смещение на 1 элемент (4 байта для типа int) int* secondElementPtr = ptr + 1; // Получаем значение второго элемента массива через арифметический указатель int secondElementValue = *secondElementPtr; Console.WriteLine(secondElementValue); // Выведет: 20 } ``` <br> ##### Работа с памятью `Unsafe` позволяет выполнять чтение и запись данных напрямую в память, что может быть полезно при сериализации данных или при работе с внешними API, которые работают с "сырыми" данными. Пример записи и чтения данных в память: ```csharp int value = 42; // Получаем указатель на переменную value IntPtr ptr = Unsafe.AsPointer(ref value); // Запись значения 100 в адрес памяти, на который указывает ptr Unsafe.Write(ptr, 100); // Чтение значения из указанного адреса памяти int newValue = Unsafe.Read<int>(ptr); // Выведет: 100 Console.WriteLine(newValue); ``` <br> Тот же пример, но если не использовать библиотеку Unsafe ```csharp int value = 42; unsafe { int* ptr = &value; *ptr = 100; // Запись значения 100 в адрес памяти, на который указывает ptr int newValue = *ptr; // Чтение значения из указанного адреса памяти Console.WriteLine(newValue); // Выведет: 100 } ```