Logo
Ana Sayfa
ÖZEL KODLAMA DERSLERİ
Yazılım Özel Ders
Tüm birebir programlara genel bakış
Python Yazılım Kursu
Sıfırdan ileri seviyeye birebir Python
Java Yazılım Kursu
OOP odaklı birebir Java eğitimi
AP Computer Science Principles
AP CSP sınav hazırlığı
GRUP DERSLERİ
Python & Django Masterclass
SINIRLI KONTENJAN
Java & Spring Boot Masterclass
SINIRLI KONTENJAN
C# .NET Masterclass
SINIRLI KONTENJAN
VİDEO DERSLER
Sıfırdan Temel Python Kursu
Kendi Hızında Öğren
ÜCRETSİZ
Seviye Testi
Ücretsiz — Python, Java, algoritma seviye testleri
Kariyerini Keşfet
Sertifika Doğrula
Belge numarası ve soyad ile doğrulama

C#'ta Record Türü ve Init-Only Property ile Immutable Veri Modelleme

Yazar: Berk Keskin 29.08.2026 ~11 dk okuma 2 Okunma
csharp-record-init-only-property-immutable-veri-modelleme

Record türü, C#'ta veri taşımak amacıyla tanımlanan ve değerlerine göre eşitlik karşılaştırması yapan bir referans türüdür; klasik bir class'ta her seferinde elle yazılması gereken constructor, Equals, GetHashCode ve ToString gibi tekrar eden kodu ortadan kaldırır. Init-only property ise bu türlerin nesne oluşturulduktan sonra değiştirilememesini, yani immutable veri modelleme yapılmasını sağlayan property türüdür. İkisi bir araya geldiğinde, hem yazımı kısa hem de mantıksal olarak güvenli veri modelleri kurmak mümkün hale gelir.

Record Türü Nedir ve Class'ın Hangi Sorununu Çözer?

C#'ta bir veri modelini klasik class ile tanımladığınızda, genellikle aynı işlemi tekrar tekrar yazarsınız: bir constructor yazıp parametreleri property'lere atarsınız, ardından iki nesnenin içerik olarak aynı olup olmadığını anlamak için Equals ve GetHashCode metotlarını ezersiniz, son olarak nesneyi okunabilir biçimde yazdırmak için ToString ekleyip dururursunuz. Basit bir Adres veya Siparis sınıfında bile bu kod tekrarı hızla büyür ve asıl iş mantığını gölgeler.

Record türü, tam olarak bu tekrarı azaltmak için tasarlanmış bir sözdizimsel kısayoldur. Bir sınıfı record anahtar kelimesiyle tanımladığınızda derleyici, üye değerlerine dayalı eşitlik karşılaştırması, okunabilir bir ToString çıktısı ve immutable kullanım için uygun bir yapı otomatik olarak üretir. Bu noktada altı çizilmesi gereken temel ayrım şudur: klasik bir referans türünde iki nesne, bellekte aynı adresi gösteriyorsa eşit kabul edilir; buna referans eşitliği (reference equality) denir. Record'larda ise karşılaştırma nesnenin kimliğine değil, taşıdığı verilere bakar; buna value-based equality denir ve bu, record'ları class'tan ayıran en temel özelliktir.

Bu ayrımın neden önemli olduğunu anlamak, nesne yönelimli programlamanın temel kavramlarını (kalıtım, kapsülleme, eşitlik semantiği) sağlam kurmaktan geçer. Bu temeller yalnızca C#'a özgü değildir; aynı mantık farklı dillerde farklı sözdizimleriyle karşınıza çıkar. Nesne yönelimli düşünme alışkanlığını dil bağımsız biçimde pekiştirmek isteyenler, birebir Java dersleri kapsamında bu kavramları başka bir dilin bakış açısıyla da görebilir. Record türü, sonuç olarak yeni bir kavram icat etmez; var olan class kavramını, veri taşıma senaryolarında daha az kod ve daha net bir niyetle ifade etmenin bir yolunu sunar.

Positional Syntax ve Nominal Syntax Arasındaki Fark

Record tanımlarken iki farklı yazım biçimi kullanılabilir. Birincisi positional record olarak adlandırılan kısa yazımdır: record Person(string Ad, int Yas); şeklinde tek satırda hem property'ler hem de constructor parametreleri tanımlanmış olur. Bu yazım, hızlıca bir DTO (veri taşıma nesnesi) veya basit bir değer nesnesi oluşturmak istediğinizde en az kod yazmanızı sağlar.

