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.
Uyarı
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ında yakalanır.
Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek için
Şampiyonluk sorunu: https://github.com/dotnet/csharplang/issues/2691
Özet
Sınıfların ve yapıların parametre listesi ve temel sınıf belirtimlerinin bağımsız değişken listesi olabilir. Birincil oluşturucu parametreleri sınıf veya yapı bildiriminin tamamında kapsam içindedir ve bir işlev üyesi veya anonim işlev tarafından yakalanırlarsa, uygun şekilde depolanırlar (örneğin, bildirilen sınıfın veya yapının anlatılamaz özel alanları olarak).
Teklif, bazı ek üyelerin sentezlenmiş olduğu bu daha genel özellik açısından kayıtlarda zaten mevcut olan birincil oluşturucuları "yeniden tutar".
Motivasyon
C# içindeki bir sınıfın veya yapının birden fazla oluşturucuya sahip olma özelliği genellik sağlar, ancak oluşturucu girişi ile sınıf durumunun temiz bir şekilde ayrılması gerektiğinden bildirim söz diziminde bir miktar tedyum elde edilmesi gerekir.
Birincil oluşturucular, bir oluşturucunun parametrelerini başlatma veya doğrudan nesne durumu olarak kullanılacak tüm sınıfın veya yapının kapsamına yerleştirir. Diğer tüm oluşturucuların birincil oluşturucu aracılığıyla çağrısı yapması gerekir.
public class B(bool b) { } // base class
public class C(bool b, int i, string s) : B(b) // b passed to base constructor
{
public int I { get; set; } = i; // i used for initialization
public string S // s used directly in function members
{
get => s;
set => s = value ?? throw new ArgumentNullException(nameof(S));
}
public C(string s) : this(true, 0, s) { } // must call this(...)
}
Ayrıntılı tasarım
Bu, kayıtlar ve kayıt dışı genelleştirilmiş tasarımı açıklar ve ardından birincil oluşturucunun varlığında bir dizi sentezlenmiş üye ekleyerek kayıtlar için mevcut birincil oluşturucuların nasıl belirtildiğini ayrıntılarıyla açıklar.
Sözdizimi
Sınıf ve yapı bildirimleri, tür adında parametre listesine, temel sınıftaki bağımsız değişken listesine ve yalnızca bir ;öğesinden oluşan bir gövdeye izin verecek şekilde genişletilir:
class_declaration
: attributes? class_modifier* 'partial'? class_designator identifier type_parameter_list?
parameter_list? class_base? type_parameter_constraints_clause* class_body
;
class_designator
: 'record' 'class'?
| 'class'
class_base
: ':' class_type argument_list?
| ':' interface_type_list
| ':' class_type argument_list? ',' interface_type_list
;
class_body
: '{' class_member_declaration* '}' ';'?
| ';'
;
struct_declaration
: attributes? struct_modifier* 'partial'? 'record'? 'struct' identifier type_parameter_list?
parameter_list? struct_interfaces? type_parameter_constraints_clause* struct_body
;
struct_body
: '{' struct_member_declaration* '}' ';'?
| ';'
;
interface_declaration
: attributes? interface_modifier* 'partial'? 'interface'
identifier variant_type_parameter_list? interface_base?
type_parameter_constraints_clause* interface_body
;
interface_body
: '{' interface_member_declaration* '}' ';'?
| ';'
;
enum_declaration
: attributes? enum_modifier* 'enum' identifier enum_base? enum_body
;
enum_body
: '{' enum_member_declarations? '}' ';'?
| '{' enum_member_declarations ',' '}' ';'?
| ';'
;
Not: Bu üretimler, Kayıtlar'daki ve kayıt yapılarındaki yerini alır record_declaration ve record_struct_declaration her ikisi de kullanımdan kalkmış olur.
Kapsayan öğesinin içermemesi bir hatasıdır class_baseargument_list.class_declarationparameter_list Kısmi bir sınıfın veya yapının en fazla bir kısmi tür bildirimi bir parameter_listsağlayabilir. Bildirimindeki parameter_listrecord parametrelerin tümü değer parametreleri olmalıdır.
Bu teklife class_bodygöre , struct_bodyinterface_body ve enum_body değerlerinin yalnızca bir ;'den oluşmasına izin verilmiyor.
ile bir parameter_list sınıfı veya yapısı, imzası tür bildiriminin değer parametrelerine karşılık gelen örtük bir ortak oluşturucuya sahiptir. Bu tür için birincil oluşturucu olarak adlandırılır ve varsa örtük olarak bildirilen parametresiz oluşturucunun gizlenmesine neden olur. Birincil oluşturucunun ve aynı imzaya sahip bir oluşturucunun tür bildiriminde zaten mevcut olması bir hatadır.
Arama Listesi
Basit adların araması, birincil oluşturucu parametrelerini işlemek için genişletilir. Değişiklikler aşağıdaki alıntıda kalın yazıyla vurgulanır:
- Aksi takdirde, dolaysız kapsayan tür bildirimindeki örnek türle başlayarak, her kapsayan sınıf veya yapının bildirimindeki (varsa) örnek türle devam eden
T(§15.3.2) örnek türü:
- bildirimi
Tbir birincil oluşturucu parametresiIiçeriyorsa ve başvuru bir alanın, özelliğinargument_listTveya olayınınclass_basebaşlatıcısı içinde veya içinde oluşuyorsaT, sonuç birincil oluşturucu parametresidirI- Aksi takdirde, sıfır ise
eve bildirimiTadlıIbir tür parametresi içeriyorsa, simple_name bu tür parametresine başvurur.- Aksi takdirde, içindeki
I'ninTtür bağımsız değişkenleriyle üye araması (e) eşleşme oluşturursa:
Themen kapsayan sınıfın veya yapı türünün örnek türüyse ve arama bir veya daha fazla yöntemi tanımlarsa, sonuç ilişkili örnek ifadesithisolan bir yöntem grubudur. Tür bağımsız değişken listesi belirtildiyse, genel bir yöntemi çağırmak için kullanılır (§12.8.10.2).- Aksi takdirde,
hemen kapsayan sınıfın veya yapı türünün örnek türüyse, arama bir örnek üyesi tanımlarsa ve başvuru bir örnek oluşturucusunun, örnek yönteminin veya örnek erişimcisinin ( §12.2.1 )bloğunda gerçekleşirse, sonuç, formun üye erişimiyle ( §12.8.7 ) aynıdır. Bu yalnızcaesıfır olduğunda gerçekleşebilir.- Aksi takdirde, sonuç form veya
T.Iüye erişimiyle (T.I<A₁, ..., Aₑ>) aynıdır.- Aksi takdirde, bildirimi
Tbirincil oluşturucu parametresiIiçeriyorsa, sonuç birincil oluşturucu parametresidirI.
İlk ekleme, kayıtlardaki birincil oluşturucular tarafından tahakkuk eden değişikliğe karşılık gelir ve birincil oluşturucu parametrelerinin başlatıcılar ve temel sınıf bağımsız değişkenleri içindeki ilgili alanlardan önce bulunmasını sağlar. Bu kuralı statik başlatıcılara da genişletir. Ancak, kayıtların her zaman parametresiyle aynı ada sahip bir örnek üyesi olduğundan, uzantı yalnızca bir hata iletisinde değişikliğe yol açabilir. Bir parametreye geçersiz erişim ve örnek üyesine geçersiz erişim.
İkinci ekleme, birincil oluşturucu parametrelerinin tür gövdesinde başka bir yerde bulunmasına izin verir, ancak yalnızca üyeler tarafından gölgelenmezse.
Başvuru aşağıdakilerden biri içinde gerçekleşmiyorsa birincil oluşturucu parametresine başvurmak bir hatadır:
- bağımsız
nameofdeğişken - bir örnek alanı, özellik veya bildirim türü olayının başlatıcısı (parametresiyle birincil oluşturucuyu bildirme türü).
-
argument_listclass_basebildirim türünün değeri. - bildirim türünün bir örnek yönteminin gövdesi (örnek oluşturucularının dışlandığını unutmayın).
- bildirim türünün örnek erişimcisinin gövdesi.
Başka bir deyişle, birincil oluşturucu parametreleri bildirim türü gövdesi boyunca kapsam içindedir. Bildirim türünün üyelerini bir alanın başlatıcısı içinde, bildirim türünün özelliğinde veya olayında ya da argument_listclass_base bildirim türü içinde gölgeler. Diğer her yerde bildirim türünün üyeleri tarafından gölgelenirler.
Bu nedenle, aşağıdaki bildirimde:
class C(int i)
{
protected int i = i; // references parameter
public int I => i; // references field
}
alanının i başlatıcısı parametresine i, özelliğin I gövdesi ise alanına ibaşvurur.
Bir üyenin tabandan gölgelendirme konusunda uyarır
Birincil oluşturucu parametresi oluşturucu aracılığıyla temel türe geçirilmediyse, bir temel üye birincil oluşturucu parametresini gölgelediğinde, derleyici tanımlayıcı kullanımıyla ilgili bir uyarı oluşturur.
Bir birincil oluşturucu parametresi, class_base bir bağımsız değişken için aşağıdaki koşulların tümü doğru olduğunda oluşturucu aracılığıyla temel türe geçirilir:
- bağımsız değişkeni, birincil oluşturucu parametresinin örtük veya açık kimlik dönüştürmesini temsil eder;
- Bağımsız değişken genişletilmiş
paramsbağımsız değişkenin parçası değildir;
Anlambilim
Birincil oluşturucu, verilen parametrelerle kapsayan türde bir örnek oluşturucu oluşturulmasına yol açar.
class_base bağımsız değişken listesi varsa, oluşturulan örnek oluşturucusunun aynı bağımsız değişken listesine sahip bir base başlatıcısı olur.
Sınıf/yapı bildirimlerindeki birincil oluşturucu parametreleri veya refinolarak bildirilebilirout.
ref Bildirim veya out parametreler kayıt bildiriminin birincil oluşturucularında geçersiz kalır.
Sınıf gövdesindeki tüm örnek üyesi başlatıcıları, oluşturulan oluşturucuda atamalar haline gelir.
Bir birincil oluşturucu parametresine bir örnek üyesinin içinden başvurulsa ve başvuru bir nameof bağımsız değişkenin içinde değilse, oluşturucu sonlandırıldıktan sonra erişilebilir kalması için kapsayan türün durumuna yakalanır. Olası bir uygulama stratejisi, mangled adı kullanan özel bir alan üzerinden yapılır. Salt okunur bir yapıda yakalama alanları salt okunur olacaktır. Bu nedenle, salt okunur yapının yakalanan parametrelerine erişim, salt okunur alanlara erişimle benzer kısıtlamalara sahip olur. Salt okunur üye içinde yakalanan parametrelere erişim, aynı bağlamdaki örnek alanlarına erişimle benzer kısıtlamalara sahip olur.
Ref benzeri türe sahip parametreler için yakalamaya ve veya parametreleri için refinout yakalamaya izin verilmez. Bu, lambdalarda yakalamaya yönelik bir sınırlamaya benzer.
Birincil oluşturucu parametresine yalnızca örnek üyesi başlatıcıları içinden başvurulabiliyorsa, bunlar bir parçası olarak yürütülürken oluşturulan oluşturucunun parametresine doğrudan başvurabilir.
Birincil Oluşturucu aşağıdaki işlem dizisini gerçekleştirir:
- Parametre değerleri varsa yakalama alanlarında depolanır.
- Örnek başlatıcıları yürütülür
- Temel oluşturucu başlatıcısı çağrılır
Herhangi bir kullanıcı kodundaki parametre başvuruları, karşılık gelen yakalama alanı başvuruları ile değiştirilir.
Örneğin bu bildirim:
public class C(bool b, int i, string s) : B(b) // b passed to base constructor
{
public int I { get; set; } = i; // i used for initialization
public string S // s used directly in function members
{
get => s;
set => s = value ?? throw new ArgumentNullException(nameof(value));
}
public C(string s) : this(true, 0, s) { } // must call this(...)
}
Aşağıdakine benzer bir kod oluşturur:
public class C : B
{
public int I { get; set; }
public string S
{
get => __s;
set => __s = value ?? throw new ArgumentNullException(nameof(value));
}
public C(string s) : this(0, s) { ... } // must call this(...)
// generated members
private string __s; // for capture of s
public C(bool b, int i, string s)
{
__s = s; // capture s
I = i; // run I's initializer
B(b) // run B's constructor
}
}
Birincil olmayan oluşturucu bildiriminin birincil oluşturucuyla aynı parametre listesine sahip olması bir hatadır. Birincil olmayan tüm oluşturucu bildirimleri, birincil oluşturucunun sonunda çağrılması için bir this başlatıcı kullanmalıdır.
Birincil oluşturucu parametresi (büyük olasılıkla oluşturulmuş) örnek başlatıcılar veya temel başlatıcı içinde okunmazsa kayıtlar bir uyarı oluşturur. Sınıflarda ve yapılarda birincil oluşturucu parametreleri için benzer uyarılar bildirilir:
- parametresi yakalanmıyorsa ve örnek başlatıcıları veya temel başlatıcı içinde okunmuyorsa, değere göre parametresi için.
- parametresinin herhangi bir
inörnek başlatıcısı veya temel başlatıcı içinde okunmaması durumunda. - parametresi için, parametre herhangi bir
reförnek başlatıcı veya temel başlatıcı içinde okunmuyor veya yazılmıyorsa.
Özdeş basit adlar ve tür adları
Genellikle "Renk Rengi" senaryoları olarak adlandırılan senaryolar için özel bir dil kuralı vardır: Aynı basit adlar ve tür adları.
E.Iformun üye erişiminde,Etek bir tanımlayıcıysa veEolarak anlamı (§12.8.4) sabit, alan, özellik, yerel değişken veya parametreEanlamı ile aynı türdeyse (§7.8.1),Eher iki olası anlamı da izin verilir.E.Iüye araması hiçbir zaman belirsiz değildir, çünküIher iki durumda daEtürüne mutlaka üye olacaktır. Başka bir deyişle, kural aksi takdirde derleme zamanı hatası oluşabilecek yerlerdeE'ın statik üyelerine ve iç içe geçmiş türlerine erişime izin verir.
Birincil oluşturucular açısından kural, örnek üyesindeki bir tanımlayıcının tür başvurusu olarak mı yoksa parametreyi kapsayan türün durumuna yakalayan bir birincil oluşturucu parametre başvurusu olarak mı ele alınması gerektiğini etkiler. "öğesinin üye araması E.I hiçbir zaman belirsiz olmasa da" arama bir üye grubu oluştururken, bazı durumlarda üye erişiminin üye erişimini tam olarak çözümlemeden (bağlamadan) statik üyeye mi yoksa örnek üyeye mi başvurduğu belirlenemez. Aynı zamanda, birincil oluşturucu parametresini yakalamak, kapsayan türün özelliklerini anlam analizini etkileyecek şekilde değiştirir. Örneğin, tür yönetilmeyen hale gelebilir ve bu nedenle bazı kısıtlamalar başarısız olabilir.
Parametrenin yakalanıp yakalanmadığına bağlı olarak bağlamanın her iki şekilde de başarılı olabileceği senaryolar bile vardır. Örneğin:
struct S1(Color Color)
{
public void Test()
{
Color.M1(this); // Error: ambiguity between parameter and typename
}
}
class Color
{
public void M1<T>(T x, int y = 0)
{
System.Console.WriteLine("instance");
}
public static void M1<T>(T x) where T : unmanaged
{
System.Console.WriteLine("static");
}
}
Alıcıyı Color bir değer olarak ele alırsak parametresini yakalarız ve 'S1' yönetilir. Ardından kısıtlama nedeniyle statik yöntem kullanılamaz hale gelir ve örnek yöntemini çağırırız. Ancak, alıcıyı bir tür olarak ele alırsak, parametresini yakalamazız ve 'S1' yönetilmeyen kalır, her iki yöntem de geçerlidir, ancak isteğe bağlı bir parametresi olmadığından statik yöntem "daha iyi" olur. İki seçenek de hataya neden olmaz, ancak her biri farklı davranışlara neden olur.
Bu göz önünde bulundurulduğunda, derleyici aşağıdaki koşulların tümü karşılandığında üye erişimi E.I için bir belirsizlik hatası oluşturur:
- Üye araması
E.I, örneği ve statik üyeleri içeren bir üye grubunu aynı anda verir. Alıcı türü için geçerli olan uzantı yöntemleri, bu denetimin amacı doğrultusunda örnek yöntemler olarak kabul edilir. - Tür adı yerine basit bir ad olarak ele alınırsa
E, birincil oluşturucu parametresine başvurur ve parametreyi kapsayan türün durumuna yakalar.
Çift depolama uyarısı
Bir birincil oluşturucu parametresi tabana geçirilirse ve aynı zamanda yakalanırsa, nesnede yanlışlıkla iki kez depolanması riski yüksektir.
Derleyici, aşağıdaki koşulların tümü doğru olduğunda içinde değer bağımsız değişkeni için in veya değerine göre bir class_baseargument_list uyarı oluşturur:
- bağımsız değişkeni, birincil oluşturucu parametresinin örtük veya açık kimlik dönüştürmesini temsil eder;
- Bağımsız değişken genişletilmiş
paramsbağımsız değişkenin parçası değildir; - Birincil oluşturucu parametresi, kapsayan türün durumuna yakalanır.
Derleyici, aşağıdaki koşulların tümü doğru olduğunda için bir variable_initializer uyarı üretir:
- Değişken başlatıcısı, birincil oluşturucu parametresinin örtük veya açık kimlik dönüştürmesini temsil eder;
- Birincil oluşturucu parametresi, kapsayan türün durumuna yakalanır.
Örneğin:
public class Person(string name)
{
public string Name { get; set; } = name; // warning: initialization
public override string ToString() => name; // capture
}
Birincil oluşturucuları hedefleyen öznitelikler
Teklifi https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-03-13.md benimsemeye https://github.com/dotnet/csharplang/issues/7047 karar verdik.
"method" öznitelik hedefi, parameter_list içeren bir class_declaration/struct_declaration izin verilir ve ilgili birincil oluşturucunun bu özniteliğe sahip olmasına neden olur.
parameter_list olmayan bir class_declaration struct_declarationmethod hedefi olan / öznitelikler bir uyarıyla yoksayılır.
[method: FooAttr] // Good
public partial record Rec(
[property: Foo] int X,
[field: NonSerialized] int Y
);
[method: BarAttr] // warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored.
public partial record Rec
{
public void Frobnicate()
{
...
}
}
[method: Attr] // Good
public record MyUnit1();
[method: Attr] // warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored.
public record MyUnit2;
Kayıtlarda birincil oluşturucular
Bu teklifle, kayıtların artık ayrı bir birincil oluşturucu mekanizması belirtmesi gerekmez. Bunun yerine, birincil oluşturucuları olan kayıt (sınıf ve yapı) bildirimleri şu basit eklemelerle genel kurallara uyar:
- Her birincil oluşturucu parametresi için, aynı ada sahip bir üye zaten varsa, bir örnek özelliği veya alanı olmalıdır. Aksi takdirde, aynı ada sahip bir genel yalnızca başlatma otomatik özelliği parametresinden atanan bir özellik başlatıcı ile sentezlenmiş olur.
- Bir dekonstrükatör, birincil oluşturucu parametreleriyle eşleşecek şekilde out parametreleriyle sentezlenmiş.
- Açık oluşturucu bildirimi , kapsayan türün tek bir parametresini alan bir oluşturucu olan "kopya oluşturucu" ise, başlatıcı
thisçağırmak gerekmez ve kayıt bildiriminde mevcut üye başlatıcıları yürütmez.
Dezavantaj -ları
- Derleyici, sınıfın tam metnine göre birincil oluşturucu parametresi için alan ayrılıp ayrılmayacağını belirlediğinden, oluşturulan nesnelerin ayırma boyutu daha az belirgindir. Bu risk, lambda ifadeleri tarafından değişkenlerin örtük olarak yakalanmasına benzer.
- Ortak bir ayar (veya yanlışlıkla desen), "aynı" parametresini temel sınıftaki korumalı bir alana açıkça atamak yerine oluşturucu zincirine geçirildiğinden birden çok devralma düzeyinde yakalamak ve nesnelerdeki aynı veriler için yinelenen ayırmalara yol açmak olabilir. Bu, bugünün otomatik özellikleri otomatik özelliklerle geçersiz kılma riskine çok benzer.
- Burada önerileceği gibi, genellikle oluşturucu gövdelerde ifade edilebilecek ek mantık için yer yoktur. Aşağıdaki "birincil oluşturucu gövdeleri" uzantısı bunu ele alır.
- Önerilen şekilde, yürütme sırası semantiği normal oluşturucuların içinden kısmen farklıdır ve üye başlatıcıları temel çağrılardan sonraya geciktirilir. Bu muhtemelen düzeltilebilir, ancak uzantı tekliflerinden bazıları (özellikle "birincil oluşturucu gövdeler") karşılığında.
- Teklif yalnızca tek bir oluşturucunun birincil olarak atanabileceği senaryolarda çalışır.
- Sınıfın ve birincil oluşturucunun ayrı erişilebilirliğini ifade etmenin bir yolu yoktur. Ortak oluşturucuların tümünün tek bir özel "build-it-all" oluşturucusna temsilci ataması örnektir. Gerekirse, daha sonra bunun için söz dizimi önerilebilir.
Alternatifler
Yakalama yok
Özelliğin çok daha basit bir sürümü, birincil oluşturucu parametrelerinin üye gövdelerde oluşmasını engeller. Bunlara başvurmak bir hata olabilir. Depolama başlatma kodunun ötesinde isteniyorsa alanların açıkça bildirilmesi gerekir.
public class C(string s)
{
public string S1 => s; // Nope!
public string S2 { get; } = s; // Still allowed
}
Bu, daha sonra tam teklife dönüşebilir ve başlangıçta daha az ortak değerin kaldırılması ve muhtemelen uygun görünmemesi karşılığında bir dizi karar ve karmaşıklıktan kaçınabilir.
Açık olarak oluşturulan alanlar
Alternatif yaklaşım, birincil oluşturucu parametrelerinin her zaman ve görünür olarak aynı ada sahip bir alan oluşturmasıdır. Parametreleri yerel ve anonim işlevlerle aynı şekilde kapatmak yerine, kayıtlardaki birincil construcor parametreleri için oluşturulan genel özelliklere benzer şekilde açıkça oluşturulmuş bir üye bildirimi olacaktır. Kayıtlarda olduğu gibi, uygun bir üye zaten varsa, bir üye oluşturulmaz.
Oluşturulan alan özelse, üye gövdelerde alan olarak kullanılmadığında yine de izlenebilir. Ancak sınıflarda, türetilmiş sınıflarda neden olabileceği durum yinelemesi nedeniyle özel bir alan genellikle doğru seçim olmaz. Burada bir seçenek, sınıflarda korumalı bir alan oluşturmak ve devralma katmanları arasında depolamanın yeniden kullanılmasını teşvik etmek olabilir. Ancak, bildirimi izleyemez ve her birincil oluşturucu parametresi için ayırma maliyetine neden olur.
Bu, kayıt dışı birincil oluşturucuları kayıtlarla daha yakından hizalar; bu durumda üyeler her zaman (en azından kavramsal olarak) oluşturulur( farklı erişim özelliklerine sahip farklı üye türleri olsa da). Ancak, parametrelerin ve yerel ayarların C# dilinde başka bir yerde nasıl yakalanacağı konusunda da şaşırtıcı farklılıklara yol açabilir. Örneğin, yerel sınıflara izin verirsek, kapsayan parametreleri ve yerel öğeleri örtük olarak yakalarlar. Bunlar için görünür bir şekilde gölgelendirme alanları oluşturmak mantıklı bir davranış gibi görünmeyebilir.
Bu yaklaşımda sık sık karşılaşılan bir diğer sorun da birçok geliştiricinin parametreler ve alanlar için farklı adlandırma kurallarına sahip olmasıdır. Birincil oluşturucu parametresi için hangileri kullanılmalıdır? Her iki seçenek de kodun geri kalanında tutarsızlığa yol açar.
Son olarak, görünür bir şekilde üye bildirimleri oluşturmak, gerçekten kayıtlar için oyunun adıdır, ancak kayıt dışı sınıflar ve yapılar için çok daha şaşırtıcı ve "karakter dışı". Sonuç olarak, ana teklifin istendiğinde açık üye bildirimleri için mantıklı davranışla (kayıtlarla tutarlı) örtük yakalamayı seçmesinin nedenleri bunlardır.
Başlatıcı kapsamından örnek üyelerini kaldırma
Yukarıdaki arama kuralları, ilgili üye el ile bildirildiğinde kayıtlardaki birincil oluşturucu parametrelerinin geçerli davranışına izin vermek ve oluşturulmadığında oluşturulan üyenin davranışını açıklamak için tasarlanmıştır. Bu, başvurunun nerede gerçekleştiğine bağlı olarak birincil oluşturucu parametreleri arandığında değiştirilerek yukarıdaki teklifin başardığı "başlatma kapsamı" (bu/temel başlatıcılar, üye başlatıcılar) ve "gövde kapsamı" (üye gövdeleri) arasında arama yapılmasını gerektirir.
Gözlem, başlatıcı kapsamında basit bir ada sahip bir örnek üyesine başvuruda bulunmak her zaman bir hataya yol açar. Bu yerlerde yalnızca örnek üyelerini gölgelendirmek yerine, bunları kapsam dışına alabilir miyiz? Bu şekilde, kapsamların bu garip koşullu sıralaması olmaz.
Bu alternatif muhtemelen mümkündür, ancak biraz uzak ve potansiyel olarak istenmeyen bazı sonuçlar doğurabilir. Her şeyden önce, başlatıcı kapsamından örnek üyelerini kaldırırsak, birincil oluşturucu parametresine değil de örnek üyesine karşılık gelen basit bir ad, tür bildiriminin dışındaki bir şeye yanlışlıkla bağlanabilir! Bu, nadiren kasıtlı olarak yapılacak gibi görünüyor ve bir hata daha iyi olacaktır.
Ayrıca, statik üyeler başlatma kapsamında başvuruda bulunabilir. Bu nedenle, aramada statik ve örnek üyelerini ayırt etmek zorundayız. Bu, bugün gerçekleştirmediğimiz bir işlemdir. (Aşırı yükleme çözünürlüğünü ayırt ediyoruz, ancak bu burada kullanılamaz). Bu nedenle, bunun da değiştirilmesi ve örneğin statik bağlamlarda bir örneğin bir örnek üyesi bulduğu için hata yerine "daha fazla" bağlaması gibi daha fazla duruma neden olması gerekir.
Tüm bu "basitleştirme", kimsenin sormadığı oldukça aşağı akış komplikasyonlarına yol açabilir.
Olası uzantılar
Bunlar, temel teklifle birlikte veya yararlı kabul edilirse daha sonraki bir aşamada dikkate alınabilecek varyasyonlar veya eklemelerdir.
Oluşturucular içinde birincil oluşturucu parametre erişimi
Yukarıdaki kurallar, başka bir oluşturucu içindeki birincil oluşturucu parametresine başvurmayı bir hata haline getirir. Ancak, birincil oluşturucu ilk olarak çalıştığından, diğer oluşturucuların gövdesinde buna izin verilebiliyor. Ancak başlatıcının bağımsız değişken listesinde this buna izin verilmemesi gerekir.
public class C(bool b, int i, string s) : B(b)
{
public C(string s) : this(b, s) // b still disallowed
{
i++; // could be allowed
}
}
Birincil oluşturucu zaten çalıştırıldıktan sonra oluşturucu gövdesinin değişkene ulaşabileceği tek yol bu olduğundan, bu erişim yine de yakalamaya neden olur.
Bu başlatıcının bağımsız değişkenlerindeki birincil oluşturucu parametreleri üzerindeki yasak, izin verecek şekilde zayıflayabilir, ancak bunların kesinlikle atanmamasına neden olabilir, ancak bu kullanışlı görünmüyor.
Başlatıcısı olmayan this oluşturuculara izin ver
Başlatıcısı olmayan this oluşturuculara (örtük veya açık base başlatıcı ile) izin verilebiliyor. Bu tür bir oluşturucu örnek alanı, özellik ve olay başlatıcıları çalıştırmaz , bu nedenle bunlar yalnızca birincil oluşturucunun parçası olarak kabul edilir.
Bu tür temel çağrı oluşturucuların varlığında, birincil oluşturucu parametre yakalamanın nasıl işleneceğini gösteren birkaç seçenek vardır. En basiti, bu durumda yakalamaya tamamen izin vermemektir. Birincil oluşturucu parametreleri yalnızca bu oluşturucular mevcut olduğunda başlatmaya yönelik olabilir.
Alternatif olarak, oluşturucular içinde birincil oluşturucu parametrelerine erişime izin vermek için önceden açıklanan seçenekle birleştirilirse, parametreler oluşturucu gövdesine kesinlikle atanmamış olarak girebilir ve yakalananların oluşturucu gövdesinin sonuna kesinlikle atanması gerekir. Bunlar temelde örtük parametreler olacaktır. Bu şekilde, yakalanan birincil oluşturucu parametreleri her zaman diğer işlev üyeleri tarafından tüketilene kadar duyarlı (açıkça atanmış) bir değere sahip olur.
Bu uzantının bir cazibesi (her iki biçimde de), başlatılmamış birincil oluşturucu parametrelerinin gözlemlendiği durumlara yol açmadan kayıtlardaki "kopya oluşturucuları" için geçerli muafiyeti tamamen genelleştirmesidir. Temel olarak, nesneyi alternatif yollarla başlatan oluşturucular uygundur. Kayıtlarda el ile tanımlanan mevcut kopya oluşturucuları için yakalamayla ilgili kısıtlamalar hataya neden olmayacaktır çünkü kayıtlar birincil oluşturucu parametrelerini hiçbir zaman yakalamaz (bunun yerine alanlar oluştururlar).
public class C(bool b, int i, string s) : B(b)
{
public int I { get; set; } = i; // i used for initialization
public string S // s used directly in function members
{
get => s;
set => s = value ?? throw new ArgumentNullException(nameof(value));
}
public C(string s2) : base(true) // cannot use `string s` because it would shadow
{
s = s2; // must initialize s because it is captured by S
}
protected C(C original) : base(original) // copy constructor
{
this.s = original.s; // assignment to b and i not required because not captured
}
}
Birincil oluşturucu gövdeleri
Oluşturucular genellikle parametre doğrulama mantığı veya başlatıcı olarak ifade edilemeyen başka bir başlatma kodu içerir.
Birincil oluşturucular, deyim bloklarının doğrudan sınıf gövdesinde görünmesine izin verecek şekilde genişletilebilir. Bu deyimler, oluşturulan oluşturucuya atamaları başlatma arasında göründükleri noktada eklenir ve böylece başlatıcılarla birlikte yürütülür. Örneğin:
public class C(int i, string s) : B(s)
{
{
if (i < 0) throw new ArgumentOutOfRangeException(nameof(i));
}
int[] a = new int[i];
public int S => s;
}
Oluşturucular ve herhangi bir nesne/koleksiyon başlatıcısı tamamlandıktan sonra çalıştırılan "son başlatıcıları" tanıtacaksak, bu senaryonun çoğu yeterli şekilde ele alınabilir. Ancak, bağımsız değişken doğrulama ideal olarak mümkün olan en erken zamanda gerçekleşebilecek bir şeydir.
Birincil oluşturucu gövdeleri, birincil oluşturucu için bir erişim değiştiriciye izin vermek için bir yer de sağlayabilir ve bunun kapsayan türün erişilebilirliğinden sapmasını sağlayabilir.
Birleştirilmiş parametre ve üye bildirimleri
Olası ve sık bahsedilen bir ekleme, birincil oluşturucu parametrelerinin tür üzerinde bir üye bildirmesi için ek açıklama eklenmesine izin vermek olabilir. En yaygın olarak, üye oluşturma işlemini tetikleyen parametrelerde bir erişim tanımlayıcısına izin vermek önerilir:
public class C(bool b, protected int i, string s) : B(b) // i is a field as well as a parameter
{
void M()
{
... i ... // refers to the field i
... s ... // closes over the parameter s
}
}
Bazı sorunlar vardır:
- Alan değil de bir özellik istenirse ne olur?
{ get; set; }Parametre listesinde satır içi söz dizimini bulundurmak hiç de işe yarmıyor. - Parametreler ve alanlar için farklı adlandırma kuralları kullanılıyorsa ne olur? O zaman bu özellik işe yaramaz.
Bu, benimsenebilecek veya benimsenmeyecek olası bir eklemedir. Mevcut teklif, olasılığı açık bırakıyor.
Açık sorular
Tür parametreleri için arama sırası
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/primary-constructors.md#lookup bölümü, bildirim türündeki tür parametrelerinin, bu parametrelerin kapsamda olduğu her bağlamda türün birincil oluşturucu parametrelerinden önce gelmesi gerektiğini belirtir. Ancak, kayıtlarda zaten mevcut bir davranışımız var - birincil oluşturucu parametreleri, temel başlatıcı ve alan başlatıcılarındaki tür parametreleri öncesinde gelir.
Bu tutarsızlık konusunda ne yapmalıyız?
- Kuralları davranışla eşleşecek şekilde ayarlayın.
- Davranışı ayarlayın (olası bir hata değişikliği).
- İlkel oluşturucu parametresinin tür parametresinin adını kullanmasına izin verme (olası bir hata değişikliği).
- Hiçbir şey yapma, belirtim ve uygulama arasındaki tutarsızlığı kabul et.
Sonuç:
Kuralları davranışla (https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-09-25.md#primary-constructors) eşleşecek şekilde ayarlayın.
Yakalanan birincil oluşturucu parametreleri için alan hedefleme öznitelikleri
Yakalanan birincil oluşturucu parametreleri için alan hedefleme özniteliklerine izin vermeli miyiz?
class C1([field: Test] int x) // Parameter is captured, the attribute goes to the capture field
{
public int X => x;
}
class C2([field: Test] int x) // Parameter is not captured, the attribute is ignored with a warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'param'. All attributes in this block will be ignored.
{
public int X = x;
}
Şu anda parametrenin yakalanıp yakalanmadığına bakılmaksızın öznitelikler uyarıyla yoksayılır.
Kayıtlar için bir özellik sentezlendiğinde alan hedefli özniteliklere izin verildiğini unutmayın. Ardından öznitelikler yedekleme alanına gider.
record R1([field: Test]int X); // Ok, the attribute goes on the backing field
record R2([field: Test]int X) // warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'param'. All attributes in this block will be ignored.
{
public int X = X;
}
Sonuç:
İzin verilmiyor (https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#attributes-on-captured-parameters).
Bir üyenin tabandan gölgelendirme konusunda uyarır
Tabandan bir üye bir üyenin içindeki birincil oluşturucu parametresini gölgelediğinde bir uyarı bildirmeli miyiz (bkz https://github.com/dotnet/csharplang/discussions/7109#discussioncomment-5666621.)?
Sonuç:
Alternatif bir tasarım onaylandı - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-08.md#primary-constructors
Kapatmada kapsayan türün örneğini yakalama
Kapsayan türün durumuna yakalanan bir parametreye örnek başlatıcı veya temel başlatıcı içindeki bir lambda'da da başvurulduğunda, lambda ve kapsayan türün durumu parametre için aynı konuma başvurmalıdır. Örneğin:
partial class C1
{
public System.Func<int> F1 = Execute1(() => p1++);
}
partial class C1 (int p1)
{
public int M1() { return p1++; }
static System.Func<int> Execute1(System.Func<int> f)
{
_ = f();
return f;
}
}
Bir parametreyi türün durumuna yakalamanın basit bir uygulaması parametreyi özel örnek alanında yakaladığından, lambdanın aynı alana başvurması gerekir. Sonuç olarak, türün örneğine erişebilmesi gerekir. Bu, temel oluşturucu çağrılmadan önce bir kapanışın yakalanmasını this gerektirir. Bu da güvenli ama kesin olmayan bir IL ile sonuçlanabilir. Bu kabul edilebilir mi?
Alternatif olarak:
- Böyle lambdalara izin verme;
- Bunun yerine, bunun gibi parametreleri ayrı bir sınıfın örneğinde (ancak başka bir kapanış) yakalayın ve bu örneği kapatma ile kapsayan türün örneği arasında paylaşın. Böylece bir kapanışta yakalama
thisihtiyacını ortadan kaldırır.
Sonuç:
Temel oluşturucu çağrılmadanhttps://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-15.md ( ) önce bir kapanışı yakalama this konusunda rahatız.
Çalışma zamanı ekibi IL desenini de sorunlu bulmadı.
Bir yapı içinde öğesine this atama
C# bir yapı içinde öğesine this atamaya izin verir. Yapı bir birincil oluşturucu parametresi yakalarsa, atama değerinin üzerine yazılır ve bu kullanıcı için belirgin olmayabilir. Bunun gibi ödevler için bir uyarı bildirmek istiyor musunuz?
struct S(int x)
{
int X => x;
void M(S s)
{
this = s; // 'x' is overwritten
}
}
Sonuç:
İzin verildi, uyarı yok (https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-15.md).
Başlatma ve yakalama için çift depolama uyarısı
Birincil oluşturucu parametresi tabana geçirilirse ve aynı zamanda yakalanırsa bir uyarımız olur çünkü nesnede yanlışlıkla iki kez depolanması riski yüksektir.
Bir üyeyi başlatmak için bir parametre kullanıldığında ve ayrıca yakalanırsa benzer bir risk olduğu görülüyor. İşte küçük bir örnek:
public class Person(string name)
{
public string Name { get; set; } = name; // initialization
public override string ToString() => name; // capture
}
öğesinin Personbelirli bir örneği için Name , üzerinde yapılan değişiklikler, büyük olasılıkla geliştiricinin tarafında istenmeyen olan çıktısına ToStringyansıtılmaz.
Bu durum için bir çift depolama uyarısı mı sunmalıyız?
Şu şekilde çalışır:
Derleyici, aşağıdaki koşulların tümü doğru olduğunda için bir variable_initializer uyarı oluşturur:
- Değişken başlatıcısı, birincil oluşturucu parametresinin örtük veya açık kimlik dönüştürmesini temsil eder;
- Birincil oluşturucu parametresi, kapsayan türün durumuna yakalanır.
Sonuç:
Onaylandı, bkz. https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-15.md#primary-constructors
LDM toplantıları
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-17.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-01-18.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-15.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-22.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-03-13.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#primary-constructors
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-08.md#primary-constructors
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-15.md#primary-constructors
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-09-25.md#primary-constructors
C# feature specifications