10 Mayıs 2023 Çarşamba

OOP | Records

Record Yapısını Anlamak İçin Ön Hazırlık: Init-Only Properties

🔹Init-Only Prop., C# 9.0 ile gelen, aslında record'dan bağımsız bir yapılanmadır.
🔹Herhangi bir nesnenin propertylerine ilk değerlerinin verilmesi ve sonraki süreçte bu değerlerin değiştirilmemesini garanti altına almayı sağlayan bir özelliktir. Yani bu özellik, elimizdeki nesnenin herhangi bir property'sini read-only hale getirir.
🔹Bu özellik sayesinde nesnenin sadece oluşturulma anında propertylerine değer atanır, böylece iş kuralları gereği runtime'da değeri değişmemesi gereken nesneler için önlem alınmış olunur. 
Elimizdeki herhangi bir property'de set bloğunu kaldırdığımızda read-only olur. Bu durumda;

    public int MyProperty { get; } = 3;

şeklinde ilk değerini verebiliriz. Ancak bulunduğu sınıftan nesne oluştururken object initializer yapılanmasında ilgili property değerini veremeyiz. Yani;

    MyClass my = new MyClass {
        MyProperty yazdığımızda gelmediğini görürüz. Çünkü set bloğunu kapattık ve şu an read-only.
    };

Bu eksik init-only properties ile kapatıldı. Çünkü init-only prop. hem elimizdeki nesneyi read-only yapar, hem de initializer'da ilk değeri vermemizi sağlar. Ama bunun için zaten getter-only-properties (read-only) yok muydu? ⤦


Yukarıdaki görselde olduğu gibi tanımlanan propertyler sadece get olduklarından dolayı ya tanımlandıkları anda ya da sadece constructor'dan değer alabilir.

📌Öyleyse init-only prop. ile getter-only prop. arasında nasıl bir fark var? : Aralarındaki temel fark Object Initializer  dediğimiz oluşum sürecinden kaynaklanır. Orada ilk değer atanabiliyorsa init-only prop., atanamıyorsa getter-only. Kısacası ikisi de read-only ama birinde ilk değer atanabiliyor. Örneklendirelim: 

Elimizde ilk örnekteki yapıda başka bir Book class'ı var. Bu class'ın propertylerine değer atamaya çalıştığımızda hata alıyoruz. Demek ki bunlar getter-only prop.


Ancak biz bunlara değer atadığımız halde hala read-only davranış sergilemesini istiyorsak init-only prop. olmaları gerekir.


Init-Only Properties Tanımlama

🔹Init-Only prop. özelliği için 'init' keyword'ü kullanılır. Aşağıdaki örneğe göz atalım:


    get-set bloklarından sadece get bloğu var. => salt okunur
    init keyword'ü var. => ilk değeri atamak mümkün. 

📌Buradaki get-init yapılanması auto property initializer olarak da geçer.

Ancak bir kere verilen değer daha sonra değiştirilemiyor:


🔹Getter-only prop. ile çalışmaktansa read-only bir field üzerinde işlem yapılması gerekiyorsa 'init' aşağıdaki örnekte olduğu gibi kullanılır.


Bu örnekte nesne ayağa kaldırılırken mevcut fieldları kapsülleyen propertyler üzerinden ilk değerlerin init ile verildiğini görüyoruz. Burada init bir nevi set bloğu görevini -bir kereye mahsus- görüyor.

📌Özet: Init-only prop. özelliği, nesne üretim esnasının dışında değişmez değerler oluşturulması için constructor ve auto property initializers yapısının yanında object initializer yapısının kullanılabilir olmasını sağlar. 


Record Nedir?

🔹Recordlar değer türlü nesneler değildir, referans türlü yapılardır.
🔹Odak noktası nesne mi yoksa değer mi, diye sorgulandığında değer ön plana çıkıyorsa ama nesnede çalışılıyorsa record kavramı devreye girer. 

📌Bir nesnenin yalnızca tek property'sinde sabitlik/salt okunurluk amaçlanıyorsa init only kullanılır demiştik (1). Ancak nesnenin bütünsel olarak değişmezliği amaçlanıyorsa daha fazlasına ihtiyaç vardır. Bu durumda nesnenin tüm propertyleri init olarak ayarlanır (2).

🔹Record, bir property'nin değişmezliğinden ziyade nesnenin genel değerinin değişmezliğine odaklıdır.
🔹Record, bir nesnenin bütün olarak sabit kalmasını garanti altına alır. Böylece nesne, artık değeri değişmeyeceğinden, aslında nesneden ziyade değer olarak bakabileceğimiz bir yapıya dönüşür.

