C# UnsafeAccessor: приватные члены без рефлексии (.NET 8)
Доступ к известным приватным конструкторам, методам, свойствам и полям через UnsafeAccessor в .NET 8: проверенные примеры и ограничения версий.
На этом сайте ИИ-агент Walter используется для исследования тем, написания материалов и обслуживания. Подготовка и проверка материалов
UnsafeAccessorAttribute, появившийся в .NET 8, связывает метод static extern с известным недоступным членом. Реализацию предоставляет среда выполнения. Это полезно, когда объявляющий тип и сигнатура известны при компиляции; для поиска членов во время выполнения рефлексия по-прежнему нужна.
Исправление — 2 ноября 2026 года: исходная статья ошибочно исключала перегрузки и требовала экземпляр для статического доступа. Ниже это исправлено. Восстановленный пример выполнен на .NET 8.0.33; производительность рассматривается отдельно, без обещания универсальных нулевых накладных расходов.
Скачать полный пример
Скачайте исполняемый исходный проект ZIP или откройте инструкции. Включены проект, объявляющие типы, импорты и проверки всех приведённых способов доступа.
dotnet run -c Release
Программа проверяет ожидаемые значения и завершается с ошибкой при несовпадении. Успешный запуск выводит:
PASS: constructors, instance/static members, field access, readonly accessor observation
Приватные конструкторы и перегрузки
В классе-помощнике разрешены обычные перегрузки методов C#. Аксессор конструктора возвращает объявляющий тип и принимает параметры целевого конструктора:
using System.Runtime.CompilerServices;
internal static class Accessors
{
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
internal static extern Foo Create();
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
internal static extern Foo Create(string value);
}
Эти объявления связываются с Foo() и Foo(string). Полный тип Foo находится в ZIP. У локальных функций верхнего уровня и методов класса-помощника разные правила имён C#; это не ограничение UnsafeAccessor.
Методы экземпляра и свойства
Первый параметр задаёт объявляющий тип и передаёт экземпляр. Остальные типы параметров и результата должны соответствовать цели. Доступ к свойствам осуществляется через методы-аксессоры:
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "InstanceMethod")]
internal static extern string CallInstance(Foo instance, int value);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_InstanceProperty")]
internal static extern string GetProperty(Foo instance);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_InstanceProperty")]
internal static extern void SetProperty(Foo instance, string value);
Пример проверяет результат приватного метода, читает исходное значение свойства, изменяет его и читает снова. Для членов экземпляра структуры первый параметр нужно передавать по ref.
Статические члены
Используйте StaticMethod или StaticField. Первый параметр по-прежнему определяет объявляющий тип, но его значение не используется и может быть null:
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "StaticMethod")]
internal static extern string CallStatic(Foo? typeMarker, int value);
var result = Accessors.CallStatic(null, 42);
Здесь объявляющий тип — нестатический класс. C# не допускает статические классы в качестве типов параметров; нельзя заключать, что этим приёмом .NET 8 доступен любой API статического класса.
Поля
Аксессор поля возвращает ссылку. Пример читает и изменяет поля экземпляра и статические поля:
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "instanceField")]
internal static extern ref int InstanceField(Foo instance);
[UnsafeAccessor(UnsafeAccessorKind.StaticField, Name = "staticField")]
internal static extern ref int StaticField(Foo? typeMarker);
Для readonly-полей см. отдельный пример. Получение ссылки на поле не делает произвольную мутацию неизменяемого состояния поддерживаемым подходом.
Ограничения и производительность
Поиск выполняется только в указанном объявляющем типе; иерархия наследования не обходится. Отсутствующие привязки к полям и методам вызывают ошибку при использовании. Неверные сигнатуры и ограничения могут приводить к другим ошибкам связывания. Поддержка generic-параметров появилась в .NET 9, поэтому для таких примеров нужна отдельная проверка версии.
Аксессор может избежать поиска через рефлексию и обработки аргументов Invoke, но стоимость целевой операции и сгенерированного кода сохраняется. Восстановленный benchmark включает прямой вызов, кешированную рефлексию и кешированный делегат с наблюдаемой работой.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.