Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
При включении обрезки в приложении пакет SDK для .NET выполняет статический анализ для обнаружения шаблонов кода, которые могут быть несовместимы с обрезкой. Предупреждения о тримминге указывают на потенциальные проблемы, которые могут привести к изменению поведения или сбою после тримминга.
Приложение, использующее обрезку, не должно выдавать предупреждения об обрезке. Если есть предупреждения об обрезке кода, тщательно протестируйте приложение после уменьшения кода, чтобы убедиться, что нет изменений в поведении.
В этой статье приведены практические рабочие процессы для устранения предупреждений об обрезке. Для более глубокого понимания того, почему возникают эти предупреждения и как работает обрезка, см. статью «Общие сведения о анализе обрезки».
Общие сведения о категориях предупреждений
Предупреждения обрезки делятся на две основные категории:
Код несовместим с обрезкой — помечен с помощью RequiresUnreferencedCodeAttribute. Код по существу нельзя сделать поддающимся анализу (например, с помощью динамической загрузки сборки или сложных схем рефлексии). Метод помечается как несовместимый, и вызывающие получают предупреждения.
Код с требованиями — аннотированный с DynamicallyAccessedMembersAttribute. Используется отражение, но типы известны во время компиляции. Когда требования выполняются, код становится полностью совместимым с усечением.
Рабочий процесс: определение правильного подхода
При обнаружении предупреждения об обрезке, выполните следующие шаги.
- Устранение отражения — это всегда лучший вариант, если это возможно.
- Используйте DynamicallyAccessedMembers — если типы известны, сделайте код совместимым с обрезкой.
- Используйте RequiresUnreferencedCode — если код действительно динамический, необходимо задокументировать несовместимость.
- Подавляйте предупреждения в качестве последнего средства - только если вы убеждены, что код безопасен.
Подход 1. Устранение отражения
Лучшее решение заключается в том, чтобы избежать отражения полностью, когда это возможно. Это делает ваш код быстрым и полностью совместимым с функцией обрезки.
Использование универсальных шаблонов во время компиляции
Замените операции типа среды выполнения универсальными параметрами во время компиляции:
// ❌ Before: Uses reflection
void CreateAndProcess(Type type)
{
var instance = Activator.CreateInstance(type);
// Process instance...
}
// ✅ After: Uses generics
void CreateAndProcess<T>() where T : new()
{
var instance = new T();
// Process instance...
}
Использование генераторов источников
Современный .NET предоставляет генераторы источников для распространенных сценариев отражения:
- Сериализация: используйте генерацию исходного кода System.Text.Json вместо сериализаторов, основанных на отражении
- Конфигурация. Использование генератора источника привязки конфигурации
Для получения дополнительной информации см. Известные проблемы совместимости с обрезкой.
Подход 2: Создание совместимости кода с DynamicallyAccessedMembers
Если отражение необходимо, но типы известны во время компиляции, используйте DynamicallyAccessedMembersAttribute для совместимости кода с обрезкой кода.
Пошаговая инструкция: Создание аннотаций использования отражения
Рассмотрим этот пример, который создает предупреждение:
void PrintMethodNames(Type type)
{
// ⚠️ IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods'
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
Шаг 1. Определение операции отражения
Код вызывает GetMethods(), который требует сохранения PublicMethods.
Шаг 2. Аннотировать параметр
Добавьте DynamicallyAccessedMembers , чтобы сообщить триммеру, что необходимо:
void PrintMethodNames(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
// ✅ No warning - trimmer knows to preserve public methods
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
Шаг 3. Убедитесь, что вызывающие удовлетворяют требованиям
При вызове этого метода с известным типом (typeof) требование выполняется автоматически:
// ✅ OK - DateTime's public methods will be preserved
PrintMethodNames(typeof(DateTime));
Пошаговые действия. Распространение требований через цепочки вызовов
Когда типы данных проходят через несколько методов, необходимо передать требования.
void Method1()
{
Method2<DateTime>(); // ⚠️ Warning: Generic parameter needs annotation
}
void Method2<T>()
{
Type t = typeof(T);
Method3(t); // ⚠️ Warning: Argument doesn't satisfy requirements
}
void Method3(Type type)
{
var methods = type.GetMethods(); // ⚠️ Warning: Reflection usage
}
Шаг 1. Начало использования отражения
Аннотировать, где на самом деле используется рефлексия:
void Method3(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
var methods = type.GetMethods(); // ✅ Fixed
}
Шаг 2. Распространение цепочки вызовов
Проработайте цепочку вызовов в обратном порядке.
void Method2<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
Type t = typeof(T);
Method3(t); // ✅ Fixed - T is annotated
}
Шаг 3. Проверка на сайте вызова
void Method1()
{
Method2<DateTime>(); // ✅ Fixed - DateTime's public methods preserved
}
Дополнительные сведения о том, как требования передаются через код, см. в разделе "Общие сведения о анализе обрезки".
Общие значения DynamicallyAccessedMemberTypes
Выберите минимальный уровень доступа:
| Тип участника | Когда следует использовать |
|---|---|
PublicConstructors |
Использование Activator.CreateInstance() или GetConstructor() |
PublicMethods |
Использование GetMethod() или GetMethods() |
PublicFields |
Использование GetField() или GetFields() |
PublicProperties |
Использование GetProperty() или GetProperties() (сериализация) |
PublicEvents |
Использование GetEvent() или GetEvents() |
Предупреждение
Использование DynamicallyAccessedMemberTypes.All сохраняет все члены целевого типа и всех членов вложенных типов (но не транзитивные зависимости, такие как члены для возвращаемого типа свойства). Это значительно увеличивает размер приложения. Более важно, сохраненные элементы становятся доступны, что означает, что они могут содержать свой собственный проблемный код. Например, если удерживаемый член вызывает метод, помеченный символом RequiresUnreferencedCode, это предупреждение не может быть разрешено, так как член удерживается с помощью аннотации отражения, а не явного вызова. Используйте минимальные необходимые типы элементов, чтобы избежать этих каскадных проблем.
Подход 3. Пометить код как несовместимый с RequiresUnreferencedCode
Если код принципиально не поддается анализу, используйте RequiresUnreferencedCodeAttribute для документирования несовместимости.
Когда следует использовать RequiresUnreferencedCode
Используйте этот атрибут, когда:
- Типы загружаются динамически: использование GetType() строк, определяемых средой выполнения.
- Сборки загружаются в процессе выполнения: использование LoadFrom(String).
- Сложные шаблоны отражения: слишком сложное использование отражения для аннотации.
-
Генерация кода во время выполнения: использование System.Reflection.Emit или ключевое слово
dynamic.
Пошаговое руководство: Отметьте несовместимые методы
Шаг 1. Определение действительно несовместимого кода
Пример кода, который не может быть совместим с trim:
void LoadPluginByName(string pluginName)
{
// Type name comes from runtime input - trimmer cannot know what types are needed
Type pluginType = Type.GetType(pluginName);
var plugin = Activator.CreateInstance(pluginType);
// Use plugin...
}
Шаг 2. Добавление атрибута RequiresUnreferencedCode
[RequiresUnreferencedCode("Plugin loading by name is not compatible with trimming. Consider using compile-time plugin registration instead.")]
void LoadPluginByName(string pluginName)
{
Type pluginType = Type.GetType(pluginName);
var plugin = Activator.CreateInstance(pluginType);
// ✅ No warnings inside this method - it's marked as incompatible
}
Шаг 3. Вызывающие абоненты получают предупреждения
void InitializePlugins()
{
// ⚠️ IL2026: Using member 'LoadPluginByName' which has 'RequiresUnreferencedCodeAttribute'
// can break functionality when trimming application code. Plugin loading by name is not
// compatible with trimming. Consider using compile-time plugin registration instead.
LoadPluginByName("MyPlugin");
}
Написание эффективных предупреждений
Хорошее RequiresUnreferencedCode сообщение должно:
- Укажите, какая функциональность несовместима. Будьте конкретны в том, что не работает с обрезкой.
- Предлагать альтернативы: направлять разработчиков к решениям, совместимым с Trim.
- Будьте краткими: сделайте сообщения короткими и доступными.
// ❌ Not helpful
[RequiresUnreferencedCode("Uses reflection")]
// ✅ Helpful - explains problem and suggests alternative
[RequiresUnreferencedCode("Dynamic type loading is not compatible with trimming. Use generic type parameters or source generators instead.")]
Для более подробного руководства добавьте параметр Url.
[RequiresUnreferencedCode(
"Plugin system is not compatible with trimming. See documentation for alternatives.",
Url = "https://docs.example.com/plugin-trimming")]
Распространение RequiresUnreferencedCode
Когда метод вызывает другой метод, помеченный RequiresUnreferencedCode, обычно необходимо распространить атрибут.
class PluginSystem
{
// Use a constant for consistent messaging
const string PluginMessage = "Plugin system is not compatible with trimming. Use compile-time registration instead.";
[RequiresUnreferencedCode(PluginMessage)]
private void LoadPluginImplementation(string name)
{
// Low-level plugin loading
}
[RequiresUnreferencedCode(PluginMessage)]
public void LoadPlugin(string name)
{
LoadPluginImplementation(name); // ✅ No warning - method is also marked
}
}
Общие шаблоны и решения
Шаблон: фабричные методы с помощью Activator.CreateInstance
// ❌ Before: Produces warning
object CreateInstance(Type type)
{
return Activator.CreateInstance(type);
}
// ✅ After: Trim-compatible
object CreateInstance(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type)
{
return Activator.CreateInstance(type);
}
Шаблон: загрузка сборок систем для плагинов
// This pattern is fundamentally incompatible with trimming
[RequiresUnreferencedCode("Plugin loading is not compatible with trimming. Consider compile-time plugin registration using source generators.")]
void LoadPluginsFromDirectory(string directory)
{
foreach (var dll in Directory.GetFiles(directory, "*.dll"))
{
Assembly.LoadFrom(dll);
}
}
Шаблон. Контейнеры внедрения зависимостей
// Complex DI containers are often incompatible
class Container
{
[RequiresUnreferencedCode("Service resolution uses complex reflection. Consider using source-generated DI or registering services explicitly.")]
public object Resolve(Type serviceType)
{
// Complex reflection to resolve dependencies
}
}
Подход 4. Подавление предупреждений в качестве последнего метода
Предупреждение
Игнорируйте предупреждения обрезки только в том случае, если вы абсолютно уверены, что код безопасен. Неправильное подавление может привести к ошибкам во время выполнения после удаления компонентов.
Используйте UnconditionalSuppressMessageAttribute, когда вы проверили, что код безопасен для обрезки, но триммер не может доказать это с помощью статического анализа.
При необходимости подавления
Подавлять предупреждения только в том случае, если:
- Вы вручную убедились, что весь необходимый код сохраняется (через
DynamicDependencyили другие механизмы). - Путь к коду никогда не выполняется в сценариях с обрезкой.
- Вы тщательно протестировали оптимизированное приложение.
Отключение предупреждений
[RequiresUnreferencedCode("Uses reflection")]
void MethodWithReflection() { /* ... */ }
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
Justification = "All referenced types are manually preserved via DynamicDependency attributes")]
void CallerMethod()
{
MethodWithReflection(); // Warning suppressed
}
Внимание
Не используйте SuppressMessage или #pragma warning disable для предупреждений об обрезке. Они работают только для компилятора, но не сохраняются в скомпилированной сборке. Триммер работает на скомпилированных сборках и не увидит эти подавления. Всегда используйте UnconditionalSuppressMessage.
Свести к минимуму область подавления
Применяйте подавление к наиболее ограниченной области. Извлеките проблемный вызов в локальную функцию
void ProcessData()
{
InitializeData();
CallReflectionMethod(); // Only this call is suppressed
ProcessResults();
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
Justification = "Types are preserved via DynamicDependency on ProcessData method")]
void CallReflectionMethod()
{
MethodWithReflection();
}
}
Такой подход:
- Дает понять, какой конкретный вызов подавляется.
- Предотвращает случайное подавление других предупреждений при изменении кода.
- Держит обоснование близко к подавленной вызову.
Советы по устранению неполадок
Предупреждение сохраняется после добавления DynamicallyAccessedMembers
Убедитесь, что вы аннотировали всю цепочку вызовов от использования рефлексии вплоть до источника Type.
- Найдите, где используется отражение (например
GetMethods()). - Заметите параметр этого метода.
- Следуйте значению
Typeобратно через все вызовы метода. - Заметите каждый параметр, поле или параметр универсального типа в цепочке.
Слишком много предупреждений, чтобы с ними справиться.
- Начните с собственного кода— сначала исправьте предупреждения в коде, который вы управляете.
- Используйте
TrimmerSingleWarn, чтобы увидеть отдельные предупреждения из пакетов. - Рассмотрите, подходит ли обрезка для вашего применения.
- Проверьте известные несовместимости обрезки для проблем на уровне фреймворка.
Не уверен, какой DynamicallyAccessedMemberTypes использовать
Посмотрите на используемый API отражения:
-
GetMethod()/GetMethods()→PublicMethods -
GetProperty()/GetProperties()→PublicProperties -
GetField()/GetFields()→PublicFields -
GetConstructor()/Activator.CreateInstance()PublicParameterlessConstructor→ илиPublicConstructors -
GetEvent()/GetEvents()→PublicEvents
Используйте самый узкий тип, чтобы свести к минимуму размер приложения.
Дальнейшие шаги
- Общие сведения об анализе обрезки. Основные понятия, лежащие в основе предупреждений обрезки
- Подготовка библиотек к обрезке . Сделайте библиотеки совместимыми с обрезкой
- Справочник по предупреждению об обрезке - Подробная информация о конкретных кодах предупреждений
- Известные несовместимости — шаблоны, которые не могут быть совместимы с функции обрезки