Aracılığıyla paylaş


Kırpma analizini anlama

Bu makalede, belirli kod desenlerinin neden uyarı ürettiğini ve kodunuzu kırpma uyumlu hale getirme adımlarını anlamanıza yardımcı olmak için kırpma analizinin ardındaki temel kavramlar açıklanmaktadır. Bu kavramları anlamak, yalnızca "araçları susturmak için öznitelikleri etrafa yaymak" yerine kırpma uyarılarını ele alırken bilinçli kararlar vermenizi sağlar.

Düzeltici kodu nasıl analiz eder?

Düzeltici, uygulamanız tarafından hangi kodun kullanıldığını belirlemek için yayımlama zamanında statik analiz gerçekleştirir. Bilinen giriş noktalarından (yönteminiz Main gibi) başlar ve uygulamanız aracılığıyla kod yollarını izler.

Düzelticinin anlayabileceği şeyler

Düzeltici doğrudan, derleme zamanı görünür kod desenlerini analiz etme konusunda mükemmeldir:

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

Bu örneklerde, trimmer kod yolunu izleyebilir ve DateTime.AddDays, List<string>.Add ve MyUtility.Process'yi son uygulamada tutulması gereken kullanılan kod olarak işaretleyebilir.

Düzelticinin anlayamadığı şeyler

Kesici, çalışma zamanına kadar bir işlemin hedefinin bilinmediği dinamik işlemlerle zorlanır.

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

Bu örneklerde, düzelticinin şunları bilmesine imkan yoktur:

  • Kullanıcı hangi türü girecek
  • GetSomeObject() hangi türü döndürür?
  • Dinamik olarak yüklenen derlemede hangi kod var?

Bu, fazlalıkları kaldırma uyarılarının ele aldığı temel sorundur.

Yansıma sorunu

Yansıma, kodun çalışma zamanında türleri ve üyeleri dinamik olarak incelemesine ve çağırmasına olanak tanır. Bu güçlüdür ancak statik analiz için bir zorluk oluşturur.

Yansıma neden kırpmayı bozar?

Şu örneği göz önünde bulundurun:

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

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

Budayıcının bakış açısından:

  • Belirlendiğinde type.GetMethods() çağrılır.
  • Ne type olacağını bilmez (bir parametredir).
  • Hangi türlerin yöntemlerinin korunması gerektiğini belirleyemez.
  • Kılavuz yoksa, DateTime'den yöntemleri kaldırmak kodun bozulmasına yol açabilir.

Sonuç olarak, kesici bu kodda bir uyarı verir.

DynamicallyAccessedMembers'ı anlama

DynamicallyAccessedMembersAttribute çağıran ile çağrılan yöntem arasında açık bir sözleşme oluşturarak yansıma sorununu çözer.

Temel amaç

DynamicallyAccessedMembers düzelticiye şunu söyler: "Bu parametre (veya alan veya dönüş değeri), yansıma kullanılarak erişilecek belirli üyelerin korunmasını gerektiren bir Type tutacaktır."

Somut bir örnek

Önceki örneği düzeltelim:

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

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

Artık trimmer anlıyor:

  1. PrintMethodNames, parametresinin PublicMethods korunmuş olmasını gerektirir.
  2. Çağrı noktası typeof(DateTime) öğesini geçirir.
  3. Bu nedenle, DateTime'nin genel yöntemleri tutulmalıdır.

özniteliği, yansıma kullanımından değerin kaynağına geriye doğru akan bir Type oluşturur.

Bu bir sözleşme, ipucu değil.

Bunu anlamak çok önemlidir: DynamicallyAccessedMembers yalnızca belgeler değildir. Trimer bu sözleşmeyi uygular.

Genel tür kısıtlamalarıyla benzetme

Genel tür kısıtlamaları hakkında bilgi sahibiyseniz, DynamicallyAccessedMembers benzer şekilde çalışır. Genel kısıtlamaların kodunuz üzerinden akması gibi:

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 kodunuz üzerinden akan benzer gereksinimler oluşturur:

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
}

Hem yerine getirilmesi gereken sözleşmeler oluşturur hem de sözleşme karşılanamıyorsa hatalar veya uyarılar üretir.

