Derinlemesine Recordlar
Selamlar,
Bu makalede Recordlar nedir ve neye çözüm olmaktadır? Class’lara göre avantajları nelerdir ? Inheritance, Deep Copy, Immutable gibi farklı taraflardan inceleyip, bu sorulara cevap bulmaya çalışacağız.
Recordlar Nedir ? Ne amaçla kullanılmaktadırlar?
Recordların esas amacı, datanın özlü ve değişmezliğinin sağlanmasıdır. Eğere bir değişiklik gerekiyorsa yenisini yarat fikrinin esas amacı, oluşabilecek hataların önüne geçmek, riskleri minimize etmek ve güncelleme sırasında oluşabilecek bugları engellemektir. Bu yüzden recodlar immutable’dır. Yani immutable atanan değerler asla değişmezler. Ayrıca recordlar, sanıldığının aksine value type değil, referance type nesnelerdir. Classlara göre yeni gelen birçok ek özellikleri bulunmaktadır. Recordlar, equality yani eşitliğe, value yani değerlere bakarak kıyaslamakta, classlarda ise bu iş tamamen referans adresler üzerinden yürütülmektedir. Makalenin devamında bu konuya ayrıca değinilecektir. Son olarak propertyleri default olarak immutable değildir. Sizin tanımlamanız gerekmektedir.
Record ile Struct’ın Tam Olarak Farkı Nedir ?
- Recordlar referance type iken, structlar value typedir.
- Struct ve Recordlarda eşitlik karşılaştırması, valuelar üzerinden yapılırken, classlarda referancelar üzerinden yapılmaktadır.
- Function parametrelerinde, class ve recordlar referance tip olarak geçerken, structlar value tip olarak atanmaktadırlar.
- Inheritance’ı, class ve recordlar desteklerken, structlar desteklememektedir.
- “==” equlity check’i, Struct desteklemez. Class ve Record destekler.
Aşağıda görüldüğü gibi 3 eşlenik Record, Struct ve Class motorlar birbirleri ile karşılaştırılmaktadır. ReferanceEquals() her zaman Structlarda “False” değeri döndürürken, Recordlarda ve Classlarda nesenlerin referansları karşılaştırılır ve bu örnekde geriye “True” döndürülür.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
using System; namespace Record { public record RecordMotor { public string Name { get; set; } public decimal Price { get; set; } } public struct StructMotor { public string Name { get; set; } public decimal Price { get; set; } } public class ClassMotor { public string Name { get; set; } public decimal Price { get; set; } } class Program { static void Main(string[] args) { RecordMotor recordHonda1 = new() { Name = "Cbr650R", Price = 120000 }; var recordHonda2 = recordHonda1; StructMotor structKawa1 = new(){ Name = "ZX6R", Price = 150000 }; var structKawa2 = structKawa1; ClassMotor classSuzuki5 = new() { Name = "GSX-R600", Price = 170000 }; var classSuzuki6 = classSuzuki5; Console.WriteLine($"RecordHonda1 == RecordHonda2 => {ReferenceEquals(recordHonda1,recordHonda2)}"); Console.WriteLine($"StructKawa1 == StructKawa2 => {ReferenceEquals(structKawa1,structKawa2)}"); Console.WriteLine($"ClassSuzuki5 == ClassSuzuki6 => {ReferenceEquals(classSuzuki5, classSuzuki6)}"); } } } |
Sonuç:
With İle Clonelama:
Aşağıda görüldüğü gibi Iphone13Mini ürünü, onUcProMax’in “with” keyword’ü ile Shallow Copy‘si alınmıştır. ProductName ve IMEI değerleri değiştirilmiştir.
1 2 3 4 5 6 7 8 |
Iphone13ProMax onUcProMax = new("Iphone13 Pro MAX", 9912345, 38731680044413, true, false); var Iphone13Mini = onUcProMax with { ProductName = "Iphone13 Mini", IMEI = 398429384293846 }; Console.WriteLine(Iphone13Mini); |
Sonuç:
Class Vs Record :
Aşağıda görüldüğü gibi Class, ShallowCopy ile kopyalanmıştır. Recordın da, with ile eşleniği alınmıştır. Classda, classSuzuki1 ve classSuzuki2 sınıflarının referancelarına göre eşitliğine bakıldığı için, “==” geriye “False” değeri dönülmüştür. Record da ise, recordHonda1 ve recordHonda2 nesnelerinin valuelarına göre eşitliğine bakıldığı için, geriye “True” değeri dönülmüştür.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class ClassMotor { public string Name { get; set; } public decimal Price { get; set; } public ClassMotor ShallowCopy() { return this.MemberwiseClone() as ClassMotor; } } public record RecordMotor { public string Name { get; set; } public decimal Price { get; set; } } RecordMotor recordHonda1 = new() { Name = "Cbr650R", Price = 120000 }; var recordHonda3 = recordHonda1 with { Name = "Cbr650R" }; ClassMotor classSuzuki1 = new() { Name = "GSX-R600", Price = 170000 }; var classSuzuki2 = classSuzuki1.ShallowCopy(); Console.WriteLine($"ClassMotor1 == ClassMotor2 => {classSuzuki2 == classSuzuki1}"); Console.WriteLine($"recordHonda3 == recordHonda1 => {recordHonda3 == recordHonda1}"); |
Sonuç:
Nasıl gördüğünü değiştir, nasıl değiştiğini gör! ―Buddha
Struct Property init():
Aşağıdaki Record propertyleri “init” olarak tanımlarsanız, sonradan değerlerini değiştiremessiniz. Kısaca ReadOnly olurlar.
1 2 3 4 5 |
public record RecordImutabledMotor { public string Name { get; init; } public decimal Price { get; init; } } |
Aşağıda görüldüğü gibi “Price” property’sinin değişimine izin verilmemektedir.
Positional Records:
Aynı durum, Positional Record tipleri için de geçerlidir. Aşağıda görüldüğü gibi Constructorda tanımlanan propertyler, default olarak “init” olarak atanmışlardır. Yani sonradan değiştirilemezler.
1 2 |
public record RecordConstructorMotor(string Name, decimal Price); RecordConstructorMotor motor1 = new("Ducati Panigale V4 R", 565000); |
Deconstructor:
Aşağıdaki örnekde, Record eşitlikde tuple bir değer dönülmektedir. Böylece ara işlemlerde recordlar, büyük bir kolaylık sağlamaktadırlar.
1 2 3 4 |
RecordConstructorMotor motor1 = new("Ducati Panigale V4 R", 565000); var (name,price)=motor1; Console.WriteLine($"Name:{name}"); Console.WriteLine($"Price:{price}"); |
Her değişim daima başka değişimlere ihtiyaç gösterir. ―Niccola Machieavelli
Inheritence:
Recordlar yanlızca başka bir record’dan Inheritance alabilirler. Bir class, record’dan inheritance alamaz. Aşağıda, bir Product record’u oluşturulmuştur. Iphone, bu Product’dan türetilmiştir. Son olarak Iphone13ProMax’de, Iphone’dan türetilerek aynı zamanda Product’dan da türetilmesi sağlanmıştır.
1 2 3 |
public record Product(string ProductName, int seriNo); public record Iphone(string ProductName, int seriNo, long IMEI) : Product(ProductName, seriNo); public record Iphone13ProMax(string ProductName, int seriNo, long IMEI, bool isProRAW, bool isProRes) : Iphone(ProductName, seriNo, IMEI); |
Örnek kullanım şekli:
1 2 3 4 5 6 7 |
Product product = new("GamePad", 123456); Iphone iphone = new("IphoneXS MAX", 6612456, 446888114967890); Iphone13ProMax onUcProMax = new("Iphone13 Pro MAX", 9912345, 38731680044413, true, false); Console.WriteLine(product); Console.WriteLine(iphone); Console.WriteLine(onUcProMax); |
Sonuç:
Örnek 2 Positional Recordlarda Inheritance:
1 2 3 4 5 6 7 8 |
public record RecordConstructorMotor(string Name, decimal Price); public record HondaMotor(string Name, decimal Price, int Speed) : RecordConstructorMotor(Name, Price); static void Main(string[] args) { HondaMotor hondaMotor=new("CBR650R",130000,240); Console.WriteLine($"Honda'nın Max Hızı:{hondaMotor.Speed}"); } |
Sonuç:
“Rüzgarın yönünü değiştiremiyorsanız, yelkenlerinizi değiştirin.” ―Max de Pree
With İle Clonelanan Record Değişir ise Root Record da Değişir mi?
Propertyleri Primitive typelardan oluşan bir Record, değişmez. Ama ya Record’un propertylerinden biri, gene Record ise! Yani complex bir type ise!
Aşağıda görüldüğü gibi, Person recordunu bir property’si de Scholl record’udur. Yani Person record’una, Complex tipde bir property atanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 |
record Scholl() { public string Name { get; set; } public int No { get; set; } } record Person() { public string Name { get; set; } public string Surname { get; set; } public int No { get; set; } public Scholl Scholl { get; set; } } |
Aşağıda görüldüğü gibi Person bora, “Hurriyet Koleji” adında bir okul ile tanımlanmıştır. Ayrıca jordan record’u “with” keyword’ü ile bora’dan “Shallow Copy” şeklinde clonelanmıştır.
jordan’ın Scholl.Name değiştiği zaman, root’daki boranın da okul ismi değişmektedir. Çünkü “with” keyword’ü ile Primitive tipler clonelanırken, Scholl record gibi Complex tipleri clonelanamayıp, aynı referance adresini göstermektedir. Bu nedenle jordan’ı ait Scholl recordunun bilgileri değiştirilir ise ,aynı referance adresinin gösterildiği bora’nın da School bilgileri değişir.
Bu durumun yaşanmaması için, complex tipli Recordlarda, DeepCopy yönetemine gidilip, “with” keywordünden uzak durulması gerekmektedir.
1 2 3 4 5 6 7 8 9 10 11 |
Person bora = new Person(); bora.Name = "Bora"; bora.Surname = "Kasmer"; bora.No = 1923; bora.Scholl = new Scholl() { Name = "Hurriyet Koleji", No = 23 }; var jordan = bora with { Name = "Jordan" }; jordan.Scholl.Name = "Yükselen Koleji"; Console.WriteLine($"Name: {bora.Name}"); Console.WriteLine($"Scholl: {bora.Scholl.Name}"); |
Sonuç:
Record ile Genericler:
Aşağıda görüldüğü gibi, “Product” record’undan türeyen 2 Iphone record aşağıdaki gibi tanımlanmıştır.
1 2 3 |
public record Product(string ProductName, int seriNo, double price); public record Iphone(string ProductName, int seriNo, long IMEI, double price) : Product(ProductName, seriNo, price); public record Iphone13ProMax(string ProductName, int seriNo, long IMEI, bool isProRAW, bool isProRes, double price) : Iphone(ProductName, seriNo, IMEI, price); |
Calculate<T> Generic Class: Aşağıda görüldüğü gibi, Calculate sınıfı “Product” record tipinde bir nesne beklemektedir. TotalCost() methodu bu “T” tipinde bir nesne’yi parametre olarak beklemektedir. Ve geriye double tipinde bir değer döndürmektedir.
1 2 3 4 5 6 7 |
public class Calculate<T> where T : Product { public double TotalCost(T motor) { return motor.price * 1.01 * 1.1 * 1.5 * 1.18 + 3531; } } |
Main(string[] args): Aşağıda görüldüğü gibi Calculate class’ına “phone“‘dan türüyen, “IPhone13ProMax“sınıfı atanmış ve parametre olarak Claculate() sınıfına gönderilmiştir. Son olarak TotalCost() methodu ile satış fiyatı ekrana basılmıştır. Böylece Recordlar, Generic classlarda rahatlıkla kullanılabilmişlerdir.
1 2 3 |
Iphone13ProMax onUcProMax = new("Iphone13 Pro MAX", 9912345, 38731680044413, true, false, 8883); Calculate<Iphone13ProMax> phone = new(); Console.WriteLine($"Iphone 13 Pro Max(500GB) Total Price: {phone.TotalCost(onUcProMax)} ₺"); |
Sonuç:
Bu makalede Recodların Immutable özelliğinden bahsettik. Belki asenkron programlamada Race Condition’a düşmemek için, Recordların Immutable özelliğinden faydalanabiliriz. Sonuçta locklamaya gerek kalmadan, kullanımı deneyebiliriz. Hızlıca “with” ile clonelaması data kopyalamada, ve constructor’a ihtiyaç duymadan propertylerin set edilebilmesi, bize kod geliştirmede büyük bir kullanım kolaylığı sağlamaktadır. Ayrıca data eşitliğinde referance yerine değer tipine bakması, bazı bussines logiclerde çokça işimize yarıyacak gibi gözüküyor.
Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Kaynaklar:
- https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record
- https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0
- https://www.c-sharpcorner.com/article/deep-dive-into-records-in-c-sharp-9/
- https://anthonygiretti.com/2020/06/19/introducing-c-9-questions-answers-about-records/
- https://www.claudiobernasconi.ch/2020/11/07/csharp-9-record-types-introduction-and-deep-dive/
- https://hoven.in/cs-lang/records-in-cs9.html
Thanks!