Menu

C# Reflection ve Öznitelikler: typeof, GetType ve Özel Öznitelikler

C#'ta reflection nasıl çalışır: typeof ve GetType'tan Type nesneleri, property okumak ve ayarlamak, metotları adıyla çağırmak, örnek oluşturmak ve öznitelikler: Obsolete gibi yerleşik olanlar, kendinizinkini bildirmek ve onları çalışma zamanında okumak. Ayrıca reflection'ın maliyeti.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Reflection, bir programın çalışırken tipleri incelemesini sağlar: bir sınıfın hangi property'lere sahip olduğu, değerlerinin ne olduğu, hangi metotların var olduğu, hangi özniteliklerin eklendiği. Öznitelikler ise diğer yarıdır: [Obsolete] ya da [JsonPropertyName("id")] gibi, derleyici ya da bir kod onları reflection ile okuyana kadar kendi başlarına hiçbir şey ifade etmeyen, köşeli parantez içindeki bildirimsel etiketler. Serializer'lar, ORM'ler, doğrulama kütüphaneleri, test framework'leri ve ASP.NET routing hepsi böyle çalışır.

Çoğu uygulama kodu nadiren doğrudan reflection'a ihtiyaç duyar, ama nasıl çalıştığını bilmek framework davranışlarının çoğunu açıklar.

Type nesneleri: typeof ve GetType

Her şey bir System.Type ile başlar. Bir tane almanın iki yolu vardır:

Çıktı:

Employee
Manager
Employee
False
True
True
System.Int32
False
Name

Hatırlanacak fark: typeof(X) kodu yazarken bildiğiniz bir tipi adlandırır, obj.GetType() ise bir nesneye gerçekte ne olduğunu sorar. GetType() == typeof(Employee) karşılaştırması türetilmiş sınıflar için başarısız olan tam bir eşleşmedir; bu genellikle istediğiniz şey değildir; is ve IsAssignableFrom kalıtıma uyar. nameof benzer görünür ama hiç reflection değildir: derleyici onu bir string sabitiyle değiştirir.

null bir referans üzerinde GetType() NullReferenceException fırlatır, çünkü sorulacak bir nesne yoktur. Box'lanmış nullable bir değer tipinde alttaki tipi döndürür: ((int?)5).GetType() System.Int32'dir.

Property okumak ve ayarlamak

GetProperties() bir tipin public property'lerini, her biri belirli bir örnek üzerinde değeri okuyup yazabilen PropertyInfo nesneleri olarak listeler:

Çıktı:

Name   String   = Mug
Price  Decimal  = 8.50
Stock  Int32    = 12
7.90
True
SUP-77

Bunun gösterdiği üç şey:

  • GetValue object döndürür, bu yüzden değer tipleri box'lanmış olarak geri gelir ve onları kullanmak için cast edersiniz.
  • Var olmayan bir adla GetProperty null döndürür ve onun üzerindeki sonraki çağrı NullReferenceException fırlatır. Kullanmadan önce kontrol edin.
  • BindingFlags.NonPublic | BindingFlags.Instance private üyelere ulaşır. Bu araçlarda ve testlerde meşrudur, ama kapsüllemeyi atlar ve sınıf yeniden düzenlendiğinde sessizce bozulur.

Bu döngü esasen bir CSV dışa aktarıcının ya da nesneden JSON'a bir serializer'ın çalışma biçimidir: property'leri dolaş, her değeri oku, biçimlendir.

Metotları çağırmak ve nesneleri adıyla oluşturmak

GetMethod bir metot bulur ve Invoke onu bir argüman dizisiyle çağırır. Activator.CreateInstance bir Type'tan bir nesne oluşturur; eklenti sistemleri ve DI container'ları çalışma zamanında seçilen tipleri böyle oluşturur:

Çıktı:

60.00
Decimal WithTax(1 parameters)
String Describe(0 parameters)
True

DeclaredOnly listeyi sınıfın kendisinde bildirilen üyelerle sınırlar; o olmadan GetMethods object'ten gelen ToString, Equals, GetHashCode ve GetType'ı da döndürür. Type.GetType("Name") namespace ile nitelenmiş adı, başka assembly'lerdeki tipler için assembly adını da gerektirir ("MyApp.Plugins.Csv, MyApp.Plugins").

