Delen via


Inzicht in trimanalyse

In dit artikel worden de basisconcepten voor trimanalyse uitgelegd, zodat u begrijpt waarom bepaalde codepatronen waarschuwingen produceren en hoe u uw code-trim compatibel kunt maken. Als u deze concepten begrijpt, kunt u weloverwogen beslissingen nemen bij het aanpakken van knipwaarschuwingen in plaats van gewoon kenmerken te verspreiden om de tooling stil te leggen.

Hoe de trimmer code analyseert

De trimmer voert tijdens het publiceren statische analyse uit om te bepalen welke code door uw toepassing wordt gebruikt. Het begint vanaf bekende toegangspunten (zoals uw Main methode) en volgt de codepaden via uw toepassing.

Wat de trimmer kan begrijpen

De trimmer excelleert bij het analyseren van directe, tijdens compileertijd zichtbare codepatronen.

// The trimmer CAN understand these patterns:
var date = new DateTime();
date.AddDays(1);  // Direct method call - trimmer knows AddDays is used

var list = new List<string>();
list.Add("hello");  // Generic method call - trimmer knows List<string>.Add is used

string result = MyUtility.Process("data");  // Direct static method call

In deze voorbeelden kan de trimmer het codepad volgen en DateTime.AddDays, List<string>.Add en MyUtility.Process markeren als gebruikte code die in de uiteindelijke toepassing moet worden bewaard.

Wat de trimmer niet begrijpt

De trimmer worstelt met dynamische bewerkingen waarbij het doel van een bewerking pas bekend is tijdens de uitvoeringstijd.

// The trimmer CANNOT fully understand these patterns:
Type type = Type.GetType(Console.ReadLine());  // Type name from user input
type.GetMethod("SomeMethod");  // Which method? On which type?

object obj = GetSomeObject();
obj.GetType().GetProperties();  // What type will obj be at runtime?

Assembly asm = Assembly.LoadFrom(pluginPath);  // What's in this assembly?

In deze voorbeelden kan de trimmer het volgende niet weten:

  • Welk type de gebruiker invoert
  • Welk type retourneert GetSomeObject()?
  • Welke code bevindt zich in de dynamisch geladen assembly-module

Dit is het fundamentele probleem dat door waarschuwingen wordt aangepakt.

Het reflectieprobleem

Weerspiegeling maakt het mogelijk om tijdens runtime typen en leden dynamisch te inspecteren en aan te roepen. Dit is krachtig, maar creëert een uitdaging voor statische analyse.

Waarom reflectie het trimmen verstoort

Kijk eens naar dit voorbeeld:

