C#, Java gibi Nesne Yönelimli Programlama Dillerinde nesneler ve değişkenler bellek yönetimi açısından farklı davranış sergilerler. Kullanımları her ne kadar basit olarak görsekte uygulama testlerinde aldığımız C# dahilinde “NullReferenceException”, veya Java dahilinde “NullPointerException” türevinden hatalar en sık karşılaştıklarımız arasındadır. “NullReferenceException” hatası çözümü, uygulama nesnelerinin bellek yönetimi açısından düşünülerek cevaplanması gereken bir yöntemdir. Peki nedir bu “Null” olma durumu ve “NullReferenceException” hatasını neden alırız.
Bu soruyu yanıtlamadan önce C# Değer ve Referans Tipi Bellek Yönetimi yazısını okumanızı öneririm.
Bir sınıf türü değişkeni “new” anahtar kelimesi ile örneklemediğinizde nesne “null” durumda bulunacaktır.
Nesnenin kullanılabilmesi için mutlaka “new” anahtar kelimesi ile örneklendirilmiş olması gerekmektedir.
Null durumda bırakılmış bir nesneyi kulanmaya çalışmak demek “NullRefrenceException” hatasını alacağınız anlamına gelmektedir. Daha iyi anlamak için basit bir örnekle inceleyelim;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Program { static void Main(string[] args) { Kitap k1; k1.Baslik="Kitabım" } } public class Kitap { public string Baslik { get; set; } public List<string> yazar { get; set; } } |
Yukarıdaki senaryo incelendiğinde k1 nesnesinin Kitap tipinde tanımlandığı ancak new anahtar kelimesi ile örneklenmediği görülmektedir. Bu bir sorun değil. Böyle bir durumda k1 nesnesi null değerine sahip olacaktır. Ancak sonraki adımda k1 nesnesinin “Baslik” isimli özelliğine erişim yapılmıştır ki işte bu nota “NullRefrenceException” hatasını beraberinde fırlatacaktır.
Şimdi bu hatayı alabileceğimiz farklı uygulama senaryolarına bir gözatalım;
- Bir nesneye bilinçli olarak null değer atama;
1234string konu= null;konu.ToUpper(); - “new” anahtar kelimesi kullanmadan nesne’yi kullanmaya çalışmak;
123456789101112131415class Program{static void Main(string[] args){Kitap k1;string baslik = k1.Baslik;}}public class Kitap{public string Baslik { get; set; }} - İç içe geçmiş sınıflarda yapıcı metodlarda (constructor) uygun başlatımların yapılmaması;
12345678910111213141516171819class Program{static void Main(string[] args){Kitap b1 = new Kitap();int authorAge = b1.Yazar.Yas;}public class Kisi{public int Yas { get; set; }}public class Kitap{public Kisi Yazar { get; set; }}}
Uygulama tarafında (Main Metodu) Kitap sınıfı “new” anahtar kelimesi ile örneklenmiş ve üyesi olan Yazar nesnesine erişim yapılmıştır. Ancak Yazar nesnesi için herhangi bir başlatıcı (constructor) bulunmamaktadır. Yazar özelliği Kisi sınıf tipinde olup kendi uyesi Yas özelliğine başvuru yapılması sonucu “NullReferenceException” hatasını fırlatacaktır.
Sorunun çözümü için Kitap sınıfında bir başlatıcı metod yazımı uygun olacaktır.12345678910public class Kitap{public Kisi Yazar { get; set; }public Kitap() {Yazar = new Kisi();}} - Sınıf değişkenlerinin kapsam alanlarında örneklenmemiş olmaları;
1234567891011121314public class Form1 {private Musteri musteri;private void Form1_Load(object sender, EventArgs e) {Musteri musteri= new Musteri();musteri.Ad= "Murat";}private void Button_Click(object sender, EventArgs e) {MessageBox.Show(musteri.Ad);}}
“Form1_Load()” metodu altında musteri nesnesi örneklenmiş gibi görünsede “Button_Click()” metodu altında Ad özelliğine başvurusu yapılan musteri sınıf değişkeni global alanda tanımlanmış olana işaret eder. Global alanda tanımlanmış musteri değişkeni null durumdadır.
- ASP.NET sayfalarının yaşam döngülerinden kaynaklanabilir.
1234567891011121314151617181920public partial class Form_Edit : System.Web.UI.Page{protected Test myTest;protected void Page_Load(object sender, EventArgs e){if (!IsPostBack){// Only called on first load, not when button clickedmyTest = new Test();}}protected void SaveButton_Click(object sender, EventArgs e){myTest.Deger= "NullReferenceException hatası!";}}
Yukarıdaki ASP.NET WebForm örnek uygulamasında sayfanın ilk yüklemesinden (Page_Load()) doğan IsPostBack kontrolü ile myTest sınıf değişkeni örneklemesi yapılmaktadır. Ancak aynı oturum sayfası yenilendiğinde (refresh) if bloğuna giremeyecek ve değişken örneklemesi ikinci kez yapılamayacaktır. ASP.NET yaşam döngüsünden kaynaklı olarak ikinci kez oluşturulan sınıf değişkeni “null” durumunda kalacaktır. Görüldüğü üzere SaveButton_click() metodunda bu nesnenin .deger üyesine başvuru yapılmıştır. Sonuçta ilk uygulama çalıştığı anda herşey normal ilerlerken sayfa yenilenir ve SaveButton_Click() yöntemi çağırılırsa uygulama “NullReferenceException” hatasını fırlatacaktır. Bu gibi bir senaryoda Session nesnelerini kullanmanız hata deneti
123string Deger= Session["Deger"].ToString(); - WPF kontrollerinin oluşturma sıralamasından kaynaklı hatalar;
WPF denetimleri XAML ile deklare edilen sıralamada kontroller hafızadaki yerlerini alırlar. Bir kontrol yüklenmeden işletmeye alınması “NullReferenceException” hatasını fırlatacaktır. Örneğin;123456789101112131415161718<Grid><!-- Combobox ilk sırada bildirilmiştir --><ComboBox Name="comboBox1"Margin="10"SelectedIndex="0"SelectionChanged="comboBox1_SelectionChanged"><ComboBoxItem Content="Item 1" /><ComboBoxItem Content="Item 2" /><ComboBoxItem Content="Item 3" /></ComboBox><!-- Label sonraki sırada bildirilmiştir. --><Label Name="label1"Content="Label"Margin="10" /></Grid>123456private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e){label1.Content = comboBox1.SelectedIndex.ToString(); // NullReference hatası!!}Combobox ilk sırada Label kontrolünden önce oluşturulmuştur. Ancak Combobox’ın SelectionChanged olayı ile Label kontrolüne başvuru programatik yönden yapılmıştır. Yükleme esnasında Combobox kontrolü öğelerini oluştururken bu olayı tetikleyecektir. Bu esnada Label kontrolü daha oluşturulmamıştır. Bu noktada “NullReferenceException” hatasını fırlatır.
Çözümü ise basit olarak tasarım kaygılarını görmezden gelip XAML tarafında Label kontrolünü Combobox’tan önce bildirimi yapılmış olmalıdır.
- LINQ Deyimlerinde Single(), First() metodlarında dönen değer olmadığında null durumda kalırlar. Bunun yerine OrDefault sürümlerini kullanın. Single() yerine SingleOrDefault(), First() yerine FirstOrDefault() tercih edilmelidir.
NullReferenceException Hatasının Olası Çözümleri
- Nesnenin bazen null değere sahip olabileceği potansiyelini görebilirsiniz. Nesnenin üyelerine erişmeden önce “null” değer taşıyor mu diye kontrol edin;
1234567void Yazdir(Kisi k) {if (k != null) {Console.WriteLine(k.Ad);}} - Yazdığınız metodlarınız da geri dönüş değerinin null olabilme ihtimali doğabilir. Örneğin aranan değer bulunamadığında geriye null yerine varsayılan bir değer döndürmeyi tercih ediniz.
1234567string KategoriVer(Kitap k) {if (k == null)return "Tanımsız";return k.Category;} - Dinamik yolla oluşturduğunuz (var) değişkenlerinizi açıkça kontrol ediniz. Hata olma ihtimallerine karşı, kontrollü özel istisnaları fırlatabilirsiniz.
12345678string KategoriVer(string kitapAd){var kitap= kutuphane.FindBook(kitapAd); // !!Geriye boş değer dönebilir!!if (kitap== null)throw new KitapBulunamadiException(kitapAd); // Özel istisnareturn kitap.Kategori;} - Null değer döndürebilme ihtimali olan düz kullanımlı metodları çağırmak yerine onların OrDefault sürümlerini kullanmaya çalışın. Örneğin tarihsel (DateTime) türünde değişkenden değer okumak için kullanılan “GetValue()” yerine “GetValueOrDefault()” yöntemini kullanmak daha uygun bir çözüm olacaktır.
1234567DateTime? tarih= null;Console.WriteLine(tarih.GetValueOrDefault(DateTime.Now));tarih= new DateTime(2022, 10, 20);Console.WriteLine(tarih.GetValueOrDefault(DateTime.Now)); - Ternary operatörleri ile null değer kontrolü yapınız. Eğer null değer taşıyacaksa, varsayılan değerler atayın. Aşağıdaki örnekleri inceleyiniz.
123456789101112131415// C#5.0 ve altındaki versiyonlar içinvar title = person.Title == null ? null : person.Title.ToUpper(); //C# 5.0//C# 6.0 versiyonundan sonravar title = person.Title?.ToUpper(); // C# 6.0// tipik null kontrolüint uzunluk= 0;if (baslik!= null)uzunluk= baslik.Length;// `?` ve `??` operator kullanımıint uzunluk= baslik?.Length ?? 0;