0Pricing
C# Academy · Урок

Обрезка и ограничения отражения

Обрабатывайте предупреждения об обрезке, используйте [DynamicallyAccessedMembers] и заменяйте отражение во время выполнения альтернативами, сгенерированными из исходного кода.

«Обрезка и ограничения отражения» — бесплатный урок C# Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Что такое удаление IL-кода

Удаление IL-кода — это оптимизация во время публикации, которая удаляет неиспользуемые типы, методы и сборки из выходных данных. В результате исполняемый файл становится меньше. Эта возможность автоматически включается для Native AOT и автономных публикаций одного файла.

Включение удаления кода

Включите удаление кода в файле проекта. Вы можете управлять режимом удаления — от partial (удаляются только неиспользуемые сборки) до full (удаляются неиспользуемые члены внутри сборок).

// .csproj
<PropertyGroup>
  <!-- Enable trimming on publish -->
  <PublishTrimmed>true</PublishTrimmed>

  <!-- full: aggressive — trims unused members within assemblies
       partial: conservative — only removes unused assemblies -->
  <TrimMode>full</TrimMode>

  <!-- Treat all trimmer warnings as errors (recommended for CI) -->
  <TrimmerRootAssemblies>MyApp</TrimmerRootAssemblies>
  <SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>

// Publish:
dotnet publish -c Release -r linux-x64 --self-contained

Как работает инструмент удаления кода

Инструмент удаления кода выполняет анализ достижимости, начиная с точек входа (Main, зарегистрированных служб и атрибутов). Любой код, недостижимый из этих корней, удаляется.

// Example: only MyApp.Program.Main is a root
// Code flow:
//   Main → WebApplication.Run → MapGet → MyHandler
//   Everything else is trimmed

// What gets KEPT:
class MyHandler { public string Handle() => "ok"; }

// What gets TRIMMED (if not reached):
class UnusedService { public void DoWork() { } }

// Problem: reflection can access UnusedService at runtime
// But the trimmer can't know that at compile time
// → UnusedService is trimmed → MissingMethodException at runtime

Отражение и удаление кода

Отражение — основной источник несовместимости с удалением кода. Инструмент удаления кода не может статически проанализировать, к каким типам вы обратитесь через Type.GetType() или Activator.CreateInstance().

// PROBLEMATIC: type name comes from config at runtime
var typeName = config["Plugin:Type"]!;
var type = Type.GetType(typeName);      // type may have been trimmed!
var instance = Activator.CreateInstance(type!); // MissingMethodException

// PROBLEMATIC: LINQ expressions with reflection
var query = dbContext.Set<T>()          // T discovered via reflection
    .Where(BuildExpression<T>("Name", "Alice"));

// PROBLEMATIC: attribute scanning
var handlers = Assembly.GetExecutingAssembly()
    .GetTypes()
    .Where(t => t.HasCustomAttribute<HandlerAttribute>());
// All handler types may be trimmed away

Атрибут DynamicDependency

[DynamicDependency] сообщает инструменту удаления кода, что необходимо сохранить определенные члены, к которым, как вам известно, будет осуществляться доступ через отражение.

// Keep all public constructors of MyPlugin:
[DynamicDependency(
    DynamicallyAccessedMemberTypes.PublicConstructors,
    typeof(MyPlugin))]
public static IPlugin CreatePlugin()
    => (IPlugin)Activator.CreateInstance(typeof(MyPlugin))!;

// Keep a specific method by name:
[DynamicDependency("ProcessOrder", typeof(OrderHandler))]
public static void Bootstrap() { }

// Keep everything on a type (use sparingly):
[DynamicDependency(
    DynamicallyAccessedMemberTypes.All,
    typeof(LegacyReflectionHelper))]
public static void EnsurePreserved() { }

Аннотация DynamicallyAccessedMembers

[DynamicallyAccessedMembers] снабжает параметры, поля и свойства аннотациями, сообщающими инструменту удаления кода, к каким членам Type будет осуществляться доступ через отражение во время выполнения.

// Annotate a method parameter:
public void Register(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
    Type serviceType)
{
    // trimmer knows to preserve public constructors of whatever Type is passed
    var instance = Activator.CreateInstance(serviceType)!;
}

// Annotate a generic parameter:
public static T Create<
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
    T>() where T : new()
    => new T();

// Annotate a field:
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
private Type _modelType = typeof(OrderModel);

RequiresUnreferencedCode

Помечайте методы, принципиально зависящие от отражения, атрибутом [RequiresUnreferencedCode]. Это передает вызывающему коду предупреждения, чтобы было понятно: после удаления кода он может перестать работать.