void PrintMethodNames(Type type)
{
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

// Called somewhere in the app
PrintMethodNames(typeof(DateTime));

Vanuit het perspectief van de trimmer:

  • Het merkt op dat type.GetMethods() wordt aangeroepen.
  • Het weet niet wat type er zal zijn (het is een parameter).
  • Er kan niet worden bepaald welke typen methoden moeten worden bewaard.
  • Zonder richtlijnen kunnen methoden uit DateTime worden verwijderd, waardoor de code wordt onderbroken.

Daarom produceert de trimmer een waarschuwing over deze code.

Inzicht in DynamicallyAccessedMembers

DynamicallyAccessedMembersAttribute Lost het reflectieprobleem op door een expliciet contract te maken tussen de aanroeper en de aangeroepen methode.

Het fundamentele doel

DynamicallyAccessedMembers vertelt de trimmer: 'Deze parameter (of veld of retourwaarde) bevat een Type die specifieke leden dient te behouden omdat reflectie wordt gebruikt om er toegang toe te krijgen.'

Een concreet voorbeeld

Laten we het vorige voorbeeld oplossen:

void PrintMethodNames(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

// When this is called...
PrintMethodNames(typeof(DateTime));

Nu begrijpt de trimmer het volgende:

  1. PrintMethodNames vereist dat de parameter behouden blijft PublicMethods .
  2. De oproepsite geeft typeof(DateTime) door.
  3. DateTimeDaarom moeten de openbare methoden worden bewaard.

Het kenmerk maakt een vereiste die terugloopt van het weerspiegelingsgebruik naar de bron van de Type waarde.

Het is een contract, geen hint

Dit is van cruciaal belang om te begrijpen: DynamicallyAccessedMembers is niet alleen documentatie. De trimmer handhaaft de contractvoorwaarden.

Analogie met algemene typebeperkingen

Als u bekend bent met algemene typebeperkingen, DynamicallyAccessedMembers werkt dit op dezelfde manier. Net zoals algemene beperkingen door uw code stromen:

void Process<T>(T value) where T : IDisposable
{
    value.Dispose();  // OK because constraint guarantees IDisposable
}

void CallProcess<T>(T value) where T : IDisposable
{
    Process(value);  // OK - constraint satisfied
}

void CallProcessBroken<T>(T value)
{
    Process(value);  // ERROR - T doesn't have IDisposable constraint
}

DynamicallyAccessedMembers maakt vergelijkbare vereisten die door uw code stromen:

void UseReflection([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    type.GetMethods();  // OK because annotation guarantees methods are preserved
}

void PassType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    UseReflection(type);  // OK - requirement satisfied
}

void PassTypeBroken(Type type)
{
    UseReflection(type);  // WARNING - type doesn't have required annotation
}

Beide maken contracten die moeten worden uitgevoerd en produceren fouten of waarschuwingen wanneer niet aan het contract kan worden voldaan.

Hoe het contract wordt afgedwongen

[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
Type GetTypeForProcessing() 
{
    return typeof(DateTime);  // OK - trimmer will preserve DateTime's public methods
}

[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
Type GetTypeFromInput()
{
    // WARNING: The trimmer can't verify that the type from GetType()
    // will have its public methods preserved
    return Type.GetType(Console.ReadLine());
}

Als u het contract niet kunt vervullen (zoals in het tweede voorbeeld), krijgt u een waarschuwing.

Begrip van RequiresUnreferencedCode

Sommige codepatronen kunnen eenvoudigweg niet statisch worden geanalyseerd. Gebruik RequiresUnreferencedCodeAttributevoor deze gevallen .

Wanneer gebruikt u RequiresUnreferencedCode

Gebruik het RequiresUnreferencedCodeAttribute kenmerk wanneer:

  • Het weerspiegelingspatroon is fundamenteel dynamisch: Assemblies laden of types via tekenreeksnamen uit externe bronnen.
  • De complexiteit is te hoog om aantekeningen te maken: code die gebruikmaakt van weerspiegeling op complexe, gegevensgestuurde manieren.
  • U gebruikt runtime-codegeneratie: Technologieën zoals System.Reflection.Emit of het dynamic keyword.

Voorbeeld:

[RequiresUnreferencedCode("Plugin loading is not compatible with trimming")]
void LoadPlugin(string pluginPath)
{
    Assembly pluginAssembly = Assembly.LoadFrom(pluginPath);
    // Plugin assemblies aren't known at publish time
    // This fundamentally cannot be made trim-compatible
}

Het doel van het kenmerk

RequiresUnreferencedCode dient twee doeleinden:

  1. Onderdrukt waarschuwingen binnen de methode: de trimmer analyseert of waarschuwt niet voor het weerspiegelingsgebruik.
  2. Hiermee worden waarschuwingen gemaakt op oproepsites: elke code die deze methode aanroept, krijgt een waarschuwing.

Met deze waarschuwing worden ontwikkelaars attent gemaakt op codepaden die niet compatibel zijn met trim.

Goede berichten schrijven

Het bericht moet ontwikkelaars helpen hun opties te begrijpen:

// ❌ Not helpful
[RequiresUnreferencedCode("Uses reflection")]

// ✅ Helpful - explains what's incompatible and suggests alternatives
[RequiresUnreferencedCode("Plugin loading is not compatible with trimming. Consider using a source generator for known plugins instead")]

Hoe eisen door de code stromen

Als u weet hoe vereisten worden doorgegeven, weet u waar u kenmerken kunt toevoegen.

Vereisten keren terug

Vereisten stromen vanaf de plek waar weerkaatsing wordt toegepast tot aan de oorsprong Type.

void CallChain()
{
    // Step 1: Source of the Type value
    ProcessData<DateTime>();  // ← Requirement ends here
}

void ProcessData<T>()
{
    // Step 2: Type flows through generic parameter
    var type = typeof(T);
    DisplayInfo(type);  // ← Requirement flows back through here
}

void DisplayInfo(Type type)
{
    // Step 3: Reflection creates the requirement
    type.GetMethods();  // ← Requirement starts here
}

Als u deze trim-compatibel wilt maken, moet u aantekeningen maken op de ketting:

void ProcessData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
    var type = typeof(T);
    DisplayInfo(type);
}

void DisplayInfo(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    type.GetMethods();
}

Nu de vereistenstromen: GetMethods() vereist PublicMethodstype parameter moet PublicMethodsT algemeen heeft PublicMethods nodig → DateTime moet PublicMethods behouden blijven.

Vereisten worden door opslagsystemen doorgevoerd

Vereisten stromen ook door velden en eigenschappen:

class TypeHolder
{
    // This field will hold Types that need PublicMethods preserved
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
    private Type _typeToProcess;

    public void SetType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
    {
        _typeToProcess = typeof(T);  // OK - requirement satisfied
    }

    public void Process()
    {
        _typeToProcess.GetMethods();  // OK - field is annotated
    }
}

De juiste benadering kiezen

Wanneer u code tegenkomt die weerspiegeling nodig heeft, volgt u deze beslissingsstructuur:

1. Kunt u reflectie vermijden?

De beste oplossing is om reflectie zo mogelijk te voorkomen:

// ❌ Uses reflection
void Process(Type type)
{
    var instance = Activator.CreateInstance(type);
}

// ✅ Uses compile-time generics instead
void Process<T>() where T : new()
{
    var instance = new T();
}

2. Is het type bekend tijdens het compileren?

Als reflectie nodig is, maar de typen bekend zijn, gebruikt u DynamicallyAccessedMembers:

// ✅ Trim-compatible
void Serialize<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(T obj)
{
    foreach (var prop in typeof(T).GetProperties())
    {
        // Serialize property
    }
}

3. Is het patroon fundamenteel dynamisch?

Als de typen pas bij runtime echt niet bekend zijn, gebruik RequiresUnreferencedCode:

// ✅ Documented as trim-incompatible
[RequiresUnreferencedCode("Dynamic type loading is not compatible with trimming")]
void ProcessTypeByName(string typeName)
{
    var type = Type.GetType(typeName);
    // Work with type
}

Algemene patronen en oplossingen

Patroon: Factory-methoden

// Problem: Creating instances from Type parameter
object CreateInstance(Type type)
{
    return Activator.CreateInstance(type);
}

// Solution: Specify constructor requirements
object CreateInstance(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type)
{
    return Activator.CreateInstance(type);
}

Patroon: Plug-insystemen

// Problem: Loading unknown assemblies at runtime
[RequiresUnreferencedCode("Plugin loading is not trim-compatible. Plugins must be known at compile time.")]
void LoadPlugins(string pluginDirectory)
{
    foreach (var file in Directory.GetFiles(pluginDirectory, "*.dll"))
    {
        Assembly.LoadFrom(file);
    }
}

// Better solution: Known plugins with source generation
// Use source generators to create plugin registration code at compile time

Belangrijke punten

  • De trimmer maakt gebruik van statische analyse. Het kan alleen codepaden begrijpen die tijdens het compileren zichtbaar zijn.
  • Weerspiegeling breekt statische analyse : de trimmer kan niet zien welke reflectie tijdens runtime wordt geopend.
  • DynamischAccessedMembers maakt contracten - het informeert de trimmer over wat er moet behouden blijven.
  • Vereisten stromen achteruit : van weerspiegelingsgebruik naar de bron van de Type waarde.
  • RequiresUnreferencedCode-documenteert incompatibiliteit - gebruik deze wanneer code niet kan worden geanalyseerd.
  • Attributen zijn niet alleen hints - de trimmer handhaaft contracten en produceert waarschuwingen wanneer hieraan niet kan worden voldaan.

Volgende stappen