Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Not
Bu makale bir özellik belirtimidir. Belirtim, özelliğin tasarım belgesi olarak görev alır. Önerilen belirtim değişikliklerini ve özelliğin tasarımı ve geliştirilmesi sırasında gereken bilgileri içerir. Bu makaleler, önerilen belirtim değişiklikleri son haline getirilene ve geçerli ECMA belirtimine dahil edilene kadar yayımlanır.
Özellik belirtimi ile tamamlanan uygulama arasında bazı tutarsızlıklar olabilir. Bu farklılıklar, ilgili dil tasarım toplantısı (LDM) notlarındaaktarılır.
Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek için
Şampiyon sayısı: https://github.com/dotnet/csharplang/issues/5737
Özet
Bu özellik, yapı oluşturucularında kullanıcı tarafından döndürülmeden veya kullanılmadan önce açıkça atanmamış alanları tanımlamamızı ve bunları kesin atama hataları vermek yerine örtük olarak default
ile başlatmamızı sağlar.
Motivasyon
Bu teklif, dotnet/csharplang#5552 ve dotnet/csharplang#5635'te bulunan kullanılabilirlik sorunlarının yanı sıra #5563(tüm alanlar kesinlikle atanmalıdır, ancak field
oluşturucu içinde erişilebilir değildir) için olası bir azaltma olarak ortaya çıkarılır.
C# 1.0'dan bu yana yapı oluşturucularının this
bir parametreymiş gibi kesinlikle out
ataması gerekiyordu.
public struct S
{
public int x, y;
public S() // error: Fields 'S.x' and 'S.y' must be fully assigned before control is returned to the caller
{
}
}
Ayarlayıcılar yarı otomatik özellikler üzerinde elle tanımlandığında, derleyici özelliğin atamasını yedekleme alanının atamasıyla eşdeğer olarak değerlendiremediği için bu durum sorunlara yol açar.
public struct S
{
public int X { get => field; set => field = value; }
public S() // error: struct fields aren't fully assigned. But caller can only assign 'this.field' by assigning 'this'.
{
}
}
Ayarlayıcının ref this
almadığı ancak parametre olarak out field
aldığı bir şema gibi ayarlayıcılar için daha ayrıntılı kısıtlamalar getirilmesinin bazı kullanım örnekleri için çok niş ve eksik olacağını varsayıyoruz.
Üzerinde zorlandığımız temel sorunlardan biri, yapı özelliklerine manuel ayarlayıcılar uygulandığında, kullanıcıların genellikle atamaları veya mantıklarını tekrar etmek zorunda kalmasıdır.
struct S
{
private int _x;
public int X
{
get => _x;
set => _x = value >= 0 ? value : throw new ArgumentOutOfRangeException();
}
// Solution 1: assign some value in the constructor before "really" assigning through the property setter.
public S(int x)
{
_x = default;
X = x;
}
// Solution 2: assign the field once in the constructor, repeating the implementation of the setter.
public S(int x)
{
_x = x >= 0 ? x : throw new ArgumentOutOfRangeException();
}
}
Önceki tartışma
Küçük bir grup bu sorunu inceledi ve birkaç olası çözümü değerlendirdi:
- Yarı otomatik özelliklerin ayarlayıcılarını elle uyguladığı durumlarda, kullanıcıların
this = default
atamasını yapmasını gerektir. Alan başlatıcılarda ayarlanan değerleri ortadan kaldırdığı için bunun yanlış çözüm olduğu konusunda hemfikiriz. - Otomatik/yarı otomatik özelliklerin tüm yedekleme alanlarını örtük olarak başlatın.
- Bu, "yarı otomatik özellik ayarlayıcıları" sorununu çözer ve açıkça bildirilen alanları farklı kurallar altına yerleştirir: "Alanlarımı örtük olarak başlatma, ancak otomatik özelliklerimi örtük olarak başlat."
- Yarı otomatik bir özelliğin yedekleme alanını atamak için bir yol sağlayın ve kullanıcıların bunu atamasını sağlayın.
- Bu, (2) ile karşılaştırıldığında hantal olabilir. Otomatik özelliğin "otomatik" olması ve belki de alanın "otomatik" başlatılmasını içermesi gerekir. Özellik, temel alan, özelliğe bir atama yoluyla atandığında mı yoksa özellik ayarlayıcısı çağrıldığında mı karışıklığa neden olabilir.
Ayrıca, örneğin her şeyi açıkça atamak zorunda kalmadan yapılara birkaç alan başlatıcı eklemek isteyen kullanıcılardan geri bildirim aldık. Bu sorunu ve "manuel ayarlayıcı ile yarı otomatik özellik" sorununu aynı anda çözebiliriz.
struct MagnitudeVector3d
{
double X, Y, Z;
double Magnitude = 1;
public MagnitudeVector3d() // error: must assign 'X', 'Y', 'Z' before returning
{
}
}
Kesin atamayı ayarlama
struct S
{
int x, y;
// Example 1
public S()
{
// ok. Compiler inserts an assignment of `this = default`.
}
// Example 2
public S()
{
// ok. Compiler inserts an assignment of `y = default`.
x = 1;
}
// Example 3
public S()
{
// valid since C# 1.0. Compiler inserts no implicit assignments.
x = 1;
y = 2;
}
// Example 4
public S(bool b)
{
// ok. Compiler inserts assignment of `this = default`.
if (b)
x = 1;
else
y = 2;
}
// Example 5
void M() { }
public S(bool b)
{
// ok. Compiler inserts assignment of `y = default`.
x = 1;
if (b)
M();
y = 2;
}
}
Örneklerde (4) ve (5), sonuçta elde edilen kodgen bazen alanların "çift atamalarına" sahiptir. Bu genelde uygundur, ancak bu tür çift atamalarla ilgilenen kullanıcılar için, önceden kesin atama hatası tanılaması olarak verilenleri, varsayılan olarak devre dışı olan uyarı tanılaması şeklinde yayabiliriz.
struct S
{
int x;
public S() // warning: 'S.x' is implicitly initialized to 'default'.
{
}
}
Bu tanılamanın önem derecesini "hata" olarak ayarlayan kullanıcılar C# 11 öncesi davranışı kabul eder. Bu tür kullanıcılar, manuel olarak uygulanan ayarlayıcılar nedeniyle yarı otomatik özelliklerden "mahrum bırakı"lmış durumdadır.
struct S
{
public int X
{
get => field;
set => field = field < value ? value : field;
}
public S() // error: backing field of 'S.X' is implicitly initialized to 'default'.
{
X = 1;
}
}
İlk bakışta bu, özelliğin bir "eksiklik" gibi görünebilir, ancak aslında yapılması gereken doğru şeydir. Kullanıcı, tanılamayı etkinleştirerek derleyicinin oluşturucudaki alanlarını örtük olarak başlatmasını istemediğini söyler. Burada örtük başlatmayı önlemenin bir yolu yoktur, bu nedenle onlar için çözüm, alanı el ile bildirmek ve atamak veya alan başlatıcısı eklemek gibi el ile uygulanan bir ayarlayıcıdan farklı bir başlatma yöntemi kullanmaktır.
JIT şu anda refler aracılığıyla ölü depoları ortadan kaldırmaz, bu da bu örtük başlatmaların gerçek bir maliyeti olduğu anlamına gelir. Ama bu düzeltilebilir. https://github.com/dotnet/runtime/issues/13727
Bireysel alanları tüm örnek yerine başlatmanın aslında sadece bir optimizasyon olduğunu unutmamak gerekir. Derleyici, tüm dönüş noktalarında veya this
ile ilgili herhangi bir alan dışı üye erişiminden önce kesinlikle atanmamış olan alanların değişmezini karşıladığı sürece, arzuladığı herhangi bir sezgisel yöntemi uygulamakta özgür olmalıdır.
Örneğin, bir yapıda 100 alan varsa ve bunlardan yalnızca biri açıkça başlatıldıysa, diğer 99 alan için örtülü olarak initobj
oluşturmak yerine, yapının tamamında bir initobj
uygulamak daha mantıklı olabilir. Ancak, diğer 99 alan için örtük olarak initobj
yayan bir uygulama yine de geçerli olacaktır.
Dil belirtimine yapılan değişiklikler
Standardın aşağıdaki bölümünü ayarlıyoruz:
https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12814-this-access
Oluşturucu bildiriminde oluşturucu başlatıcısı yoksa,
this
değişkeni yapı türününout
parametresiyle tam olarak aynı şekilde davranır. Özellikle bu, değişkenin örnek oluşturucusunun her yürütme yolunda kesinlikle atanacağı anlamına gelir.
Bu dili okuyacak şekilde ayarlıyoruz:
Eğer oluşturucu bildiriminde bir oluşturucu başlatıcısı yoksa, this
değişkeni, kesin atama gereksinimlerinin (out
) karşılanmadığında hata olarak değerlendirilmemesi dışında, yapı türünün parametresine benzer şekilde davranır. Bunun yerine aşağıdaki davranışları tanıtacağız:
-
this
değişkeninin kendisi gereksinimleri karşılamadığında, gereksinimlerin ihlal edildiği tüm noktalardathis
içindeki atanmamış tüm örnek değişkenleri, oluşturucudaki diğer kodlar çalıştırılmadan önce başlatma aşamasındaki varsayılan değere (§9.3) örtük olarak başlatılır. -
içinde
this
örnek değişkeni gereksinimleri karşılamazsa veya v içinde, herhangi bir seviyedeki iç içe geçmiş başka bir örnek değişkeni gereksinimleri karşılamazsa, başlatma aşamasında, oluşturucudaki diğer herhangi bir kod çalıştırılmadan önce v örtük olarak varsayılan değerle başlatılır.
Tasarım toplantıları
C# feature specifications