Bir struct, kendiniz tanımladığınız bir değer tipidir. Alanları, property'leri, metotları ve constructor'larıyla bir sınıfa benzer, ama struct tipindeki bir değişken bir nesneye referans yerine veriyi doğrudan içerir. Bu tek fark atamanın, metot çağrılarının, eşitliğin ve null'ın nasıl davrandığını değiştirir.
Değer semantiği: atama kopyalar
Yerleşik sayısal tipler, bool, char, DateTime, TimeSpan ve Guid hepsi struct'tır. Sizinkiler de aynı şekilde çalışır:
Çıktı:
struct: s1.X = 1, s2.X = 50
class: c1.X = 50, c2.X = 50
after Move: s1.X = 1, c1.X = 150
Struct ile s2 bağımsız bir kopyadır ve Move da kendi kopyası üzerinde çalışır. Bir metodun çağıranın struct'ını değiştirmesine izin vermek için onu ref ile referansla geçirin (bkz. ref ve out). Değişikliğe izin vermeden büyük bir struct'ı kopyalamaktan kaçınmak için C# 7.2 in parametrelerini ekledi.
Değer tipi olmanın diğer sonuçları:
- Bir struct değişkeni asla
nullolamaz.PointS p = null;derlenmez. "Değer yok" anlamlıysaPointS?(nullable bir değer tipi) kullanın. - Başlatılmamış bir struct tamamen sıfırdır: sayısal alanlar
0,boolalanlarfalse, referans alanlarnull. - Bir struct başka bir struct'tan ya da class'tan kalıtım alamaz ve hiçbir şey bir struct'tan kalıtım alamaz. Interface uygulayabilir.
- Yerel struct değişkenleri genellikle stack'te ya da içeren nesnenin içinde yaşar, bu yüzden bir tane oluşturmak heap'te ayırma yapmaz.
Struct listesi tuzağı: CS1612
Bir struct'ı okumak bir kopya ürettiği için masum görünen bu satır derlenmez:
var points = new List<PointS> { new PointS(1, 2) };
points[0].X = 10;
// error CS1612: Cannot modify the return value of 'List<PointS>.this[int]' because it is not a variable
List<T> indeksleyicisi elemanın bir kopyasını döndüren bir metottur. O geçici kopyada X'i ayarlamak hiçbir şeyi değiştirmezdi, bu yüzden derleyici sizi durdurur. Aynı hata bir property bir struct döndürdüğünde de görünür: order.Location.X = 10. Kopyalayın, değiştirin, geri yazın:
Çıktı:
35
7
Diziler istisnadır: array[0] bir kopya değil elemanın kendisidir. Buradaki tekrarlayan sıkıntı, struct'ları değiştirilemez yapma yönündeki standart tavsiyenin nedenidir: bir struct değiştirilemiyorsa, bir kopyayı değiştirmekle kaybedilecek bir şey yoktur.
Constructor'lar ve varsayılan değerler
C# 7'den 9'a kadar struct constructor kuralları katıdır:
- Parametresiz bir constructor bildiremezsiniz.
new PointS()her zaman vardır ve her alanı sıfırlar. - Bildirdiğiniz bir constructor, dönmeden önce her alanı (ve auto-property'yi) atamalıdır.
- Örnek alanlarında alan başlatıcılarına (
public int X = 1;) izin verilmez.
Çıktı:
19.90 EUR
0.00 (none)
0.00 (none)
0.00 (none)
Money varsayılan değerin neden önemli olduğunu da gösterir: sıfırlanmış bir Money'nin para birimi null'dır ve kodunuz bunu ele almalıdır, çünkü diziler, default(T) ve başlatılmamış alanların hepsi böyle bir değer üretir.
Yeni sürümler bu kuralları gevşetti:
// C# 10: explicit parameterless constructors and field initializers
public struct Settings
{
public int Retries = 3;
public Settings() { }
}
// C# 11: fields you do not assign in a constructor are zeroed automatically,
// instead of being a compile error.
Bir C# 10 parametresiz constructor'ı new Settings() için çalışır ama default(Settings) ya da dizi elemanları için çalışmaz; onlar hâlâ tamamen sıfırdır. Bu ayrım insanları şaşırtır, bu yüzden dikkatli kullanın.
readonly struct (C# 7.2)
Struct'ın kendisini readonly işaretlemek derleyicinin değiştirilemezliği uygulamasını sağlar: her alan readonly ve her auto-property yalnızca get olmalıdır.
public readonly struct Temperature
{
public double Celsius { get; }
public Temperature(double celsius) { Celsius = celsius; }
public Temperature WarmerBy(double delta) => new Temperature(Celsius + delta); // returns a new value
}
Niyeti belgelemenin ötesinde performansa yardımcı olur: readonly olmayan bir struct readonly bir alanda saklandığında ya da bir in parametresi olarak geçirildiğinde, derleyici her metot çağrısından önce onu kopyalar (metodun onu değiştirmeyeceğini bilemez). Bir readonly struct böyle savunma amaçlı kopyalara ihtiyaç duymaz. C# 7.0'da yine de her alanı readonly yapabilirsiniz, Money'nin yalnızca get property'lerle yaptığı gibi: bu size değiştirilemezliği verir, ama kazanılan kopyaları vermez, çünkü derleyici yalnızca readonly bildirilmiş bir struct'a güvenir.
Eşitlik
Bir struct üzerinde Equals varsayılan olarak alan alan karşılaştırır; istediğiniz değer davranışı budur. Ama varsayılan uygulama (ValueType.Equals) reflection kullanabilir ve yavaştır ve == operatörü hiç tanımlı değildir: kendi struct'ınızda a == b CS0019 hatasıdır. Struct karşılaştırılacaksa ikisini de uygulayın:
Çıktı:
True
True
True
IEquatable<T>'yi uygulamak koleksiyonlar için önemlidir: HashSet<T>, Dictionary<TKey, TValue> ve List<T>.Contains, Equals(object)'i çağırmak için her değeri box'lamak yerine doğrudan Equals(GridCell)'i çağırır. Record struct'lar (aşağıda) bunların hepsini sizin için üretir.
Boxing
Bir struct'ı object'e ya da bir interface tipine dönüştürmek onu box'lar: çalışma zamanı değeri yeni bir heap nesnesine kopyalar. Kutu ile orijinal bundan sonra bağımsızdır:
Çıktı:
2
0
Boxing her seferinde bir ayırmaya mal olur; ArrayList gibi eski generic olmayan koleksiyonların değer tipleriyle yavaş olmasının ve generics'in onların yerini almasının nedeni budur. Ayrıca bir interface üzerinden erişilen değiştirilebilir bir struct'ın orijinalde değil kutusunda değiştirildiği anlamına gelir; bu da struct'ları değiştirilemez tutmak için bir neden daha.
Struct ile class
| struct | class | |
|---|---|---|
| Tür | değer tipi | referans tipi |
| Atama ve parametreler | veriyi kopyalar | referansı kopyalar |
null olabilir | hayır (T? olabilir) | evet |
| Varsayılan değer | tüm alanlar sıfır | null |
| Kalıtım | yok; interface uygulayabilir | tek temel sınıf, interface'ler |
== | siz aşırı yüklemedikçe tanımlı değil | aşırı yüklenmedikçe referans eşitliği |
Varsayılan Equals | alanları karşılaştırır | referansları karşılaştırır |
| Ayırma | satır içi (stack ya da içeren nesne) | heap, çöp toplayıcılı |
| Uygun olduğu yer | küçük değiştirilemez değerler | varlıklar, büyük ya da paylaşılan durum |
Struct ne zaman kullanılır
Şunların hepsi geçerli olduğunda struct seçin: tip tek bir mantıksal değerdir (bir koordinat, bir para miktarı, bir renk, bir tarih aralığı), küçüktür (Microsoft'un rehberi yaklaşık 16 bayt, kabaca dört int), değiştirilemezdir ve kalıtıma ihtiyacınız yoktur. Struct'lar çok sayıda oluşturduğunuzda, örneğin bir dizide milyonlarca nokta, kendini amorti eder, çünkü öğe başına bir heap ayırmasından ve bir çöp toplayıcı girdisinden kaçınırlar.
Kimliği olan her şey (bir müşteri, bir sipariş), büyük olan her şey, birkaç yerden değiştirilen her şey ve emin olmadığınız her durum için class seçin. Büyük, değiştirilebilir bir struct size kopyalamanın maliyetini ve kopyaları değiştirmenin kafa karışıklığını birlikte verir.
record struct (C# 10)
C# 10, bir struct için değer eşitliğini, =='i, ToString'i ve with desteğini tek satırda üreten record struct'ı ekledi:
public readonly record struct Coordinate(double Lat, double Lng);
var home = new Coordinate(38.72, -9.14);
var same = new Coordinate(38.72, -9.14);
Console.WriteLine(home == same); // True
Console.WriteLine(home); // Coordinate { Lat = 38.72, Lng = -9.14 }
var north = home with { Lat = 38.80 };
Düz bir record struct'ın değiştirilebilir konumsal property'leri vardır; readonly record struct onları yalnızca init yapar ve genellikle istediğiniz budur. Record'lar sayfası class sürümünü ele alır ve derleyicinin yazdığı üyeleri gösterir.
Yaygın hatalar
- Bir struct'ı bir kopya üzerinden değiştirmek.
list[0].X = 1(CS1612), birforeachdeğişkeni (CS1654), bir property getter'ının sonucu. Değiştirilmiş kopyayı geri yazın ya da struct'ı değiştirilemez yapın. - Büyük struct'lar. Her atama ve çağrı hepsini kopyalar. Birkaç alanı geçince bir class genellikle daha hızlıdır.
- Sıfırlanmış varsayılanı unutmak. Diziler ve
default(T)constructor'ınızı çalıştırmadan struct değerleri oluşturur, bu yüzden orada doğruladığınız alanlar yine sıfır ya danullolabilir. - Tanımlamadan
==ile karşılaştırmak. CS0019.IEquatable<T>'yi ve operatörleri uygulayın ya da bir record struct kullanın. - Interface'lerin arkasındaki değiştirilebilir struct'lar. Boxing, değişikliğin bakmadığınız bir kopyada gerçekleştiği anlamına gelir.
Sıkça Sorulan Sorular
C#'ta struct nedir?
Struct, kullanıcı tanımlı bir değer tipidir: struct Point { public int X; public int Y; }. Struct tipindeki bir değişken bir nesneye referans yerine verinin kendisini tutar, bu yüzden atama ve bir metoda geçirme tüm değeri kopyalar. int, double, DateTime ve Guid hepsi struct'tır.
C#'ta struct ile class arasındaki fark nedir?
Class bir referans tipidir: değişkenler tek bir nesneyi paylaşır ve null'a izin verilir. Struct ise bir değer tipidir: her değişkenin kendi kopyası vardır, null olamaz (Point? kullanmadıkça), kalıtım alamaz ya da kalıtım verilemez ve varsayılan değeri tüm alanlarının sıfırlanmış halidir. Struct'lar küçük, değiştirilemez değerlere; class'lar kimliği ve davranışı olan varlıklara uyar.
C#'ta class yerine ne zaman struct kullanmalıyım?
Tip tek bir küçük değeri temsil ettiğinde (bir koordinat, bir para miktarı, bir tarih aralığı), ideal olarak değiştirilemez olduğunda, içeriğine göre karşılaştırıldığında ve heap ayırmalarından kaçınmanın önemli olduğu büyük sayılarda oluşturulduğunda. Microsoft'un rehberi kabaca 16 bayt ya da daha az bir boyut ekler. Tipin kimliği varsa, çok alanı varsa ya da referanslar üzerinden değiştiriliyorsa class kullanın.
Neden "Cannot modify the return value because it is not a variable" hatası alıyorum?
Bu CS1612 hatasıdır; tipik olarak struct'lardan oluşan bir List<Point> üzerinde list[0].X = 5'ten gelir. İndeksleyici struct'ın bir kopyasını döndürür, bu yüzden kopyayı değiştirmek kaybolurdu ve derleyici reddeder. Elemanı bir değişkene kopyalayın, değiştirin ve geri atayın: var p = list[0]; p.X = 5; list[0] = p;. Dizilerde bu sorun yoktur, çünkü array[0] elemanın kendisine başvurur.
C#'ta readonly struct nedir?
readonly struct (C# 7.2), struct'ın hiçbir üyesinin durumunu değiştirmediğini bildirir: tüm alanlar readonly ve auto-property'ler yalnızca get olmalıdır. Derleyici bunu uygular ve struct in ile geçirildiğinde ya da readonly bir alanda saklandığında derleyicinin savunma amaçlı kopyalardan kaçınmasını sağlar. Çoğu struct readonly olmalıdır.