Sözleşme nasıl uygulanır?

[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());
}

Sözleşmeyi yerine getiremiyorsanız (ikinci örnekte olduğu gibi) bir uyarı alırsınız.

RequiresUnreferencedCode'un Anlaşılması

Bazı kod desenleri statik olarak çözümlenebilir hale getirilemez. Bu durumlarda kullanın RequiresUnreferencedCodeAttribute.

RequiresUnreferencedCode ne zaman kullanılır?

Özniteliğini RequiresUnreferencedCodeAttribute şu durumlarda kullanın:

  • Yansıma deseni temel olarak dinamiktir: Derlemeleri veya türleri dış kaynaklardan dize adlarına göre yükleme.
  • Açıklama eklemek için karmaşıklık çok yüksek: Yansımayı karmaşık, veri temelli yollarla kullanan kod.
  • Çalışma zamanı kod üretimi kullanıyorsunuz: System.Reflection.Emit veya dynamic anahtar sözcüğü gibi teknolojiler.

Örnek:

[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
}

Özniteliğin amacı

RequiresUnreferencedCode iki amaca hizmet eder:

  1. Yöntemin içindeki uyarıları bastırır: Düzeltici, yansıma kullanımını analiz etmez veya bu konuda uyarı vermez.
  2. Çağrı sitelerinde uyarılar oluşturur: Bu yöntemi çağıran tüm kodlar bir uyarı alır.

Bu, geliştiricilere kırpma ile uyumsuz kod yolları hakkında görünürlük sağlamak için uyarıyı yukarı iletir.

İyi iletiler yazma

İleti, geliştiricilerin seçeneklerini anlamasına yardımcı olmalıdır:

// ❌ 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")]

Gereksinimler kod aracılığıyla nasıl akıyor?

Gereksinimlerin yayılımının anlaşılması, özniteliklerin nereye ekleneceğini bilmenize yardımcı olur.

Gereksinimler geriye doğru akar

Gereksinimler, yansımanın kullanıldığı yerden kaynaklandığı Type yere geri döner.

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
}

Bu kesim işlemini uyumlu hale getirmek için zincire etiketleme eklemeniz gerekir.

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

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

Şimdi gereksinim akışları: GetMethods() gerektirir PublicMethodstype parametresi gerekiyor PublicMethods → genel T gerekiyor PublicMethodsDateTime gerekiyor PublicMethods korunmalıdır.

Gereksinimler depolama sistemi üzerinden akıyor

Gereksinimler alanlar ve özellikler arasında da akar:

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

Doğru yaklaşımı seçme

Yansıma gerektiren bir kodla karşılaştığınızda şu karar ağacını izleyin:

1. Yansımayı önleyebilir misiniz?

En iyi çözüm mümkün olduğunda yansımayı önlemektir:

// ❌ 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. Tür, derleme zamanında bilinir mi?

Yansıma gerekliyse ancak türler biliniyorsa kullanın DynamicallyAccessedMembers:

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

3. Desen temelde dinamik mi?

Türler çalışma zamanına kadar gerçekten bilinmiyorsa RequiresUnreferencedCode kullanın:

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

Yaygın desenler ve çözümler

Desen: Fabrika yöntemleri

// 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);
}

Örüntü: Eklenti sistemleri

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

Önemli çıkarımlar

  • Düzeltici statik analizi kullanır ; yalnızca derleme zamanında görünen kod yollarını anlayabilir.
  • Yansıma statik analizi bozar ; düzeltici çalışma zamanında hangi yansımaya erişeceğini göremez.
  • DynamicallyAccessedMembers sözleşmeler oluşturur. Trimmer'a korunması gerekenleri bildirir.
  • Gereksinimler geriye doğru akar - yansıma kullanımından değerin Type kaynağına geri döner.
  • RequiresUnreferencedCode kodun analiz edilemez olduğunu belirtir - kodun çözümlenebilir hale getirilemediği durumlarda kullanın.
  • Öznitelikler yalnızca ipuçları değildir ; düzeltici sözleşmeleri zorlar ve karşılanmadığında uyarılar üretir.

Sonraki Adımlar