C# 10 Record Struct
Selamlar,
C# 9.0 ile hayatımıza giren recordlar, aynı classlar gibi davranıp aynı zamanda birçok ekstra özelliği barındırmaktadırlar. En önemli özellikleri immutabale, yani “init-only” property’si ile değişmez olmalarıdır. Böylece kullanım alanlarına göre, hata riskini minimum’a indirmektedirler. Recordlar üzerine daha detaylı bilgiye, önceki makalemden ulaşabilirsiniz.
Bugünkü konuşacağımız konu, aşağıda görüldüğü gibi structlar’ın record olarak tanımlanması ile kazandıkları yeni kabiliyetlerdir.
1 |
public record struct Person {} |
Global dünyada C#’da bolca kullanılan, ama iş hayatında Code Review yaptığım kodlarda nerde ise hiç göremediğim “Structlar”‘ı, isterseniz önce kısaca bir hatırlayalım.
Bilindiği gibi Classlar referance typedır ve “Heap” bellekte saklanırlar. Ama structlar value type ve “Stack” bellekde saklanırlar. Heap bölgesindeki bir nesneye erişmek, ayrıca bir zaman ve fazladan bellek kullanımına neden olacağından, içinde az sayıda eleman içeren ve sadece bir veri tipine sahip değişkenleri sınıflar yerine structlarda tanımlamak, performans ve verimlilik açısından çok daha faydalıdır. Bana göre structların en büyük eksiği Inheritance’ı desteklememesidir. Ve en büyük artısı da Stack bellekde saklandıkları için, GC’a ihtiyaç duyulmamalarıdır. İşleri bitince stack bellekden, usulca silinirler. Bu nedenle de, IDisposable yani Deconstructor() methodlar structlar için yasaklanmıştır.
Aynı classlarda olduğu gibi istenir ise new() anahtar sözcüğü ile Constructor çağırlabilir. Ama classlardan farklı olarak aşağıda görüldüğü gibi new() keyword’ü kullanılmadan da çağrılabilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// See https://aka.ms/new-console-template for more information People po; po.No = 1; po.Name = "Bora"; Console.WriteLine($"Po No:{po.No}"); Console.WriteLine($"Po Name:{po.Name}"); public struct People { public string Name, Surname; public int No; } |
Ya da istenir ise structlar, “new()” keyword ile de aşağıdaki gibi kullanılabilirler. Tek önemli husus, yaratılma anında tüm değişkenlerin atanması gerektiğidir.
1 2 3 4 5 6 7 8 9 10 11 12 |
People bora = new People("Bora", "Kasmer", 1923); public struct People { public string Name, Surname; public int No; public People(string name, string surname, int no) { Name = name; Surname = surname; No = no; } } |
Aşağıdaki kullanımda, “People.No” atanmadığı için, uygulama derlenmemektedir.
Structlarda hiç değer atanmasa bile “new()” keyword’ü ile kullanılmaları durumunda, default değerler atanmış olur. Aşağıda görüldüğü gibi bora People struct’ına hiç değer atanmadığı halde “new()” keyword’ü ile tanımlandığı için, ekrana No “0” olarak yazdırılmıştır.
1 2 3 4 5 6 7 |
People bora = new People(); Console.WriteLine($"Bora No:{bora.No}"); public struct People { public string Name, Surname; public int No; } |
Structlar value type olduklarından dolayı classların aksine, bir method’a parametre olarak atanıp değiştirildiklerinde, kendileri değişmezler. Aynı durum, classlar için geçerli değildir. Classlar, methodlara parametre olarak Referance tipi olarak atandıkları için, method içi parametre değişiminde kendileri de değişirler. Aşağıdaki örnekde, kullanım ve ekran çıktısı gözükmektedir.
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 |
PeopleStruct bora = new PeopleStruct("Bora", "Kasmer", 1923); PeopleClass duru = new PeopleClass("Duru", "Kasmer", 1938); ChangeName(bora,duru); Console.WriteLine($"Bora Name:{bora.Name} - Surname : {bora.Surname}"); Console.WriteLine($"Duru Name:{duru.Name} - Surname : {duru.Surname}"); void ChangeName(PeopleStruct person,PeopleClass person2) { person.Name = "Bill"; person.Surname = "Gates"; person2.Name = "Elon"; person2.Surname = "Musk"; } public struct PeopleStruct { public string Name, Surname; public int No; public PeopleStruct(string name, string surname, int no) { Name = name; Surname = surname; No = no; } } public class PeopleClass { public string Name, Surname; public int No; public PeopleStruct(string name, string surname, int no) { Name = name; Surname = surname; No = no; } } |
Kısaca Structlar:
- GC’a ihtiyaç duymamaları ve böylece çok daha az sistem tüketmeleri.
- Stack’de saklandıkları için, erişimleri ve propertylerinin kopyalanma hızları sınıflara göre çok daha yüksek ve performanslı olmasından dolayı, içinde fazla propertysi olmayan, primitive typeların kullanıldığı, inheritance gerektirmeyen yapılarda örnek ViewModellerde şiddetle tercih edilmelidirler.
Şimdi kısaca Structları hatırladığımıza göre, gelin başlarına “Record” eklediğimiz zaman neler değişiyor hep beraber inceleyelim:
Immutability:
Aşağıda görüldüğü gibi, recordlardan gelen “init” keyword’ü ile tanımlı propertyler, birkere atandıktan sonra değiştirilememktedirler. “Norman.Name“‘in, Constructor’da bir kere atandıktan sonra, tekrardan “Bora” olarak değiştirilmesine izin verilmemektedir.
“Recordların” aksine “record structlar“‘da, “positional records”lar farklı davranmaktadır. Aşağıda görüldüğü gibi :
- “Recordlarda” Constructorda tanımlı değişkenler, “init” yani immutable’dır. Kısaca birkere atandıktan sonra, tekrardan değiştirilemezler. Örneğin “Elon” bir record nesnedir. Name, yaratılma anında Constructorda tanımlandığı için, sonrasında Elon.Name=”Bora”‘ya izin verilmemektedir.
- “record struct“‘da durum, biraz farklıdır. Aşağıdaki örnekde Norman record struct bir nesnedir. Constructorda Name,Surname ve No alanı tanımlanmış olmasına rağmen, immutable değillerdir. Ve sonradan da değiştirilebilmektedirler.
Not: “record structların” immutable olması istenir ise, tanımlamada başına readonly koymak yeterlidir.. Örneği aşağıdadır..
1 |
public readonly record struct PeopleRC(string Name, string Surname, int No); |
With Keyword:
Aşağıda görüldüğü gibi, “record struct” şeklinde tanımlama ile “with” keyword’ü kullanılarak “shallow copy” işlemi yapılabilmektedir. Yani bora nesnesi, Norman nesnesinin sadece adını değiştirilerek, bir kopyası şeklinde oluşturulmuştur.
1 2 3 4 5 6 |
PeopleRecordStruct Norman = new PeopleRecordStruct("Norman", "Reedus", 1969); var bora = Norman with { Name = "Bora" }; Console.WriteLine($"Bora => Name:{bora.Name} Surname: {bora.Surname} No: {bora.No}"); public record struct PeopleRecordStruct(string Name, string Surname, int No); |
İşin ilginç yani, strcutlar da artık “with” keyword’ü ile shallow copy işlemine tabi tutulabilmektedirler. Yani structlar artık “with”‘e destek vermektedir. Kısaca “with” keyword’ü, “record struct“‘a özel değildir.
Structlar artık başlarında record tanımlamaya gerek duymadan “with“‘e destek vermektedir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
PeopleStruct Norman = new PeopleStruct("Norman", "Reedus", 1969); var bora = Norman with { Name = "Bora" }; Console.WriteLine($"Bora => Name:{bora.Name} Surname: {bora.Surname} No: {bora.No}"); public struct PeopleStruct { public string Name { get; init; } public string Surname { get; init; } public int No { get; set; } public PeopleStruct(string name, string surname, int no) { Name = name; Surname = surname; No = no; } } |
Equality eşitlik karşılaştırması:
Aşağıda görüldüğü gibi structlar, “==” veya “!=” eşitliğine destek vermemekte, ve daha kodlama anında aşağıda görülen hatayı vermektedir.
Ama “==” veya “!=” eşitliğine “record struct“lar, aşağıda görüldüğü gibi destek vermektedir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
PeopleRecordStruct bora = new PeopleRecordStruct("Bora", "Kasmer", 1923); PeopleRecordStruct bora2 = new PeopleRecordStruct("Bora", "Kasmer", 1923); Console.WriteLine($"bora - bora2'ye eşit mi : {bora == bora2}"); public record struct PeopleRecordStruct { public string Name { get; init; } public string Surname { get; init; } public int No { get; set; } public PeopleRecordStruct(string name, string surname, int no) { Name = name; Surname = surname; No = no; } } |
Print Members:
“record struct”larda, “ToString()” methodu override edilmiştir. Normal “structlar” çıktı olarak bize pek birşey vermez iken, “record structlar” bize struct’ın tüm string halini vermektedir. Aşağıdaki örnekde, “struct” ve “record struct” iki nesne ekrana yazdırılmış ve aradaki fark, bariz bir şekilde görülmüştür.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
PeopleRecordStruct recordStructUser = new PeopleRecordStruct("Bora", "Kasmer", 1923); PeopleStruct structUser = new PeopleStruct("Bill", "Gates", 1881); Console.WriteLine($"recordStructUser : {recordStructUser}"); Console.WriteLine($"structUser : {structUser}"); public record struct PeopleRecordStruct(string Name, string Surname, int No); public struct PeopleStruct { public string Name { get; init; } public string Surname { get; init; } public int No { get; set; } public PeopleStruct(string name, string surname, int no) { Name = name; Surname = surname; No = no; } } |
Record Structlar, bence C# 10 ile gelen ama hak ettiği değeri pek de görmeyeceğini düşündüğüm önemli yeniliklerden sadece biri. Özellikle, kaynaklarda da belirttiğim (nietras.com)’daki Benchmark testleri gösteriyor ki, “record struct”‘lar => “record”lardan en az x20 kat daha performanslıdır. Tabi bu farkın hissedilebilmesi için, her yerde class tanımlamak yerine, gerçekten gerektiği yerlerde “record struct” lara da bir şansın verilmesi gerekmektedir.
Unutmayın gerçek performans , küçük parçaların verimleri toplamıdır.
Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source :
teşekkürler çok güzel bir makaleydi