C# 9 ile gelen record tipi, birçok geliştiricinin "bunu ne zaman kullanayım?" sorusunu beraberinde getirdi. Class'tan ne farkı var? Struct ne zaman seçilmeli? Bu yazıda üç tipi bellek modeli, eşitlik semantiği ve değişmezlik açısından karşılaştırıyoruz.
Temel Farklar
| Özellik | class | struct | record |
|---|---|---|---|
| Bellek | Heap | Stack/Inline | Heap (class) / Stack (struct) |
| Eşitlik | Referans | Değer | Değer (otomatik) |
| Kalıtım | Evet | Hayır | Evet (record'dan) |
| Varsayılan değişmezlik | Mutable | Mutable | Immutable (init) |
class: Referans Tipi, Kimlik Semantiği
var a = new Point(1, 2);
var b = new Point(1, 2);
Console.WriteLine(a == b); // false — aynı heap adresi değil
Console.WriteLine(ReferenceEquals(a, b)); // false
var c = a;
c.X = 99;
Console.WriteLine(a.X); // 99 — aynı nesneyi paylaşıyorlar
Class ne zaman kullanılmalı:
- Nesnenin kimliği önemliyse (iki farklı kullanıcı aynı veriye sahip olsa da farklı nesnedir)
- Mutable state gerekiyorsa
- Kalıtım hiyerarşisi gerekiyorsa
- DI container ile yönetilecekse (servisler, repository'ler)
struct: Değer Tipi, Kopya Semantiği
var a = new Point(1, 2); // Stack'te veya inline
var b = a; // Tam kopya — bağımsız
b.X = 99;
Console.WriteLine(a.X); // 1 — a etkilenmedi
// struct eşitliği değer karşılaştırır
Console.WriteLine(a == b); // false (1,2) != (99,2)
readonly struct: Değişmez Değer Tipi
// Tüm field'lar readonly — immutability garanti altında
public readonly struct Vector3(float x, float y, float z)
{
public float X { get; } = x;
public float Y { get; } = y;
public float Z { get; } = z;
public float Length => MathF.Sqrt(X * X + Y * Y + Z * Z);
public Vector3 Normalize()
{
var len = Length;
return new Vector3(X / len, Y / len, Z / len);
}
// Operatör overloading
public static Vector3 operator +(Vector3 a, Vector3 b)
=> new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
}
struct ne zaman kullanılmalı:
- Küçük veri taşıyıcıları: koordinat, renk, para birimi, ID sarmalayıcı
- Kısa ömürlü ve sık oluşturulan nesneler (GC baskısını azaltır)
- 16 byte altında kalmak koşuluyla (daha büyük struct kopyalama maliyeti yaratır)
- Koleksiyon içinde sıkıştırılmış (value type arrays cache-friendly)
record: Değer Eşitliği + Immutability
public record Person(string FirstName, string LastName, int Age);
var alice1 = new Person("Alice", "Smith", 30);
var alice2 = new Person("Alice", "Smith", 30);
Console.WriteLine(alice1 == alice2); // true — değer eşitliği
Console.WriteLine(alice1.Equals(alice2)); // true
Console.WriteLine(alice1.GetHashCode() == alice2.GetHashCode()); // true
// ToString otomatik üretilir
Console.WriteLine(alice1); // Person { FirstName = Alice, LastName = Smith, Age = 30 }
with Expression: Değiştirilmiş Kopya
var alice = new Person("Alice", "Smith", 30);
// Yaşı değişmiş yeni kopya — orijinal bozulmaz
var olderAlice = alice with { Age = 31 };
Console.WriteLine(alice.Age); // 30
Console.WriteLine(olderAlice.Age); // 31
// Zincirleme
var renamed = alice
with { FirstName = "Alicia" }
with { Age = 25 };
record class vs record struct
// record class (varsayılan) — heap, referans tipi, değer eşitliği
public record class Point2D(double X, double Y);
// record struct — stack, değer tipi, değer eşitliği
public record struct Point3D(double X, double Y, double Z);
// readonly record struct — immutable + stack
public readonly record struct Color(byte R, byte G, byte B)
{
public static readonly Color Red = new(255, 0, 0);
public static readonly Color Green = new(0, 255, 0);
public static readonly Color Blue = new(0, 0, 255);
}
Karar Ağacı
// Kimlik önemli mi? (aynı değer = farklı nesne)
// → class
// Küçük (<16 byte), kısa ömürlü, GC baskısı önemli mi?
// → struct (veya readonly struct)
// Veri taşıyıcı, eşitlik değer bazlı, immutable tercih mi?
// → record
// Veri taşıyıcı + GC baskısı önemli + immutable?
// → readonly record struct
// Örnekler:
public record OrderId(Guid Value); // Değer nesnesi — record
public record Address(string City, string Zip); // DTO — record
public record class Customer(...) // Domain entity — record class
public readonly record struct Money(decimal Amount, string Currency); // Para — record struct
public readonly struct Rgb(byte R, byte G, byte B); // Renk — readonly struct
public class UserService(IUserRepo repo) { } // Servis — class
Record ile Kalıtım
public abstract record Shape(string Color);
public record Circle(string Color, double Radius) : Shape(Color);
public record Rectangle(string Color, double Width, double Height) : Shape(Color);
// Pattern matching ile mükemmel uyum
double Area(Shape shape) => shape switch
{
Circle { Radius: var r } => Math.PI * r * r,
Rectangle { Width: var w, Height: var h } => w * h,
_ => throw new NotSupportedException()
};
Özet: Servisler ve davranış ağırlıklı nesneler için class; küçük sayısal değerler için struct; veri taşıyıcılar ve domain value objects için record. Bu üçlüyü doğru yerde kullanmak hem performansı hem de kodun anlaşılırlığını önemli ölçüde artırıyor.