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.GetType
uygulama 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:
- İşlev, kırpma ile uyumlu değil
- İş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 RequiresUnreferencedCode
iş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 Message
belirtmenizi 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ı Url
RequiresUnreferencedCodeAttribute 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.DateTime
System.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.DateTime
tutması 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.DateTime
iç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 Type
yö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 type
açı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 type
Method3
Method2
kadar ek açıklamalar yaydık. Benzer bir sorun var. Düzeltici, çağrısı typeof
aracılığıyla akan değeri T
izleyebilir, yerel değişkenine t
atanır ve öğesine Method3
geçirilir. Bu noktada parametresinin type
gerekli PublicMethods
olduğunu ancak üzerinde T
hiç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.DateTime
System.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 T
Method2
açı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 RequiresUnreferencedCode
bir 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.
Not
kullanıldığında DynamicallyAccessedMembersAttribute
türün belirtilen DynamicallyAccessedMemberTypes
tüm üyeleri köklenir. Bu, üyelerin yanı sıra bu üyeler tarafından başvuruda bulunan meta verileri de tutacağı anlamına gelir. Bu, beklenenden çok daha büyük uygulamalara yol açabilir. Gereken minimum DynamicallyAccessedMemberTypes
değeri kullanmaya dikkat edin.
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
}
}