İkinci yaklaşım ise nominal record olarak bilinen, property tabanlı yazımdır. Bu biçimde property'ler açıkça tanımlanır ve her biri { get; init; } erişimcileriyle işaretlenir:

public record Person
{
    public string Ad { get; init; }
    public int Yas { get; init; }
}

Nominal yazım daha uzun görünse de, her property'ye ayrı doğrulama mantığı, XML dokümantasyon yorumu veya öznitelik (attribute) eklemek istediğinizde çok daha esnek bir zemin sunar. Dolayısıyla seçim, modelin karmaşıklığına göre yapılır: hızlıca tanımlanıp geçilecek basit bir veri taşıyıcısı için positional syntax, ek doğrulama veya belgeleme gerektiren daha kalıcı bir model için nominal syntax daha uygun bir tercih olur.

Burada dikkat edilmesi gereken önemli bir nokta, bu iki yazımın birbirinden tamamen farklı iki mekanizma olmadığıdır. Positional syntax'te tanımlanan Ad ve Yas parametreleri, derleyici tarafından arka planda yine init erişimcili property'lere dönüştürülür. Yani hangi sözdizimini seçerseniz seçin, altta yatan mekanizma aynıdır: her iki durumda da elde ettiğiniz şey init-only property'lerdir. Bu da yazım tercihinin bir stil kararı olduğunu, davranış farkı yaratmadığını gösterir.

Value-Based Equality: Record'larda Eşitlik Nasıl Çalışır?

Record türlerinin en belirgin davranış farkı eşitlik kontrolünde ortaya çıkar. Bir record tanımladığınızda derleyici, Equals ve GetHashCode metotlarını sizin için otomatik üretir ve bu metotlar nesnenin bellek adresine değil, içindeki tüm üye değerlerine bakar. Bu sayede iki farklı örnek, aynı property değerlerine sahipse hem == operatörü hem de Equals metodu bunları eşit olarak değerlendirir.

var p1 = new Person("Ayşe", 28);
var p2 = new Person("Ayşe", 28);

Console.WriteLine(p1 == p2);        // True
Console.WriteLine(p1.Equals(p2));   // True

Bu davranış, aynı senaryonun klasik bir class ile kurgulandığı durumla doğrudan çelişir. Bir class'ta Equals ezilmediği sürece varsayılan karşılaştırma her zaman referans eşitliğine dayanır; yani iki nesne aynı property değerlerini taşısa bile, bellekte farklı adreslerde oluşturulduysa == ve Equals bunları eşit saymaz. Aynı örneği class ile yazdığınızda p1 == p2 ifadesi False döner, çünkü karşılaştırılan şey veri değil, nesnenin kimliğidir.

Bu fark, özellikle koleksiyonlarda arama yaparken, test senaryolarında beklenen değeri doğrularken veya iki nesnenin "aynı veriyi mi temsil ediyor" sorusuna cevap ararken pratik sonuçlar doğurur. Eşitlik semantiğinin bu şekilde ayrışması, nesne yönelimli programlamada sıkça atlanan ama üretim kodunda hataya yol açabilen bir ayrıntıdır; bu tür ince farkları birebir kod üzerinde görmek isteyenler birebir Java dersleri sırasında benzer eşitlik kavramlarının başka bir dilde nasıl ele alındığını karşılaştırmalı olarak inceleyebilir. Record'ların sağladığı bu otomatik value-based equality, veri modelleme senaryolarında elle yazılan karşılaştırma kodunun hem hacmini hem de hata olasılığını azaltır.

Init-Only Property: Set'ten Farkı ve Değişmezlik Mantığı

Bir record'un pozisyonel parametreleri arka planda init erişimcili property'lere dönüşür. init, bir property'ye yalnızca nesne oluşturma anında — yani constructor içinde veya object initializer bloğunda ({ } ile değer atarken) — yazma izni verir. Nesne bir kez oluşturulduktan sonra, o property'ye dışarıdan tekrar değer atamaya çalışmak derleme hatasıyla sonuçlanır.

