Recordlar Nerede Kullanılır Ve Classlar Yerine Kullanılabilirler mi ?
Selamlar,
Bu makaleyi, C# 9 ile baya bir zaman önce hayatımıza giren Recordların pek de kullanılmadığını görmem üzerine yazmaya karar verdim. Tam olarak kullanım alanları nerelerdir ve Class ‘dan farkları var mıdır ? Eğer Recordlar hakkında bilginiz yok ise şu makalemden başlamanızı tavsiye ederim.
Bilmek Kullanmak Degildir ! ―@coderbora
Class:
Aşağıda .Net’de yıllardır kullanılan klasik bir ürün sınıf yapısı görülmektedir. Propertyler, Constructor’da alınan parametreler ile setlenmektedir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Product { public int ProductID { get; set; } public string ProductName { get; set; } public decimal ProductPrice { get; set; } public int ProductQuantity { get; set; } public Product(int productID, string productName, decimal productPrice, int productQuantity) { ProductID = productID; ProductName = productName; ProductPrice = productPrice; ProductQuantity = productQuantity; } } |
Record:
Aynı durum Recordlarda, aşağıdaki gibi tek satır ile tanimlanabilmektedir.
1 |
public record ProductRecord(int ProductID, string ProductName, decimal ProductPrice, int ProductQuantity); |
Buradaki Class VS Record Esas Farklar Neler ?
A-) Mutability:
Aşağıda görüldüğü gibi Calss için atanan değerler sonradan değiştirilebiliyorken, record için atanan değerler sonradan değiştirilemez(Immutability). “productRecord” için geçerli “ProductQuntity” sonradan değiştiremez iken, “product” sınıfı için geçerli “ProductQuantity” değeri sonradan güncellenebilmiştir.
Gelelim esas soruya. Bunu nerde kullanabiliriz ? Değişmesini istemediğimiz yerlerde. Mesela :
Api Response:
Endpoint’den dönecek modeller için değişim istenmez. Hesaplanan ya da çekilen datanın geri dönülmesi istenir.
1 |
public record ServiceResult(bool isSuccess, string errorMessage, T Data) |
DTO (Data Transfer Object):
Farkli katmanlar arasında data aktarımı sırasında da data tutarliliğı çok önemlidir. DTO nesnelerinde, Immutable yapıları nedeni ile Recordlar kullanılabilir.
1 |
public record WareHouseDTO(int ProductID, decimal price, int quantity) |
Config Class:
Config dosyalarının, bir sınıfa maplendiği yapılarda, yine Recordlar Immutable yapısı ile çok mantıklı olacaktır.
1 |
public record RedisConfig(string connectionString, int expireTime, string password, int port) |
Gelelim Class Vs Record’da Value Equality’e:
Class ve Recordlarin değer eşitliği kavramı farklıdır. Sınıflar, eşitlikte referans adreslerine bakarken, recordlar referans adreslerinden bağımsız, doğrudan valuelarına bakarlar.
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 |
public class Program { public static void Main() { ShapeClass class1 = new() { Name = "Kare", Width = 5, Height = 6 }; ShapeClass class2 = new() { Name = "Kare", Width = 5, Height = 6 }; Console.WriteLine("'Class1 == Class2' : " + class1.Equals(class2)); ShapeRecord record1 = new ShapeRecord("Kare", 5, 6); ShapeRecord record2 = new ShapeRecord("Kare", 5, 6); Console.WriteLine("'Record1 == Record2' : " + record1.Equals(record2)); } } public class ShapeClass { public string Name { get; set; } public int Width { get; set; } public int Height { get; set; } } public record ShapeRecord(string Name, int Width, int Height); |
Yukarıdaki örnekde görüldüğü gibi iki class eşitliğinde, referansları farklı olduğu için “false” dönmüştür. Ama Recordlarda sadece değerlere bakıldığı için “true” dönmüştür.
Database Entity:
Database üzerinde değişen dataların karşılaştırılması için Recordlar, tam bir biçilmiş kaftandır. Örneğin data değişmiş ise Update, değişmemiş ise hiçbir işlem yapılmasın diyebiliriz.
Cache Keys:
Immutable olmasini istediğimiz konuların başında Cache’e atadığımız keyler vardır.
1 2 3 4 5 6 7 |
public record UserCacheKey { public string UserDetail = "User:{0}"; public string UserDetailByDb = "{0}:User:{1}"; public string AboneList = "Abone:List:{0}:{1}"; public string GetUserCompaniesById = "UserCompanies:{0}"; } |
Kullanışı:
1 2 |
UserCacheKey userCachekey = new(); Console.WriteLine(string.Format(userCachekey.AboneList, 13, 5)); |
Çıktı:
1 |
Abone:List:13:5 |
Audit, User veya Authorization Loglar:
Recordlar gene immutable yapıları ile değişmesini istemediğimiz logların, basit ve okunabilir bir şekilde kodlanmasını sağlarlar.
1 |
public record UserLog(DateTime Timestamp, string Controller, string Action, int UserID); |
Class Ve Recordlarda Clone:
Recordlarda “with” keyword’ü ile Shallow Copy işlemi yapılır yani, complex type propertyler için gene Deep Copy işlemi yapılması gerekmektedir. Recordlar’da clonelama işlemi with ile, sade okunabilir ve kolay bir şekilde aşağıda görüldüğü gibi yapılabilmektedir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using System; public record Iphone13ProMax(string ProductName,decimal Price,long IMEI ,bool IsNew,bool IsGuaranteed); public class Program { public static void Main() { Iphone13ProMax onUcProMax = new("Iphone13 Pro MAX", 70000, 38731680044413, true, false); var Iphone13Mini = onUcProMax with { ProductName = "Iphone13 Mini", IMEI = 398429384293846 }; Console.WriteLine(Iphone13Mini); } } |
Sonuç: “Iphone13ProMax { ProductName = Iphone13 Mini, Price = 70000, IMEI = 398429384293846, IsNew = True, IsGuaranteed = False }”
İsterseniz Classların içinde Recordları Kullanabilirsiniz:
Aşağıda görüldüğü gibi Program sınıfı altında “ProductRecord” tanımlanmıştır. Random olarak oluşturulan tüm Productlar, List of Record olarak geri dönülmektedir. Bu sayede, daha sade ve okunabilir bir koda sahip olunmaktadı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 |
using System; using System.Text; using System.Collections.Generic; public class Program { public static void Main() { foreach (var product in GenerateRandomProductList()) Console.WriteLine($"Name: {product.Name} Id:{product.ProductId} Price: {product.Price}"); } public static List<ProductRecord> GenerateRandomProductList() { Random random = new Random(); List<ProductRecord> products = new(); for (var i = 0; i < 10; i++) { products.Add(new ProductRecord(GenerateRandomString(6), random.Next(10000000, 99999999), (float)(random.NextDouble() * (1000 - 50) + 50))); } return products; } public record ProductRecord(string Name, long ProductId, float Price); static string GenerateRandomString(int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; StringBuilder randomString = new StringBuilder(); Random random = new Random(); for (int i = 0; i < length; i++) { randomString.Append(chars[random.Next(chars.Length)]); } return randomString.ToString(); } } |
Result:
Özellikle Prototype gibi oyun geliştirmede sıkça kullanılan Design Patternler için Recordlar çok pratik yapılardır. Prototype’ın özünde, birbirinin aynı ama sadece bazı alanları farklı nesneleri, tekrar tekrar yaratmak yerine clonelama ve farklı özellikleri tekrar atama mantığı vardı. İşte tam bu noktada Recordlar, bize büyük kolaylık sağlamaktadır.
Sonuç:
Recordlar özellikle immutable yapısı ile projede değişmesi istenmeyen yapılarda, ne kadar faydalı olduğunu hep birlikte gördük. Özellikle Cache Key, Audit Log ve Config sınıflarda Record kullanımı bize büyük kolaylıklar sağlayacaktır. Recordlarda, sınıflara göre nesnelerin eşitlik ilkesi referansları ile değil value değerlere göre bakıldığı için, özellikle Repository ya da servislerde, seçilecek operasyon tipinin belirlenmesinde kullanılabilirler. Servislerde Global dönülen Result tiplerinde, recordlar immutable özelliklerinden dolayı özellikle tercih edilebilirler. Clonelama gerektiren Prototype gibi farklı Design Patternlerinde veya operasyonlarda Recordların “with” sayesinde basitçe özelleştirilip kullanılması, kod okunaklıgını arttırması genel kullanım sebeplerinden biri olabilir.
Bu makalede aklıma gelmeyen ama karşılaştığımda sizlerle paylaşacağım recordlar ile alakalı daha birçok durum olabilir. Umarım Recordların hayatımıza kattığı kolaylıkları fark eder ve projelerinizde ona daha çok yer verirsiniz. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source:
- https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record
- https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/
- https://medium.com/@dotnetcode/exploring-the-differences-between-classes-and-records-in-c-and-when-to-use-each-07929b037b2e
Son Yorumlar