C# 8.0 – Nullable Reference Types

Merhaba,

Dün dünde kaldı, yarınlar ise şekilleniyorken biz şimdinin sınırları içerisinde aslında sade ve sadece basit ve tek kare bir anı yaşıyoruz şu fani dünyada. Her şey değişiyor. Bu değişimden en büyük atılımları gelişebilenler sağlıyor, diğerleri ise ya nasibini bekliyor yahut rüzgarda savrulan yaprak misali beyhude süreci seyr ediyor… Sanat, müzik, fizik vs. tümü bu gün düne nazaran farkını ortaya koyuyor ve yarın daha da iyi olabilmek için değişiyor, gelişiyor. Biz bu pencereden yazılım dünyasına bakarsak eğer düne kadar sineye çekilen büyük bir gediği yarınların ehemmiyeti ve bu değişim girdabındaki en verimli gelişimsel devrimi sağlayabilmek için cesur bir adımla ortadan kaldırmaya çalışan yeni bir özellik geliştiriliyor. C# 8.0 Nullable Reference Types özelliği…

Her zaman vurguladığım gibi, gelen her bir yenilik esasında olmayanı oldurması için değil, olması gerektiği içindir. .NET geliştiricileri bu güne kadar referans türlü değişkenler için null değerinin karşılanabilir olmasından dolayı tip güvenliğinde yaratılan anlamsız boşluğu ve lüzumsuz koda sebebiyet verilmesi durumunu C# 8.0 ile gelen en radikal ve büyük özelliği olan Nullable Reference Types sayesinde kontrol altına almaya karar vermiştir.