Bu davranış, klasik set erişimcisinden köklü biçimde farklıdır. Bir property set ile tanımlandığında, nesne yaşam döngüsünün herhangi bir anında (metot içinde, başka bir sınıftan erişimle, döngü içinde) değeri değiştirilebilir. Bu esneklik bazı senaryolarda gereklidir, ama veri bütünlüğünün korunması gereken durumlarda tam tersi bir risk kaynağıdır: nesne oluşturulduktan sonra kodun herhangi bir yerinde sessizce değişebilir, bu da hata ayıklamayı zorlaştırır ve nesnenin "gerçek durumunu" takip etmeyi güçleştirir.

init bu iki uç arasında bir denge kurar. Property dışarıdan hâlâ okunabilir durumdadır — kontrollü immutability dediğimiz de tam olarak budur — fakat nesne bir kez kurulduktan sonra mutasyona tamamen kapanır. Böylece bir Person nesnesinin Ad veya Yas değeri, sistemin herhangi bir katmanında beklenmedik şekilde değiştirilemez; değer yalnızca oluşturulma anında belirlenir ve sonrasında sabit kalır.

Bu yaklaşımın pratik faydası, çok katmanlı uygulamalarda özellikle görünür. Bir API'den gelen veriyi temsil eden bir record, servis katmanından veri erişim katmanına kadar taşınırken kimse onun içeriğini yanlışlıkla değiştiremez. Değişiklik gerektiğinde ise geliştirici bunu örtük bir yan etki olarak değil, açık bir işlem olarak yapmak zorunda kalır — ki bu da bizi doğrudan with ifadesine götürür.

With-Expression ile Değiştirilmiş Kopya Üretmek

with ifadesi, mevcut bir record örneğinden yeni bir örnek türetmenin standart yoludur. Kritik nokta şudur: with orijinal nesneyi hiçbir şekilde değiştirmez; bellekte tamamen yeni bir örnek oluşturur ve bu yeni örneği geri döndürür. Orijinal referans, oluşturulduğu andaki değerleriyle olduğu gibi kalmaya devam eder.

with bloğunda yalnızca değiştirmek istediğiniz property'leri belirtirsiniz; belirtmediğiniz tüm property'ler orijinal nesneden aynen kopyalanır. Bu, init erişimcisinin sağladığı değişmezlikle doğrudan uyumlu çalışır: property'ler tek tek elle kopyalanmaz, derleyici arka planda tüm alanları taşıyıp yalnızca belirtilenleri günceller.

Bu mekanizma, immutable veri akışlarının merkezinde yer alır. Özellikle durum (state) yönetimi gerektiren senaryolarda — bir isteğin işlenmesi sırasında ara adımlarda farklı versiyonların tutulması, bir nesnenin geçmiş hâllerinin karşılaştırılması, veya eşzamanlı (concurrent) kod içinde paylaşılan verinin yanlışlıkla değiştirilmesinin önlenmesi gibi durumlarda — with ile üretilen her kopya bağımsız ve güvenli bir anlık görüntü (snapshot) işlevi görür. Aynı referans üzerinde mutasyon yapmak yerine her değişiklik yeni bir örnek olarak temsil edildiğinden, "bu nesne ne zaman, kim tarafından değiştirildi" sorusu ortadan kalkar; değişen şey referansın kendisidir, nesnenin içeriği değil.

Tek Örnekte Person Record'ı: Tanım, With ve Eşitlik

Aşağıdaki örnek, pozisyonel syntax ile tanımlanmış bir Person record'unun with ile nasıl kopyalandığını ve iki farklı örneğin eşitlik karşılaştırmasının nasıl sonuçlandığını tek parça hâlinde gösterir:

public record Person(string Ad, int Yas);

class Program
{
    static void Main()
    {
        var kisi1 = new Person("Ayse", 28);
        var kisi2 = kisi1 with { Yas = 29 };

        Console.WriteLine(kisi1); // Person { Ad = Ayse, Yas = 28 }
        Console.WriteLine(kisi2); // Person { Ad = Ayse, Yas = 29 }

        var kisi3 = new Person("Ayse", 28);

        Console.WriteLine(kisi1.Equals(kisi3)); // True
        Console.WriteLine(kisi1 == kisi3);       // True
        Console.WriteLine(kisi1 == kisi2);       // False
    }
}

