Kırpma uyarılarına giriş

Kavramsal olarak kırpma basittir: Bir uygulamayı yayımladığınızda .NET SDK'sı uygulamanın tamamını analiz eder ve kullanılmayan tüm kodları kaldırır. Bununla birlikte, kullanılmayan veya daha kesin olarak nelerin kullanıldığını belirlemek zor olabilir.

.NET SDK' sı, uygulamaları kırpırken davranış değişikliklerini önlemek için kırpma uyarıları aracılığıyla kırpma uyumluluğunun statik analizini sağlar. Düzeltici, kırpmayla uyumlu olmayabilecek kodu bulduğunda kırpma uyarıları üretir. Kırpma uyumlu olmayan kod, kırpıldıktan sonra bir uygulamada davranış değişiklikleri ve hatta kilitlenmeler oluşturabilir. İdeal olarak, kırpma kullanan tüm uygulamalar herhangi bir kırpma uyarısı üretmemelidir. Herhangi bir kırpma uyarısı varsa, herhangi bir davranış değişikliği olmadığından emin olmak için kırpma işleminden sonra uygulama kapsamlı bir şekilde test edilmelidir.

Bu makale, bazı desenlerin neden kırpma uyarıları ürettiğini ve bu uyarıların nasıl ele alınabileceğini anlamanıza yardımcı olur.

Kırpma uyarısı örnekleri

Çoğu C# kodu için, hangi kodun kullanıldığını ve hangi kodun kullanılmadığını belirlemek kolaydır; düzeltici yöntem çağrılarını, alan ve özellik başvurularını ve benzeri adımları izleyebilir ve hangi koda erişileceğini belirleyebilir. Ne yazık ki yansıma gibi bazı özellikler önemli bir sorun oluşturur. Aşağıdaki kodu inceleyin:

string s = Console.ReadLine();
Type type = Type.GetType(s);
foreach (var m in type.GetMethods())
{
    Console.WriteLine(m.Name);
}

Bu örnekte, GetType() dinamik olarak bilinmeyen bir ada sahip bir tür istemektedir ve ardından tüm yöntemlerinin adlarını yazdırır. Yayımlama zamanında hangi tür adının kullanılacağını bilmenin bir yolu olmadığından, düzelticinin çıkışta hangi türün korunacağını bilmesine imkan yoktur. Büyük olasılıkla bu kod kırpmadan önce çalışmış olabilir (girişin hedef çerçevede var olduğu bilinen bir şey olduğu sürece), ancak tür bulunamadığında null döndürdüğünden kırpmadan Type.GetType sonra büyük olasılıkla null başvuru özel durumu oluşturur.

Bu durumda, düzeltici çağrısında Type.GetTypeuygulama tarafından hangi türün kullanılacağını belirleyemediğini belirten bir uyarı yayınlar.

Kırpma uyarılarına tepki verme

Kırpma uyarıları, kırpmaya öngörülebilirlik kazandırmak içindir. Büyük olasılıkla göreceğiniz iki büyük uyarı kategorisi vardır:

  1. İşlev, kırpma ile uyumlu değil
  2. İşlevlerin kırpma uyumlu olması için girişte belirli gereksinimleri vardır

Kırpma ile uyumlu olmayan işlevsellik

Bunlar genellikle hiç çalışmayan veya kırpılmış bir uygulamada kullanılıyorsa bazı durumlarda bozuk olabilecek yöntemlerdir. İyi bir örnek, önceki örnekteki Type.GetType yöntemdir. Kırpılmış bir uygulamada işe yarayabilir, ancak bunun garantisi yoktur. Bu TÜR API'ler ile RequiresUnreferencedCodeAttributeişaretlenir.

RequiresUnreferencedCodeAttribute basit ve geniştir: Bu, üyeye kırpmayla uyumsuz bir açıklama eklendiği anlamına gelen bir özniteliktir. Bu öznitelik, kod temel olarak kırpma uyumlu olmadığında veya kırpma bağımlılığı düzelticiye açıklanamayacak kadar karmaşık olduğunda kullanılır. Bu genellikle, örneğin aracılığıyla LoadFrom(String)dinamik olarak kod yükleyen yöntemler için geçerlidir. Örneğin, aracılığıyla bir uygulama veya derlemedeki tüm türleri numaralandırır veya bu türlerde arama yapar, örneğin GetType()C# dynamic anahtar sözcüğünü kullanır veya diğer çalışma zamanı kod oluşturma teknolojilerini kullanır. Örnek olarak:

[RequiresUnreferencedCode("This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead")]
void MethodWithAssemblyLoad()
{
    ...
    Assembly.LoadFrom(...);
    ...
}

void TestMethod()
{
    // IL2026: Using method 'MethodWithAssemblyLoad' which has 'RequiresUnreferencedCodeAttribute'
    // can break functionality when trimming application code. This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead.
    MethodWithAssemblyLoad();
}

için RequiresUnreferencedCodeçok fazla geçici çözüm yoktur. En iyi düzeltme, kırpma sırasında yöntemi çağırmaktan kaçınmak ve kırpma uyumlu başka bir şey kullanmaktır.

İşlevi kırpmayla uyumsuz olarak işaretleme

Bir kitaplık yazıyorsanız ve uyumsuz işlevselliği kullanıp kullanmayacağınız denetiminizde değilse, ile RequiresUnreferencedCodeişaretleyebilirsiniz. Bu, yönteminize kırpma ile uyumsuz olarak ek açıklama ekler. Kullanıldığında RequiresUnreferencedCode , verilen yöntemdeki tüm uyarılar kesilir, ancak başka biri her çağırduğunda bir uyarı üretir.

, RequiresUnreferencedCodeAttribute bir Messagebelirtmenizi gerektirir. İleti, işaretli yöntemi çağıran geliştiriciye bildirilen bir uyarının parçası olarak gösterilir. Örneğin:

IL2026: Using member <incompatible method> which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. <The message value>

Yukarıdaki örnekle, belirli bir yönteme yönelik bir uyarı aşağıdaki gibi görünebilir:

IL2026: Using member 'MethodWithAssemblyLoad()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead.

Bu tür API'leri çağıran geliştiriciler genellikle etkilenen API'nin özellikleriyle veya kırpmayla ilgili özellikleriyle ilgilenmez.

İyi bir ileti, hangi işlevselliğin kırpmayla uyumlu olmadığını belirtmeli ve ardından geliştiriciye olası sonraki adımlarının ne olduğunu göstermelidir. Farklı bir işlevsellik kullanmanızı veya işlevselliğin nasıl kullanıldığını değiştirmenizi önerebilir. Ayrıca, işlevselliğin net bir değiştirme olmadan kırpma işlemiyle henüz uyumlu olmadığını da belirtebilir.

Geliştiricinin kılavuzu bir uyarı iletisine eklenemeyecek kadar uzun olursa, geliştiriciyi sorunu ve olası çözümleri daha ayrıntılı bir şekilde açıklayan bir web sayfasına yönlendirmek için isteğe bağlı UrlRequiresUnreferencedCodeAttribute bir ekleyebilirsiniz.

Örneğin:

[RequiresUnreferencedCode("This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead", Url = "https://site/trimming-and-method")]
void MethodWithAssemblyLoad() { ... }

Bu bir uyarı üretir:

IL2026: Using member 'MethodWithAssemblyLoad()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead. https://site/trimming-and-method

Kullanmak RequiresUnreferencedCode genellikle aynı nedenden dolayı onunla daha fazla yöntemin işaretlenmesine neden olur. Üst düzey bir yöntem kırpma uyumlu olmayan düşük düzeyli bir yöntem çağırdığı için kırpma ile uyumsuz hale geldiğinde bu durum yaygındır. Uyarıyı genel API'ye "kabartırsınız". her kullanımı RequiresUnreferencedCode için bir ileti gerekir ve bu gibi durumlarda iletiler büyük olasılıkla aynıdır. Dizelerin çoğaltılmasını önlemek ve bakımını kolaylaştırmak için, iletiyi depolamak için sabit bir dize alanı kullanın:

class Functionality
{
    const string IncompatibleWithTrimmingMessage = "This functionality is not compatible with trimming. Use 'FunctionalityFriendlyToTrimming' instead";

    [RequiresUnreferencedCode(IncompatibleWithTrimmingMessage)]
    private void ImplementationOfAssemblyLoading()
    {
        ...
    }

    [RequiresUnreferencedCode(IncompatibleWithTrimmingMessage)]
    public void MethodWithAssemblyLoad()
    {
        ImplementationOfAssemblyLoading();
    }
}

Girişinde gereksinimleri olan işlevsellik

Kırpma, yöntemlere ve kırpma uyumlu koda yol açan diğer üyelere yönelik girişlerle ilgili daha fazla gereksinim belirtmek için API'ler sağlar. Bu gereksinimler genellikle yansıma ve bir türdeki belirli üyelere veya işlemlere erişim olanağıyla ilgili olur. Bu tür gereksinimler kullanılarak DynamicallyAccessedMembersAttributebelirtilir.

'nin aksine RequiresUnreferencedCode, doğru açıklama ek açıklamalı olduğu sürece yansıma bazen düzeltici tarafından anlaşılabilir. Şimdi özgün örneğe bir kez daha göz atalım:

string s = Console.ReadLine();
Type type = Type.GetType(s);
foreach (var m in type.GetMethods())
{
    Console.WriteLine(m.Name);
}

Önceki örnekte asıl sorun şudur Console.ReadLine(): . Herhangi bir tür okunabildiğinden, düzelticinin yöntemlere veya başka bir türe System.DateTimeSystem.Guid ihtiyacınız olup olmadığını bilmesinin hiçbir yolu yoktur. Öte yandan, aşağıdaki kod iyi olacaktır:

Type type = typeof(System.DateTime);
foreach (var m in type.GetMethods())
{
    Console.WriteLine(m.Name);
}

Burada, düzeltici tam olarak başvurulmakta olan türü görebilir: System.DateTime. Artık tüm genel yöntemleri üzerinde System.DateTimetutması gerektiğini belirlemek için akış analizini kullanabilir. Peki nereden DynamicallyAccessMembers geliyor? Yansıma birden çok yönteme bölündüğünde. Aşağıdaki kodda, türün System.DateTime yansımanın 'nin yöntemlerine Method3 erişmek System.DateTimeiçin kullanıldığı yere aktığını görebiliriz.

void Method1()
{
    Method2<System.DateTime>();
}
void Method2<T>()
{
    Type t = typeof(T);
    Method3(t);
}
void Method3(Type type)
{
    var methods = type.GetMethods();
    ...
}

Önceki kodu derlerseniz aşağıdaki uyarı oluşturulur:

IL2070: Program.Method3(Tür): 'this' bağımsız değişkeni ,'System.Type.GetMethods()' çağrısında 'DynamicallyAccessedMemberTypes.PublicMethods'u karşılamıyor. 'Program.Method3(Type)' yönteminin 'type' parametresinin eşleşen ek açıklamaları yok. Kaynak değerin, atandığı hedef konumda bildirilenlerle en az aynı gereksinimleri bildirmesi gerekir.

Performans ve kararlılık için yöntemler arasında akış analizi gerçekleştirilmez, bu nedenle yansıma çağrısından (GetMethods) kaynağındaki Typeyöntemler arasında bilgi geçirmek için bir ek açıklama gerekir. Önceki örnekte, düzeltici uyarısı ek açıklamanın olması için PublicMethods çağrıldığı nesne örneğini gerektirdiğini ancak değişkenin type aynı gereksinime sahip olmadığını söylüyorGetMethods.Type Başka bir deyişle, gereksinimleri arayandan GetMethods çağırana geçirmemiz gerekir:

void Method1()
{
    Method2<System.DateTime>();
}
void Method2<T>()
{
    Type t = typeof(T);
    Method3(t);
}
void Method3(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    var methods = type.GetMethods();
  ...
}

parametresine typeaçıklama ekledikten sonra özgün uyarı kaybolur, ancak başka bir uyarı görüntülenir:

IL2087: 'type' bağımsız değişkeni, 'Program.Method3(Type)' çağrısında 'DynamicallyAccessedMemberTypes.PublicMethods' öğesini karşılamıyor. 'Program.Method2<T()' genel parametresinin 'T>' parametresi eşleşen ek açıklamalara sahip değil.

içinde parametresine typeMethod3Method2 kadar ek açıklamalar yaydık. Benzer bir sorun var. Düzeltici, çağrısı typeofaracılığıyla akan değeri T izleyebilir, yerel değişkenine tatanır ve öğesine Method3geçirilir. Bu noktada parametresinin type gerekli PublicMethods olduğunu ancak üzerinde Thiçbir gereksinim olmadığını görür ve yeni bir uyarı oluşturur. Bunu düzeltmek için, statik olarak bilinen bir türe (veya gibi System.DateTimeSystem.Tuple) veya başka bir açıklamalı değere ulaşana kadar çağrı zincirinde ek açıklamalar uygulayarak "açıklama eklemeli ve yaymalıyız". Bu durumda, type parametresine TMethod2açıklama eklememiz gerekir.

void Method1()
{
    Method2<System.DateTime>();
}
void Method2<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
    Type t = typeof(T);
    Method3(t);
}
void Method3(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    var methods = type.GetMethods();
  ...
}

Artık hiçbir uyarı yoktur çünkü düzeltici çalışma zamanı yansıması (genel yöntemler) ve hangi türlerde ()System.DateTime aracılığıyla hangi üyelere erişilebileceğini bilir ve bunları korur. Düzelticinin neleri koruyacağını bilmesi için ek açıklamalar eklemek en iyi yöntemdir.

Etkilenen kod ile RequiresUnreferencedCodebir yöntemdeyse, bu ek gereksinimler tarafından oluşturulan uyarılar otomatik olarak gizleniyor.

aksine RequiresUnreferencedCode, yalnızca uyumsuzluğu bildirir, ekleme DynamicallyAccessedMembers kodu kırpma ile uyumlu hale getirir.

Düzeltici uyarılarını gizleme

Bir şekilde çağrının güvenli olduğunu saptayabiliyorsanız ve gerekli olan tüm kodlar kırpılmayacaksa, kullanarak UnconditionalSuppressMessageAttributeuyarıyı da gizleyebilirsiniz. Örneğin:

[RequiresUnreferencedCode("Use 'MethodFriendlyToTrimming' instead")]
void MethodWithAssemblyLoad() { ... }

[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode",
    Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")]
void TestMethod()
{
    InitializeEverything();

    MethodWithAssemblyLoad(); // Warning suppressed

    ReportResults();
}

Uyarı

Kırpma uyarılarını bastırırken çok dikkatli olun. Arama şu anda kırpma uyumlu olabilir, ancak siz kodunuzu değiştirdiğinizde değişebilir ve tüm gizlemeleri gözden geçirmeyi unutabilirsiniz.

UnconditionalSuppressMessage gibi SuppressMessage ancak ve diğer derleme sonrası araçlar tarafından publish görülebilir.

Önemli

Düzeltici uyarılarını kullanmak veya #pragma warning disable engellemek için kullanmayınSuppressMessage. Bunlar yalnızca derleyici için çalışır, ancak derlenmiş derlemede korunmaz. Düzeltici derlenmiş derlemelerde çalışır ve gizlemeyi görmez.

Gizleme yöntemi gövdesinin tamamına uygulanır. Bu nedenle yukarıdaki örneğimizde yönteminden gelen tüm IL2026 uyarıları gizler. Bu, açıklama eklemediğiniz sürece sorunlu yöntemin hangisi olduğu net olmadığından anlaşılmasını zorlaştırır. Daha da önemlisi, kod gelecekte değişirse , örneğin ReportResults kırpma uyumsuz hale gelirse, bu yöntem çağrısı için hiçbir uyarı bildirilir.

Sorunlu yöntem çağrısını ayrı bir yönteme veya yerel işleve yeniden düzenleyip gizlemeyi yalnızca bu yönteme uygulayarak bu sorunu çözebilirsiniz:

void TestMethod()
{
    InitializeEverything();

    CallMethodWithAssemblyLoad();

    ReportResults();

    [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode",
        Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")]
    void CallMethodWithAssemblyLoad()
    {
        MethodWIthAssemblyLoad(); // Warning suppressed
    }
}