Peki bu özellik hangi sorunu ortadan kaldırmaya gelmiştir?
OOP temelli birçok programlama dilinde(Keza C# dışındakilerden %100 emin değilim 🙂) referans türlü tanımlanan tüm değişkenler ‘NULL’ ile işaretlenebilmektedirler. Bu özellik tanımlanabilirlik açısından doğası gereği bir fiiliyatı simgeleyen -nullanabilirlik yahut -null’lamak gibisinden halk dilinde geliştirilen bir jargon ile ifade edilmektedir diyebiliriz. Yapısal olarak geliştirilen uygulamalarda -null’lanmış bir referansı kullanmada önce null kontrolü yapma zarureti doğuran bu durum modern kodlama standarlarına pekte çağdaş olmayan bir yaklaşım sergilememizi gerektirmektedir.

Düşünsenize;

            void Do(bool state)
            {
                string key = state ? "abc" : null;
                if (key != null)
                    Console.WriteLine(key);
            }

şeklinde yazılan bir kodu ele alalım. Sizce 4. satırda yapılan kontrol ne kadar doğaldır? Bu alenen redundancy(gereğinden fazla) bir satır değil midir? Evet, bu güne kadar bu kod olması gereken ve böylece zoraki de olsa doğal olmak mecburiyetinde olan bir kontroldü. Lakin C# geliştiricileri, 8.0 versiyonunda gelen nullable reference types özelliği ile referans türlü değişken dahi olsa “NULL” olabilecek referansları önceden tanımlamamızı sağlayarak buradaki kodun elzemliğini daha da kontrol edilebilir bir hale getirmiş bulunmaktadırlar. Bu özellik sayesinde “NULL” almayacak bir değişkenin “NULL” alması durumu söz konusu olduğu taktirde ilgili developer compiler tarafından uyarılmakta ve böylece hem lüzumsuz kodlardan ve koşullardan kodumuzu arındırmış hemde null durumlarının ortaya çıkarabileceği handikaplar dolaylıda olsa engellenmiş olmaktadır.

null durumlarının ortaya çıkarabildiği handikaplar neler olabilir?
Burada kastedilen genel olarak; Java prog. dilinde “null pointer exception” şeklinde geçen ve C#’ta “object reference not set to an instance of an object” şeklinde karşılığı olan ve genel olarak “null reference exception” diye bilinen hatalardır. null referans objelerin söz konusu olduğu durumlarda alınan bu hatalara istinaden bilgisayar biliminin kurucularından Tony Hoare -billion dollar mistake- tabirini kullanmaktadır. Üstat, özellikle belirtilmediği sürece hiçbir referansın nullable olmaması gerektiğini vurgulamakta ve aksi taktirde alınan hatalardan dolayı ortaya çıkan maliyetin dünya ekonomisine bilançosu milyar dolar olarak yansıdığını ifade etmektedir. Keza onlarca kurumsal yahut bireysel projede şahit olduğum üzere ve piyasada en çok şikayet edilen hatalar olması hasebiyle null durumlarıyla ilişkili exception hatalarına dair tecrübeme dayanaraktan üstadın bu yorumunu oldukça haklı buluyor ve nacizane olarak katıldığımı ifade etmek istiyorum.

Nullable Reference Types

Ön Hazırlıklar

Bir uygulamada Nullable Reference Types özelliğini kullanabilmek için öncelikle ilgili projede C# 8.0’ın kullanıldığından emin olunması gerekmektedir. Bunun için ilgili projeye Solution Explorer penceresinden sağ tıklayarak “Edit Project File” sekmesine gelerek açılan sayfaya <LangVersion>8.0</LangVersion> kodunun yerleştirilmesi gerekmektedir.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <LangVersion>8.0</LangVersion>
  </PropertyGroup>
</Project>

Bu işlemin ardından ya uygulama/proje seviyesinde yahut dosya seviyesinde Nullable Reference Types özelliğini etkinleştirebiliriz.

  • Proje düzeyinde etkinleştirmek için;
    Yine aynı sayfaya aşağıdaki gibi <Nullable>enable</Nullable> komutunun eklenmesi yeterlidir.

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp3.1</TargetFramework>
        <LangVersion>8.0</LangVersion>
        <Nullable>enable</Nullable>
      </PropertyGroup>
    </Project>
    
  • Dosya düzeyinde etkinleştirmek için;
    Nullable reference types özelliğinin kullanılacağı dosyada #nullable enable komutunun yazılması yeterlidir. Eğer ki, ilgili özelliğin pasifleştirilip tekrar aktifleştirilmesini istiyorsak aşağıdaki gibi kullanılabilir.

        class Program
        {
    #nullable enable
            static void Main(string[] args)
            {
                //Active
            }
    #nullable disable
            static void Metot2()
            {
                //Passive
            }
    #nullable enable
            static void Metot3()
            {
                //Active
            }
        }
    

    Görüldüğü üzere bu opsiyonalite oldukça faydalı bir niteliktedir.

Temel Örnekler

Nullable Reference Types özelliğinin aktif olarak kullanıldığı bir projede artık referans türlü değişkenler varsayılan olarak null değer alamamaktadırlar. Bunun için ilgili referansa, ? operatörü ile işaretleyerek nullable özelliğinin kazandırılması gerekmektedir.

            MyClass m1 = null; //Converting null literal or possible null value to non - nullable type.
            MyClass? m2 = null; //Ok
            string s1 = null; //Converting null literal or possible null value to non - nullable type.
            string? s2 = null; //Ok

Görüldüğü üzere “m1” ve “s1” referansları null değer aldığı taktirde compiler uyarmakta lakin “m2” ve “s2” referanslarında herhangi bir uyarı söz konusu olmamaktadır. Farklı bir perspektiften durumu değerlendirebilmek kaydıyla aşağıdaki metotlarıda inceleyebilirsiniz;

    class Program
    {
        static void Main(string[] args)
        {
            CannotBeNull(null); //Cannot convert null literal to non - nullable reference type.

            string? s1 = null;
            CannotBeNull(s1); //Possible null reference argument for parameter 'value' in ...

            string? s2 = "test";
            CannotBeNull(s2); //Ok

            string s3 = "test";
            CannotBeNull(s3); //Ok

            MayBeNull(null); //Ok

            string? s4 = null;
            MayBeNull(s4); //Ok

            string? s5 = "test";
            MayBeNull(s5); //Ok

            string s6 = "test";
            MayBeNull(s6); //Ok

        }

        static void CannotBeNull(string value) => _ = value.Length; //Ok
        static void MayBeNull(string? value) => _ = value.Length; //Dereference of a possibly null reference
    }

Nullable Reference Types hataları sadece developerı bilgilendirmek ve yazdığı koda uygun kontratı hatırlatmak içindir. Lakin derleme hatalarına girmedikleri için olası NullReferenceException hatalarından arındırılmış değildir.

Nullable Özellikte Olup Null Olmayan Değerleri Garantiye Almak

Null-Forgiving Operator(Null Affedici Operatörü) – !

Bazen nullable özellikte olup değeri null olmayan değişkenlerimiz olabilir. İşte bu değişkenler üzerinde çalışırken nasıl ki bizler null olmadığına eminsek compilerında bundan emin olmasını sağlayabilir ve hata vermesini engelleyebiliriz. Nihayetinde compiler bu durumu anlayabilmek için yeterince akıllı olmadığından dolayı bu işlemi bir operasyonel bağlam ile ele almak zorundayız. İşte bunun için null-forgiving(!) operatörünü kullanacağız.

Null-Forgiving Kullanmazken Null-Forgiving Kullanırken
C# 8.0 - Nullable Reference Types C# 8.0 - Nullable Reference Types
Yukarıda mukayesesi yapılan ekran görüntülerine göz atarsanız eğer “GetValue” isimli metot aldığı parametre değerine göre geriye ya string bir value yahut da null değerlerini döndürecektir. Görüldüğü üzere parametreye verilen “true” değeri sayesinde kesinlikle geriye bir değer dönecek ve bu değer üzerinde bir işlem yapıldığı taktirde compilerın hata vermemesi ve bizim gibi durumdan haberdar edilmesi için kesinlikle null bir durumun söz konusu olmadığını !(null-forgiving) operatörü ile bildirmekte ve nihayetinde uyarıların kaldırıldığını görmekteyiz.

Yeri gelmişken null-forgiving(!) operatörüne dair daha detaylı bir izahette bulunabilmek için aşağıdaki kod bloğundaki örneğide incelemenizi tavsiye ederim.

            string s1 = null; //Converting null literal or possible null value to non - nullable type.
            string s2 = null!; //Ok
            string s3 = default!; //Ok

Preconditions Attributes(Ön Koşul Nitelikler)

AllowNull

Bir propertye null değer set edildiği taktirde null yerine boş bir değer döndürebilmesi için aşağıdaki gibi çalışabiliriz.
C# 8.0 - Nullable Reference Types
Görüldüğü üzere “Value” propertysi “?” operatörü ile nullable yapıldığı halde içerisinde her daim null dışında bir değer döndürmektedir. Lakin 10. satırda verilen null değerine karşılık 11. satırdaki hamle yapıldığı taktirde görseldeki uyarı verilmektedir.

Bu duruma istinaden bizde property’de ki nullable özelliğini kaldıralım ve birde öyle çalışalım.
C# 8.0 - Nullable Reference Types
Hoopbala! Bu şekilde bir çalışma sergilediğimizde de “Value” propertysinin null değer alamayacağı ifade edilmektedir.

İşte bu tarz aşağı tükürsem sakal, yukarı tükürsem bıyık durumlarında “AllowNull” attributeunu kullanarak hem işimizi görebilir, hemde compilerı rahatlatabiliriz.
C# 8.0 - Nullable Reference Types

Nullable olmayan bir referans “AllowNull” ile null değer karşılayabilir bir özellik kazanmaktadır.

DisallowNull

Yukarıdaki “AllowNull” senaryosunun tam tersi durumunda ise “DisallowNull” attributeunu kullanabiliriz. Yani nullable olan bir propertye yinede null değer girilmemesine dair bir uyarıda bulunulmasını istiyorsanız, kullanabilirsiniz.
C# 8.0 - Nullable Reference Types
Yukarıdaki görseli incelerseniz eğer; nullable olan “Value” propertysi “DisallowNull” attribute ile işaretlenerek null değer verilmemesine dair uyarı verdirilmekte ve böylece işaretlenen işlemde alınabilecek olası hatanın önüne derleyici aşamasında geçilmeye çalışılmaktadır.

“DisallowNull” ile nullable olan referanslara null değer atanması yasaklanır.

AllowNull/DisallowNull Attributelarının ref Parametrelerinde Kullanımı

Aşağıdaki görsel olarak yayınlanmış durumları mukayese ediniz;

AllowNull
C# 8.0 - Nullable Reference Types C# 8.0 - Nullable Reference Types
Görüldüğü üzere ref parametresinde AllowNull ile null bir değer alınabileceğini bildirmiş bulunmaktayız. Aksi taktirde developer uyarılmaktadır.
DisallowNull
C# 8.0 - Nullable Reference Types C# 8.0 - Nullable Reference Types
Ref parametresinin nullable olma durumundan dolayı, null değerinin verilmesi durumunda olası null exception hatalarının önüne geçilmesi için ilgili parametre DisallowNull ile işaretlenmiştir.

Post-Condition Attributes(Koşul Sonrası Nitelikler)

MaybeNull

Bir metot geriye nullable değer döndürmese dahi “null” dönebileceğini bildirmek için kullanılır.
C# 8.0 - Nullable Reference Types

“MaybeNull” ile geri dönüş değeri nullable olmayan metotlarda geriye null değeri döndürülebilir.

NotNull

Kullanmazken Kullanırken
C# 8.0 - Nullable Reference Types C# 8.0 - Nullable Reference Types
Yukarıdaki ekran görüntülerinden de anlaşılacağı üzere geri dönüş değeri nullable olan bir metodun null değer döndürmeyeceğini belirtir.

“NotNull” ile geri dönüş değeri nullable olan metotlarda geriye null değerinin döndürülmesi yasaklanmakta/engellenmektedir.

Conditional Post-Conditions Attributes(Koşullu Nitelikler)

NotNullWhen

Bir metodun geri dönüş değerine göre parametresinin null olup olmayacağını belirtmek için kullanılır.
C# 8.0 - Nullable Reference Types
Yukarıdaki örneğin out parametre üzerinden verilmesi ilgili attributen metoda dair diğer tüm parametre çeşitleri için kullanılmayacağı anlamına gelmemektedir!

“NotNullWhen” ile bir metodun geri dönüş değerine göre herhangi bir parametrenin null olup olmayacağı belirlenir.

MayBeNullWhen

“NotNullWhen” attribute’una nazaran, nullable olmayan bir parametrenin metodun boolean tipteki dönüş değerine göre null olup olmayacağını belirlemektedir.
C# 8.0 - Nullable Reference Types
Yukarıdaki kod bloğunu incelerseniz eğer, “Metot” isimli metodun geri dönüş değeri false olduğu taktirde “value” isimli nullable olmayan parametre için yinede null değer alabileceğini bildirmiş bulunmaktayız.

“MayBeNullWhen” ile bir metodun nullable olmayan parametresinin, metodunun boolean tipte geri dönüş değerine göre null olup olmayacağını belirler.

NotNullIfNotNull

Bir metodun geri dönüş değerinin null olup olmamasını herhangi bir parametresinin değerine bağlayan attribute’dur.

        [return: NotNullIfNotNull(parameterName: "param1")]
        static string? Metot(string? param1, string? param2)
        {
            return null;
        }

Yukarıdaki kod bloğunu incelerseniz eğer, “param1” isimli parametrenin değeri null olduğu taktirde ilgili metot geriye null değer döndürebilir. Aksi taktirde döndüremez.

Flow Attributes(Akış Nitelikler)

DoesNotReturn

Bir metodun geri dönüş değerinin olmadığını ifade eder.

        [DoesNotReturn]
        static void NotReturn(string value)
        {
            throw new ArgumentNullException(value);
        }

DoesNotReturnIf

Bir metodun boolean tipteki parametresindeki değere göre geriye bir şey döndürüp döndürmeyeceğini ifade eder.

Bunu aşağıdaki gibi manevratik algoritmalar için kullanabiliriz;
C# 8.0 - Nullable Reference Types

“condition” parametresi “false” ise ilgili “NotReturnIf” isimli metodun geriye bir değer döndürmeyeceğini ifade etmektedir. Dolayısıyla “Main” metodu içerisinde “value” isimli değişken “NotReturnIf” isimli metot içerisinde kontrol edilmekte ve false olduğu taktirde ilgili metot tarafından hata fırlatılacağı kesin olarak bilinmektedir. Böylece “_ = value.length” kodundaki olası hata derleyici tarafından göz ardı edilebilmektedir.

Generic Constraint(notnull)

Bir Generic yapılanmada generic parametrenin null olmamasını istiyorsak eğer notnull constraintini aşağıdaki gibi uygulamamız yeterlidir.
C# 8.0 - Nullable Reference Types

Ayriyetten küçük bir parantez olarak “notnull” constraintine örnek olarak genel kültür mahiyetinde “Dictionary” sınıfını verebiliriz;

public class Dictionary<TKey, [NullableAttribute(2)] TValue> : ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable, IDictionary<TKey, TValue>, IReadOnlyCollection<KeyValuePair<TKey, TValue>>, IReadOnlyDictionary<TKey, TValue>, ICollection, IDictionary, IDeserializationCallback, ISerializable where TKey : notnull
{
   ....
}

Evet, böylece C# programlama dilinin 8.0 versiyonunda en heyecan verici gelişmelerden biri olan Nullable Reference Types özelliğini incelemiş bulunmaktayız. Muhtemeldir ki, içeriğimizin ilk paragraflarında bahsedilen redundancy kodları developera yapmış olduğu yardım neticesinde azaltacağı ve tip güvenliğine(type safety) daha anlamlı bir yeni boyut kazandıracağı gözükmektedir. Hayırlısı diyelim 🙂

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*