Çağrılan metot istisna fırlatırsa Invoke istisnayı bir TargetInvocationException içine sarar; orijinali onun InnerException'ındadır.

Öznitelikler: derleyicinin ve framework'lerin okuduğu etiketler

Bir öznitelik tanımladığı şeyden önce köşeli parantez içinde yazılır. Framework birçoğunu tanımlar; derleyicinin yaptığını değiştiren birkaçı:

public class OrderService
{
    [Obsolete("Use PlaceOrderAsync instead.")]
    public void PlaceOrder(Order order) { }
    // Every call site: warning CS0618: 'OrderService.PlaceOrder(Order)' is obsolete: 'Use PlaceOrderAsync instead.'
    // [Obsolete("...", true)] makes it error CS0619 instead.

    [Conditional("DEBUG")]
    public void Trace(string message) => Console.WriteLine(message);
    // Calls to Trace are removed entirely from builds without the DEBUG symbol.
}

[Flags] enum Channels { None = 0, Email = 1, Sms = 2 }   // changes how ToString formats combinations
[Serializable] class Snapshot { }                     // marks a type for legacy binary serialization

Diğerleri çalışma zamanında kütüphaneler tarafından okunur: System.Text.Json tarafından [JsonPropertyName] ve [JsonIgnore], ASP.NET Core model doğrulaması ve Entity Framework tarafından [Required] ve [MaxLength], ASP.NET routing tarafından [HttpGet("orders/{id}")], test çalıştırıcıları tarafından [Fact] ve [Test]. Özniteliğin kendisi hiçbir şey yapmaz; onu arayan kod yapar.

ObsoleteAttribute adı uygulandığında [Obsolete]'e kısalır: gelenek olarak her öznitelik sınıfı adı Attribute ile biter ve C# son eki atmanıza izin verir.

Özel bir öznitelik bildirmek ve okumak

Özel bir öznitelik, Attribute'tan türeyen bir sınıftır. [AttributeUsage] neye uygulanabileceğini söyler. Constructor parametreleri konumsal argümanlara, public ayarlanabilir property'ler ise adlandırılmış argümanlara dönüşür:

Çıktı:

Username must be at most 20 characters
Email is required
Keep the city code short
0