📌Nesne ön plandaysa bu bir class, data/değer ön plandaysa bu bir record'dır.

🔹Buradan yola çıkarak recordları içerisinde data barındıran lightweight classlar olarak düşünebiliriz (nesneden çok değeri ön planda tuttuğu için).
🔹Recordlar classlara istinaden nesneden ziyade içerisinde bulunan dataları sabitleyerek dataları öne çıkarır.
🔹Recordlar bir class'tır -sadece nesnelerinden ziyade değerleri ön plana çıkaran bir class.

👉🏼Classlarda verisel olarak nesne ön plandadır ve farklı bir referansa sahip olan nesne, farklı değer olarak algılanır. Dolayısıyla Equals(x,y) karşılaştırması yanlıştır.

Örnek: Yukarıdaki gibi bir araba class'ı düşünelim. Araba class'ından üretilen iki instance da araba instance'ıdır, ancak ikisi de fiziksel anlamda farklı değerler olarak değerlendirilir.


👉🏼Recordlarda ise verisel olarak değer ön plandadır. Sadece nesnel olarak bu veriler bir nesnede tutulmakta ama değiştirilmemektedir. Haliyle farklı nesnelerde de olsa veriler/property değerleri aynı olduğu sürece Equals(x, y) önermesi doğru olacaktır.

Örnek: Yine bir araba class'ı ve iki araba instance'ı olsun. Bunlar yine farklı instancelar ama asıl önemli olan içlerindeki değerlerdir. Taşıdıkları değerler birebir aynı ise farklı birer nesne de olsalar aynı gibi/eşit değerlendirilirler. => Equals karşılaştırması true döner.


📌Her iki türde de veriler objede tutulur ama recordlar classlara nazaran bir nesneden ziyade bütünsel olarak veri imajında olacak şekilde spesifik bir davranış sergiler.


Record Tanımlama

🔹Sıradan bir class ile benzer bir yapılanmaya sahiptir, zaten kendisi de bir classtır. Önemli kısım şu: Değeri ön planda tutmak için propertylerin hepsi init yapılmalı. 


Örnekte olduğu şekilde tanımlanan recordlara Norminal Records denilir.


Record ile Class Arasında Fark Kritiği

Recordlar değiştirilemez nesneler oluşturmamızı sağlar, demiştik. Peki bu değiştirilemez nesneleri classlar ile gerçekleştiremez miyiz?


Init-only zaten record'dan bağımsız bir özellik, yani sıradan classlar ile de kullanılabilir. Ama değerin ön planda tutulacağı durumlarda record kullanmak daha mantıklı olacaktır. 

Ayrıca -record kullanmadığımız takdirde- nesnenin süreçte herhangi bir property değerini değiştirmek istediğimizde yeni bir Employee nesnesi üretmemiz ve değişikliğin yapılacağı property dışındaki propertyleri bu nesneden eşleştirmemiz gerekecektir. Örnek:


Değişmeyecek olan propertylerin hepsinin değerlerini bir önceki nesneden almamız ve değişecek Position property değerini manuel olarak vermemiz gerekti. Bu yöntem çok sayıda property'ye sahip olunduğu durumlarda iş yükü olacaktır. İşte bunu önlemek için record sayesinde ilgili sınıftaki nesneyi çoğaltarak işlem yapmak mantıklı olacaktır. Bu senaryoda da record kullansaydık bu tarz kopyalama durumlarında with Expression'dan faydalanıp istenilen özelliği hızlıca değiştirebilirdik.


With Expressions

🔹Nesneyi hızlıca klonlamak istediğimizde ve bunun için records ile çalıştığımızda with Expression'dan faydalanabiliyoruz.
🔹Immutable/Sabit türlerde çalışırken nesne üzerinde değişiklik yapabilmek için ilgili nesneyi ya çoğaltmamız/klonlamamız (deep copy) ve üzerinde değişiklik yapmamız gerekir, ya da manuel şekilde yeni bir nesne üreterek mevcut nesnedeki değerleri, istenilen değişikliği yansıtacak şekilde aktarmamız gerekir. 

with kullanımı örneği:



Başka bir örnek daha verelim. Düz class ile uğraşmak yerine record ile with kullanırsak istediğimiz property üzerinde hızlı bir şekilde değişikliğe gidebiliriz:

0 yorum:

Yorum Gönder