0Pricing
C# Academy · Lekcja

Ograniczenia trimowania i refleksji

Obsługuj ostrzeżenia trimowania, używaj [DynamicallyAccessedMembers] i zastępuj refleksję w czasie działania alternatywami generowanymi na podstawie kodu źródłowego.

Ograniczenia trimowania i refleksji to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.

Czym jest przycinanie kodu IL

Przycinanie kodu IL to optymalizacja wykonywana podczas publikowania, która usuwa z wyniku nieużywane typy, metody i zestawy. Rezultatem jest mniejszy plik wykonywalny. Przycinanie jest automatycznie włączane dla Native AOT oraz samodzielnych publikacji do pojedynczego pliku.

Włączanie przycinania

Przycinanie należy włączyć w pliku projektu. Tryb przycinania można zmieniać od partial (przycinanie tylko nieużywanych zestawów) do full (przycinanie nieużywanych elementów wewnątrz zestawów).

// .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

Jak działa mechanizm przycinania

Mechanizm przycinania przeprowadza analizę osiągalności, rozpoczynając od punktów wejścia (Main, zarejestrowane usługi i atrybuty). Każdy fragment kodu, do którego nie można dotrzeć z tych elementów głównych, zostaje usunięty.

// 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

Refleksja a przycinanie

Refleksja jest głównym źródłem niezgodności z przycinaniem. Mechanizm przycinania nie może przeanalizować statycznie typów, do których uzyskają Państwo dostęp za pomocą Type.GetType() lub 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

Atrybut DynamicDependency

[DynamicDependency] informuje mechanizm przycinania, że należy zachować określone elementy, do których wiadomo, że będzie uzyskiwany dostęp za pomocą refleksji.

// 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() { }

Adnotacja DynamicallyAccessedMembers

[DynamicallyAccessedMembers] adnotuje parametry, pola i właściwości, informując mechanizm przycinania, do których elementów obiektu Type będzie uzyskiwany dostęp za pomocą refleksji w czasie działania.

// 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

Metody, które w istotny sposób polegają na refleksji, należy oznaczać za pomocą [RequiresUnreferencedCode]. Dzięki temu wywołujący otrzymają ostrzeżenia informujące, że kod może przestać działać po przycięciu.

// 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

Alternatywy bezpieczne przy przycinaniu

Wiele wzorców intensywnie korzystających z refleksji ma we współczesnym .NET alternatywy bezpieczne przy przycinaniu. Aby uniknąć problemów z przycinaniem, należy wybierać właśnie te alternatywy.

// 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+)

Deskryptor XML przycinania

Należy używać głównego deskryptora XML, aby zachować całe przestrzenie nazw lub zestawy, których mechanizm przycinania nie może przeanalizować — na przykład biblioteki innych firm intensywnie korzystające z refleksji.

<!-- 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>

Praktyczny przykład: naprawianie ostrzeżeń dotyczących przycinania

Praktyczny sposób rozwiązywania ostrzeżeń mechanizmu przycinania w rzeczywistej bazie kodu.

// 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) { /* ... */ }

Szybkie sprawdzenie

Co atrybut [DynamicallyAccessedMembers] przekazuje mechanizmowi przycinania kodu IL?

Podsumowanie: ograniczenia przycinania i refleksji

Najważniejsze informacje:

  • Przycinanie usuwa nieosiągalny kod podczas publikowania — refleksja zakłóca tę analizę
  • Należy włączyć za pomocą <PublishTrimmed>true</PublishTrimmed>; aby uzyskać maksymalne oszczędności, należy użyć TrimMode=full
  • [DynamicDependency]: jawnie zachowuje określone elementy na potrzeby refleksji
  • [DynamicallyAccessedMembers]: adnotuje parametry Type, aby mechanizm przycinania wiedział, co zachować
  • [RequiresUnreferencedCode]: ostrzega wywołujących, że metoda nie jest bezpieczna przy przycinaniu
  • Należy wybierać generatory kodu źródłowego i interfejsy zamiast wzorców intensywnie korzystających z refleksji

Często zadawane pytania

Czy lekcja „Ograniczenia trimowania i refleksji” jest bezpłatna?

Tak — pełny tekst „Ograniczenia trimowania i refleksji” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Ograniczenia trimowania i refleksji”?

Obsługuj ostrzeżenia trimowania, używaj [DynamicallyAccessedMembers] i zastępuj refleksję w czasie działania alternatywami generowanymi na podstawie kodu źródłowego. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć C# Academy?

Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Ograniczenia trimowania i refleksji”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?

Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Kompilacja Native AOT
  2. Ograniczenia trimowania i refleksji
  3. ReadyToRun i kompilacja warstwowa
  4. Benchmarki z BenchmarkDotNet
← Powrót do C# Academy