Bu, ASP.NET Core model doğrulamasının System.ComponentModel.DataAnnotations ile yaptığının minyatür bir sürümüdür. Öznitelik argümanları derleme zamanı sabitleri olmalıdır (sayılar, string'ler, typeof(...), enum değerleri ya da bunlardan oluşan diziler), çünkü assembly'nin meta verisinde saklanırlar. GetCustomAttribute<T>() System.Reflection içindeki bir extension metottur; yalnızca bir özniteliğin olup olmadığını bilmeniz gerektiğinde IsDefined(typeof(T)) da vardır.

Reflection'ın maliyeti

Reflection esneklik karşılığında hızdan ve güvenlikten ödün verir:

  • Hız. Bir üyeyi adıyla aramak ve onu Invoke ya da GetValue üzerinden çağırmak doğrudan bir çağrıdan çok daha yavaştır ve değer tiplerini box'lar. Tekrarlanan kullanım için PropertyInfo'yu ya da MethodInfo'yu bir kez arayıp saklayın ya da onu Delegate.CreateDelegate veya MethodInfo.CreateDelegate ile bir delegate'e çevirip onu çağırın.
  • Güvenlik. Yanlış yazılmış bir ad ya da değişmiş bir imza sorunsuz derlenir ve çalışma zamanında başarısız olur. Yeniden adlandırmalar yakalansın diye mümkün olan her yerde "Price" string'i yerine nameof(Product.Price)'ı tercih edin.
  • Trimming ve AOT. Trim edilmiş ve Native AOT uygulamaları hiçbir şeyin kullanmıyor göründüğü üyeleri kaldırır ve reflection kullanımı bu analizden gizler. Modern kütüphaneler (System.Text.Json, GeneratedRegex, log yazma) aynı işi derleme zamanında yapan source generator'lara geçiyor.

Reflection'ı programın tiplerini gerçekten önceden bilmeyen kısımları için kullanın: eklentiler, generic araçlar, serializer'lar, test yardımcıları. Tipler bilindiğinde sıradan kod, generics ya da interface'ler daha hızlıdır ve derleyici tarafından kontrol edilir.

Yaygın hatalar

  • Bir temel sınıfı test etmek için GetType() == typeof(Base). Türetilmiş tipler için başarısız olur. is ya da IsAssignableFrom kullanın.
  • null kontrolü yapmamak. GetProperty, GetMethod ve Type.GetType hiçbir şey eşleşmediğinde null döndürür.
  • Önbelleğe almadan sıcak bir döngüde reflection. MemberInfo'yu önbelleğe alın ya da bir delegate derleyin.
  • Invoke'tan yanlış istisnayı yakalamak. Gerçek istisna TargetInvocationException'ın InnerException'ıdır.
  • Üye adları için sihirli string'ler. nameof kullanın.

Sıkça Sorulan Sorular

C#'ta reflection nedir?

Reflection, bir programın tipleri çalışma zamanında incelemesidir: bir sınıfın property'lerini ve metotlarını listelemek, değerleri adıyla okuyup ayarlamak, metotları çağırmak, örnek oluşturmak ve öznitelikleri okumak. System.Reflection içinde yaşar ve bir Type nesnesiyle başlar. Serializer'lar, ORM'ler, dependency injection container'ları ve test framework'leri onun üzerine kuruludur.

C#'ta typeof ile GetType arasındaki fark nedir?

typeof(Customer) derleme zamanında bir tip adından çözülür ve nesne gerektirmez. obj.GetType() ise çalışma zamanında bir örnek üzerinde çağrılır ve nesnenin gerçek tipini döndürür; bu, değişkenin bildirilen tipinden daha türetilmiş olabilir: Animal a = new Dog(); için a.GetType() Dog'dur. Null bir referans üzerinde GetType() NullReferenceException fırlatır.

C#'ta bir property değeri adıyla nasıl alınır?

obj.GetType().GetProperty("Price") bir PropertyInfo döndürür (böyle public bir property yoksa null) ve .GetValue(obj) onu bir object olarak okur. .SetValue(obj, value) onu yazar. Bunu bir döngüde yapıyorsanız PropertyInfo'yu önbelleğe alın, çünkü pahalı kısım aramadır.

C#'ta özel bir öznitelik nasıl oluşturulur?

System.Attribute'tan türeyen bir sınıf bildirin, onu Attribute son ekiyle adlandırın ve nerede kullanılabileceğini [AttributeUsage] ile işaretleyin: [AttributeUsage(AttributeTargets.Property)] class MaxLengthAttribute : Attribute { public int Length { get; } public MaxLengthAttribute(int length) { Length = length; } }. Onu [MaxLength(50)] olarak uygulayın ve property.GetCustomAttribute<MaxLengthAttribute>() ile okuyun.

C#'ta Obsolete özniteliği ne yapar?

Bir üyedeki [Obsolete("Use PlaceOrderAsync instead")] derleyicinin her çağrı yerinde mesajınızla birlikte CS0618 uyarısı vermesini sağlar. [Obsolete("...", true)] uyarıyı CS0619 hatasına çevirir. Kütüphaneler bir API'yi çağıranları bir gecede bozmadan böyle emekliye ayırır.

C#'ta reflection yavaş mı?

Doğrudan bir çağrıyla karşılaştırıldığında evet: bir üyeyi adıyla bulup MethodInfo.Invoke ya da PropertyInfo.GetValue üzerinden çağırmak genellikle onlarca ila yüzlerce kat daha yavaştır ve değer tiplerini box'lar. Başlangıç, yapılandırma ve ara sıra kullanım için sorun değildir. Sıcak yollar için MemberInfo'yu önbelleğe alın, bir kez bir delegate oluşturun ya da bunun yerine generics ya da bir source generator kullanın.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA