Oğuzhan KARAGÜZEL

OĞUZHAN KARAGÜZEL

Yazılım geliştirici

Native AOT Uyarıları: IL2026, IL3050, IL2057 ve IL2075 Nasıl Çözülür?

Yazar: Oğuzhan KARAGÜZEL ·

JIT ve AOT farkı yazısında küçük bir MVC uygulamasını Native AOT ile derlemiştim. Derleme bir sürü uyarıyla birlikte başarıyla bitti, ama uygulama daha ilk isteğe cevap veremeden TypeLoadException ile çöktü. O yazıda vardığım sonuç şuydu: AOT uyarısı veren bir projeyi yayına almayın.

Bu yazıda bir adım daha ileri gidip en sık karşılaşılan dört uyarıyı tek tek ele alacağım. Her biri için küçük bir örnek yazdım, Native AOT ile derledim ve çalıştırdım. Uyarının ne dediğini, çalışınca ne olduğunu ve nasıl düzeltildiğini gerçek çıktılarla göstereceğim.

Ortam: .NET 10, Linux konteynerinde dotnet publish -c Release -r linux-x64, proje dosyasında <PublishAot>true</PublishAot>.

Önce kısa bir hatırlatma: neden uyarı çıkıyor?

Native AOT, uygulamayı derlerken hangi kodun kullanıldığını analiz ediyor ve sadece onu dosyaya koyuyor. Kullanılmadığını düşündüğü tipleri, metotları ve property'leri dışarıda bırakıyor. Buna trimming deniyor.

Sorun reflection'da başlıyor. Type.GetType("EmailNotifier") gibi bir çağrıda hangi tipin isteneceği ancak çalışma anında belli oluyor. Derleyici bunu önceden göremiyor. Göremediği yerde de bize uyarı veriyor.

Uyarılar iki aileye ayrılıyor:

  • IL2xxx: Trimming uyarıları. "Burada ihtiyaç duyulan kod derlenen dosyada olmayabilir" demek.
  • IL3xxx: AOT uyarıları. "Burada çalışma anında kod üretilmesi gerekebilir, AOT'ta bu mümkün değil" demek.

IL2026 ve IL3050: JSON serileştirme

En sık göreceğiniz ikili bu. Şu kadar sıradan bir satır:

C#
var todo = new Todo(1, "Yazıyı bitir", false);
Console.WriteLine(JsonSerializer.Serialize(todo));

Derlerken iki uyarı veriyor:

Çıktı
warning IL2026: Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, JsonSerializerOptions)'
which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code.

warning IL3050: Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, JsonSerializerOptions)'
which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling.

Çalıştırınca:

Çıktı
InvalidOperationException: Reflection-based serialization has been disabled for this application.
Either use the source generator APIs or explicitly configure the 'JsonSerializerOptions.TypeInfoResolver' property.

System.Text.Json normalde tipin property'lerini çalışma anında reflection ile buluyor. Native AOT'ta bu yol kapalı.

Çözüm: Kaynak üretici (source generator). Serileştireceğiniz tipleri bir JsonSerializerContext üzerinde tanımlıyorsunuz, gereken kodu derleyici derleme sırasında üretiyor:

C#
[JsonSerializable(typeof(Todo))]
internal partial class AppJsonContext : JsonSerializerContext;

Console.WriteLine(JsonSerializer.Serialize(todo, AppJsonContext.Default.Todo));
Çıktı
{"Id":1,"Title":"Yaz\u0131y\u0131 bitir","Done":false}

Uyarılar gitti, çıktı geldi. Türkçe karakterlerin \u0131 gibi kaçış dizisine dönüşmesi AOT'la ilgili değil, System.Text.Json'un varsayılan davranışı. JSON için geçerli bir çıktı; ama okunabilir olsun isterseniz JavaScriptEncoder ayarını değiştirebilirsiniz.

ASP.NET Core minimal API'de aynı context'i şöyle bağlıyorsunuz:

C#
builder.Services.ConfigureHttpJsonOptions(o =>
    o.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));

IL2057: Adı string olarak verilen tip

Bir eklenti ya da strateji yapısında sık yazılan bir kod:

C#
var typeName = Environment.GetEnvironmentVariable("PLUGIN")!;
var type = Type.GetType(typeName);
var instance = Activator.CreateInstance(type!);

Uyarı:

Çıktı
warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String)'.
It's not possible to guarantee the availability of the target type.

Bunu iki şekilde çalıştırdım. Tip adı olarak aynı projedeki EmailNotifier'ı verdiğimde tip bulundu ama:

Çıktı
MissingMethodException: No parameterless constructor defined for type 'EmailNotifier'.

Tip dosyada vardı ama constructor'ı yoktu. Derleyici kodda hiçbir yerde new EmailNotifier() görmediği için onu gereksiz saymış ve atmıştı.

Başka bir kütüphanedeki bir tipi verdiğimde ise sonuç daha sessizdi: Type.GetType null döndü. Tip hiç yoktu.

Çözüm: Tip adını string olarak almak yerine bir kayıt tablosu kullanmak:

C#
public static class Notifiers
{
    static readonly Dictionary<string, Func<INotifier>> Registry = new()
    {
        ["email"] = () => new EmailNotifier(),
        ["sms"] = () => new SmsNotifier(),
    };

    public static INotifier Create(string key) =>
        Registry.TryGetValue(key, out var create)
            ? create()
            : throw new ArgumentException($"Bilinmeyen bildirim türü: {key}");
}

Artık her tip kodda açıkça new ile oluşturuluyor. Derleyici hepsini görüyor, hiçbirini atmıyor. Bilinmeyen bir anahtar gelirse de anlaşılır bir hata alıyorum. Kod biraz daha uzun ama hangi tiplerin oluşturulabileceği artık tek bir yerde duruyor. Bence bu AOT olmasa da daha iyi bir tasarım.

IL2075: Tipin property'lerini okumak

Bir nesneyi metne çeviren küçük bir yardımcı:

C#
static string Describe(object value)
{
    var type = value.GetType();
    return string.Join(", ", type.GetProperties().Select(p => $"{p.Name}={p.GetValue(value)}"));
}

Uyarı:

Çıktı
warning IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties'
in call to 'System.Type.GetProperties()'. The return value of method 'System.Object.GetType()'
does not have matching annotations.

Bu bence dört uyarının en tehlikelisi, çünkü hata vermiyor. İlk denememde Describe(new Todo(2, "Kapak", true)) boş bir string döndürdü. Exception yok, log yok. Sadece yanlış sonuç.

value.GetType() çalışma anında hangi tipi döndüreceğini derleyiciye söylemiyor. Derleyici de Todo'nun property'lerine reflection ile erişileceğini bilmiyor, onların bilgisini atıyor.

Çözüm: Derleyiciye neye ihtiyacım olduğunu söylemek. Bunun için DynamicallyAccessedMembers özniteliği var:

C#
static string Describe<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(T value)
{
    return string.Join(", ", typeof(T).GetProperties().Select(p => $"{p.Name}={p.GetValue(value)}"));
}
Çıktı
'Id=2, Title=Kapak, Done=True'

Metodu generic yaptım ve T'ye "bu tipin public property'leri lazım" notunu düştüm. Artık Describe(todo) çağrıldığında derleyici Todo'nun property'lerini koruyor.

Bu denemede ilginç bir şey daha oldu. Düzeltilmiş metodu aynı projeye ekledikten sonra eski Describe da doğru sonuç vermeye başladı. Sebebi, yeni metot yüzünden Todo'nun property'lerinin artık korunması. Eski metodun uyarısı ise hâlâ duruyordu. Yani uyarı veren kod bugün tesadüfen çalışabiliyor, yarın alakasız bir değişiklikten sonra bozulabiliyor. Uyarıyı "test ettim, çalışıyor" diye geçmemek gerekiyor.

Uyarıyı susturmak ne zaman doğru?

Bazen reflection kullanan kodun güvenli olduğundan emin olursunuz. Örneğin gereken tipi başka bir yerde zaten açıkça kullanıyorsunuzdur. O zaman UnconditionalSuppressMessage ile uyarıyı kapatabilirsiniz:

C#
[UnconditionalSuppressMessage("Trimming", "IL2075",
    Justification = "Sadece Todo ile çağrılıyor, Todo'nun property'leri DescribeFixed ile korunuyor.")]

Ama bunu çok nadir yapıyorum. Yukarıdaki örnekte gördüğünüz gibi "çalışıyor" ile "her zaman çalışacak" aynı şey değil. Justification alanına da neden güvenli olduğunu açıkça yazıyorum. Bir gün başkası o kodu değiştirdiğinde ne varsaydığımı görsün.

Kütüphaneden gelen uyarılar

Uyarı sizin kodunuzdan değil, kullandığınız bir paketten geliyorsa şöyle görünüyor:

Çıktı
warning IL3053: Assembly 'Microsoft.EntityFrameworkCore' produced AOT analysis warnings.

Bunu kendi kodunuzda düzeltemezsiniz. Seçenekleriniz şunlar:

  • Paketin AOT uyumlu bir sürümü var mı diye bakmak. Paket sayfasında ya da belgelerinde "AOT compatible" ibaresi geçiyor mu, kontrol etmek.
  • Aynı işi gören AOT uyumlu başka bir paket kullanmak. Örneğin Newtonsoft.Json yerine kaynak üreticili System.Text.Json.
  • O projeyi Native AOT ile değil, ReadyToRun ile derlemek. Bu sitenin altyapısı Elevare'de MVC ve EF Core yüzünden bu yolu seçtim.

Kendi kütüphanenizi yazıyorsanız

Kütüphanenizin AOT uyumlu olup olmadığını görmek için proje dosyasına şunu ekleyin:

XML
<IsAotCompatible>true</IsAotCompatible>

Bu, AOT analizörlerini kütüphanenin kendi derlemesinde de çalıştırıyor. Kütüphaneyi kullanan birinin bu uyarılarla karşılaşmasından önce siz görmüş oluyorsunuz.

Özet

UyarıNe diyor?Bende ne oldu?Çözüm
IL2026 / IL3050Reflection ile JSONInvalidOperationExceptionJsonSerializerContext
IL2057Tip adı stringConstructor yok ya da tip nullKayıt tablosu, açık new
IL2075Property'lere reflectionHata yok, boş sonuçDynamicallyAccessedMembers
IL3053Paketin kendisi uyumsuzUygulama açılırken çöktüUyumlu paket ya da ReadyToRun

Sonuç

Native AOT uyarıları "belki sorun olur" demiyor, "derleyici burayı göremiyor" diyor. Benim denemelerimde dört uyarının biri exception ile, biri yanlış constructor hatasıyla, biri null ile, biri de hiçbir hata vermeden boş sonuçla karşıma çıktı. En tehlikelisi sonuncusuydu. O yüzden AOT ile yayınladığım projelerde hedefim sıfır uyarı. Bir uyarıyı gideremiyorsam o kodu ya değiştiriyorum ya da o projede Native AOT kullanmıyorum.