Bu örnekte üç ayrı davranış aynı anda görülür. Birincisi, kisi2 oluşturulurken yalnızca Yas değeri belirtilmiştir; Ad property'si kisi1'den olduğu gibi kopyalanmıştır ve kisi1 bu işlemden hiç etkilenmemiştir. İkincisi, kisi1 ile kisi3 farklı bellek konumlarında duran iki ayrı nesne olmasına rağmen, Ad ve Yas değerleri aynı olduğu için hem Equals hem de == operatörü true döner — bu, record'ların referans yerine değer bazlı karşılaştırma yaptığının somut kanıtıdır. Üçüncüsü, kisi1 ile kisi2 arasında Yas farklı olduğundan karşılaştırma false sonucunu verir. Bu üç satır, bir önceki bölümlerde anlatılan init, with ve değer bazlı eşitlik kavramlarının pratikte nasıl bir araya geldiğini net biçimde gösterir.

Record mu Class mı? Ne Zaman Hangisi Tercih Edilmeli

Record ve class arasındaki seçim bir "hangisi daha iyi" sorusu değil, bir "hangi görevi üstleniyor" sorusudur. İkisi de nesne yönelimli programlamanın referans türleridir, ancak varsayılan davranışları taban tabana zıttır. Aşağıdaki tablo, bir tip tanımlarken karar verirken bakılması gereken dört temel kriteri karşılaştırır.

Kriter Class Record
Eşitlik Referans tabanlı (aynı bellek adresi değilse eşit değildir) Value-based (aynı verilere sahipse eşittir)
Mutability Genelde mutable state, alanlar zaman içinde değişebilir Varsayılan olarak immutable, init-only ile tanımlanır
Syntax Constructor, property ve Equals elle yazılır Constructor, property, Equals, ToString otomatik üretilir
Kullanım senaryosu Davranış odaklı, kalıtım ağırlıklı tasarımlar DTO, API modeli, veri taşıma amaçlı yapılar

Bu tablodan çıkarılacak pratik ölçüt şudur: bir tipin asıl görevi davranış sergilemekse — yani metotları çağrıldıkça kendi iç durumunu değiştiriyor, karmaşık bir kalıtım hiyerarşisinin parçası oluyor veya nesne kimliği (identity) verilerinden daha önemliyse — class doğru seçimdir. Bir sipariş işleyici, bir bağlantı havuzu yöneticisi ya da bir oyun karakteri sınıfı bu kategoriye girer; bunların "aynı verilere sahip iki kopyası" kavramsal olarak bile anlamsızdır.