// Mark a method that uses reflection internally:
[RequiresUnreferencedCode("Uses Type.GetType() — not safe for trimming")]
public static object LoadPlugin(string typeName)
{
    var type = Type.GetType(typeName)!
        ?? throw new InvalidOperationException($"Type {typeName} not found");
    return Activator.CreateInstance(type)!;
}

// Callers will see a trimmer warning:
// IL2026: Members decorated with [RequiresUnreferencedCode]
// need their callers to be decorated with the same attribute
// or be suppressed with [UnconditionalSuppressMessage]

// The warning reminds callers to use a trim-safe alternative
// or add a DynamicDependency for the types they expect

Безопасные при удалении кода альтернативы

Для многих подходов с активным использованием отражения в современном .NET существуют безопасные при удалении кода альтернативы. Предпочитайте их, чтобы избежать проблем с удалением кода.

// INSTEAD OF: Type.GetType(name) + Activator.CreateInstance
// USE: registered factories or switch expressions:
public IHandler CreateHandler(string name) => name switch
{
    "order" => new OrderHandler(),
    "email" => new EmailHandler(),
    _       => throw new ArgumentException($"Unknown: {name}")
};

// INSTEAD OF: reflection-based JSON serialization
// USE: source-generated JsonSerializerContext

// INSTEAD OF: attribute scanning via Assembly.GetTypes()
// USE: compile-time source generators

// INSTEAD OF: dynamic proxy libraries (Castle, DispatchProxy)
// USE: source-generated interceptors (.NET 8+)

XML-дескриптор для удаления кода

Используйте корневой XML-дескриптор, чтобы сохранить целые пространства имен или сборки, которые инструмент удаления кода не может проанализировать, — например, сторонние библиотеки с активным использованием отражения.

<!-- TrimmerRoots.xml -->
<linker>
  <!-- Keep everything in LegacyLib -->
  <assembly fullname="LegacyLib">
    <type fullname="LegacyLib.*" preserve="all" />
  </assembly>

  <!-- Keep a specific type -->
  <assembly fullname="MyApp">
    <type fullname="MyApp.Plugins.PluginLoader" preserve="all" />
  </assembly>
</linker>

// Reference in .csproj:
<ItemGroup>
  <TrimmerRootDescriptor Include="TrimmerRoots.xml" />
</ItemGroup>

Практический пример: устранение предупреждений об удалении кода

Практический процесс устранения предупреждений инструмента удаления кода в реальной кодовой базе.

// Step 1: enable analyzer during development
// .csproj:
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>

// Step 2: build and read warnings:
// warning IL2026: 'MyMapper.MapDynamic(Type)' uses
// [RequiresUnreferencedCode] via 'Type.GetMethod'

// Step 3: fix the root cause:
// BEFORE:
public void MapDynamic(Type t)
{
    var method = t.GetMethod("MapFrom")!; // IL2026
    method.Invoke(null, null);
}

// AFTER: use an interface instead of reflection
public void Map(IMapper mapper) => mapper.MapFrom();

// Step 4: if fix is not feasible, add annotation and document:
[RequiresUnreferencedCode("Uses reflection: ensure target types are preserved")]
public void MapDynamic(Type t) { /* ... */ }

Быстрая проверка

Что сообщает атрибут [DynamicallyAccessedMembers] инструменту удаления IL-кода?

Итоги: ограничения удаления кода и отражения

Основные выводы:

  • Удаление кода убирает недостижимый код во время публикации — отражение нарушает этот анализ
  • Включите с помощью <PublishTrimmed>true</PublishTrimmed>; используйте TrimMode=full для максимального уменьшения размера
  • [DynamicDependency]: явно сохраняет определенные члены для отражения
  • [DynamicallyAccessedMembers]: помечает параметры Type, чтобы инструмент удаления кода знал, что нужно сохранить
  • [RequiresUnreferencedCode]: предупреждает вызывающий код о том, что метод небезопасен при удалении кода
  • Предпочитайте генераторы исходного кода и интерфейсы подходам с активным использованием отражения

Часто задаваемые вопросы

Урок «Обрезка и ограничения отражения» бесплатный?

Да — полный текст урока «Обрезка и ограничения отражения» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Обрезка и ограничения отражения»?

Обрабатывайте предупреждения об обрезке, используйте [DynamicallyAccessedMembers] и заменяйте отражение во время выполнения альтернативами, сгенерированными из исходного кода. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Обрезка и ограничения отражения»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Компиляция Native AOT
  2. Обрезка и ограничения отражения
  3. ReadyToRun и многоуровневая компиляция
  4. Бенчмаркинг с BenchmarkDotNet
← Назад к C# Academy