Buna karşılık bir tipin görevi sadece veri taşımaksa — bir API'den dönen yanıtı temsil ediyor, bir katmandan diğerine parametre olarak geçiyor veya sonradan değiştirilmemesi gereken bir anlık durumu (snapshot) ifade ediyorsa record daha az kod ile daha güvenli bir sonuç verir. DTO'lar, komut/sorgu nesneleri (CQRS'teki command ve query tipleri), event modelleri ve konfigürasyon nesneleri bu gruba örnektir. Nesne yönelimli programlamanın temel prensiplerini class, kalıtım ve arayüz seviyesinde sağlam kurmak, record'un ne zaman gerçek bir kazanç sağladığını görebilmek için de gereklidir; bu temelleri birebir yürütülen özel ders çalışmaları ile pekiştirmek, karar verme sürecini soyut kurallardan çıkarıp somut kod pratiğine taşır. Sonuç olarak seçim, tipin "kim olduğu" değil "ne yaptığı" sorusuna verilen cevaba bağlıdır.

Record Struct'a Kısa Bir Bakış ve Pratik Yapma Önerisi

Record kavramı yalnızca referans türleriyle sınırlı değildir. record struct, record'un sağladığı value-based equality, otomatik ToString ve with-expression desteği gibi özellikleri bir değer türü (value type) üzerinde taşır. Yani mantıksal davranış aynı kalır — iki örnek aynı verilere sahipse eşit sayılır — ama bellekte tutulma biçimi class tabanlı record'dan farklıdır; struct semantiğine bağlı kalarak kopyalama değer bazlı gerçekleşir.

Bu fark, ne zaman record struct'ın gündeme geleceğini de belirler. Küçük, birkaç alandan oluşan ve program akışında sık sık kopyalanan veri yapıları — örneğin bir koordinat çifti, bir para birimi tutarı veya kısa ömürlü bir ölçüm değeri — struct'ın stack üzerinde tutulma eğiliminden fayda görebilir. Buna karşılık büyük veri kümeleri veya sık sık referansla paylaşılması gereken nesneler için class tabanlı record daha uygun kalmaya devam eder; struct'ın her kopyalamada tüm alanları taşıması, büyük yapılarda performans avantajını tersine çevirebilir.

public readonly record struct Point(double X, double Y);

var p1 = new Point(3, 4);
var p2 = new Point(3, 4);

Console.WriteLine(p1 == p2); // True, value-based eşitlik
Console.WriteLine(p1);        // Point { X = 3, Y = 4 }

Bu örnekte readonly record struct tanımı, hem değişmezliği hem de değer türü davranışını tek satırda birleştirir; p1 ve p2 farklı bellek konumlarında olsa da taşıdıkları veriler aynı olduğu için eşit kabul edilir.

 

Record, init-only property ve with-expression gibi kavramlar okunarak değil, koda dökülerek oturur. Bir sonraki adım olarak bu yazıda geçen tanımları küçük varyasyonlarla kendiniz yazmayı denemek ve mevcut bilgi seviyenizi C# bilgi testi ile ölçmek, hangi konularda tekrar yapmanız gerektiğini net biçimde ortaya koyar.

Sık Sorulan Sorular

Record ile class arasındaki temel fark nedir?

Class referans tabanlı eşitlik kullanır ve genellikle mutable state taşır; record ise varsayılan olarak value-based eşitlik sunar ve init-only property'lerle immutable veri modelleme için tasarlanmıştır.

Init-only property normal set property'den nasıl farklıdır?

Normal set property nesnenin ömrü boyunca her an değiştirilebilirken, init-only property yalnızca nesne oluşturma (constructor veya object initializer) sırasında bir kez değer alabilir; sonrasında değiştirilemez.

İki record örneği neden aynı verilerle eşit sayılır?

Çünkü record'un derleyici tarafından otomatik üretilen Equals metodu, referans karşılaştırması yerine tüm property değerlerini tek tek karşılaştırır; bu value-based equality mantığıdır.

With-expression tam olarak ne yapar, orijinal nesneyi değiştirir mi?

With-expression orijinal nesneyi değiştirmez; belirtilen alanlar güncellenmiş, geri kalanları aynı kalan yeni bir kopya üretir. Orijinal record değişmeden kalmaya devam eder.

Record hangi durumlarda tercih edilmemeli?

Tipin davranış odaklı olduğu, iç durumunun sık sık değiştiği veya karmaşık kalıtım hiyerarşilerinin parçası olduğu senaryolarda class daha uygun kalır; bu durumlarda record'un immutability avantajı fazladan karmaşıklığa dönüşebilir.

Record struct, normal record'dan ne bakımdan farklıdır?

Normal record bir referans türüdür; record struct ise aynı value-based equality ve with-expression desteğini bir değer türü üzerinde taşır ve genellikle küçük, sık kopyalanan veri yapıları için tercih edilir.

Positional syntax kullanmak zorunlu mudur?

Hayır. Positional syntax kısa ve pratik bir tanım yolu sunar, ancak nominal syntax ile property'leri tek tek yazarak da aynı immutable record'u tanımlamak mümkündür; seçim okunabilirlik ve ekip tercihine bağlıdır.

Record türü, init-only property ve with-expression üçlüsü, C#'ta veriyi güvenli ve öngörülebilir biçimde modellemenin pratik bir yoludur; ancak bu kavramların gerçek değeri, ne zaman class'ın ne zaman record'un daha doğru olduğunu ayırt edebilmekten gelir. Nesne yönelimli programlamanın bu tür ince ayrımlarını canlı bir ortamda tartışmak isteyenler için online yazılım eğitimi sayfası, konuyu daha geniş bir müfredat içinde ele alma imkânı sunar.

Bu içeriğin üretilmesinde yapay zeka araçlarından destek alınmıştır.

İlgili Eğitimler

Berk Keskin — Yazılım Geliştirici ve Eğitmen
Yazar

Berk Keskin Kimdir?

Yazılıma 12 yaşında başladı; bugün öğrencinin seviyesine ve hedefine göre şekillenen sürdürülebilir öğrenme sistemleri tasarlıyor. 300'den fazla kişiye ezber değil, düşünerek kod yazmayı öğretti — Berk Akademi'de izlemeye değil üretmeye dayalı öğrenme kültürünü o kuruyor.

WhatsApp Hemen Ara