Sınıfımda bir bool oluşturursam, tıpkı bir şey gibi bool check
, varsayılan olarak false olur.
Benim yöntem içinde aynı bool oluşturduğunuzda, bool check
(sınıf içinde yerine), "atanmamış yerel değişken denetimi kullanımı" hata alıyorum. Neden?
Sınıfımda bir bool oluşturursam, tıpkı bir şey gibi bool check
, varsayılan olarak false olur.
Benim yöntem içinde aynı bool oluşturduğunuzda, bool check
(sınıf içinde yerine), "atanmamış yerel değişken denetimi kullanımı" hata alıyorum. Neden?
Yanıtlar:
Yuval ve David'in cevapları temel olarak doğrudur; özetliyor:
David'in cevabına gelen bir yorumcu, atanmamış bir alanın kullanımını statik analizle neden tespit etmenin imkansız olduğunu sorar; bu cevapta genişlemek istediğim nokta bu.
Öncelikle, herhangi bir değişken için, yerel veya başka bir şekilde, bir değişkenin tam olarak atanıp atanmadığını tam olarak belirlemek imkansızdır . Düşünmek:
bool x;
if (M()) x = true;
Console.WriteLine(x);
"X atanmış mı?" Sorusu. "M () doğru mu dönüyor?" Şimdi, eğer Fermat'ın Son Teoremi'nin yirmi gajillion'dan daha küçük tüm tamsayılar için doğru olduğunu, aksi takdirde yanlış olduğunu varsayalım. X'in kesinlikle atanıp atanmadığını belirlemek için, derleyici esasen Fermat'ın Son Teoreminin bir kanıtını üretmelidir. Derleyici o kadar akıllı değil.
Derleyicinin bunun yerine yerliler için yaptığı şey, hızlı bir algoritma uygular ve bir yerel kesinlikle atanmadığında fazla tahmin eder. Yani, siz ve ben bildiğim halde, "Bu yerelin atandığını kanıtlayamam" diyen bazı yanlış pozitifleri var. Örneğin:
bool x;
if (N() * 0 == 0) x = true;
Console.WriteLine(x);
Diyelim ki N () bir tamsayı döndürüyor. Sen ve ben N () * 0'ın 0 olacağını biliyoruz, ancak derleyici bunu bilmiyor. (Not: C # 2.0 derleyicisi bunu biliyordu , ancak şartname derleyicinin bildiğini söylemediği için bu optimizasyonu kaldırdım .)
Pekala, şu ana kadar ne biliyoruz? Yerliler için kesin bir cevap almak pratik değildir, ancak atanmamış olanları ucuza tahmin edebilir ve "belirsiz programınızı düzeltmenizi sağlayın" tarafında oldukça iyi bir sonuç elde edebiliriz. Bu iyi. Neden aynı şeyi alanlar için yapmıyorsunuz? Yani, ucuza fazla tahmin kesin bir atama denetleyicisi yapmak?
Peki, bir yerelin başlatılması için kaç yol var? Yöntemin metninde atanabilir. Yöntem metninde bir lambda içinde atanabilir; bu lambda asla çağrılmayabilir, bu nedenle bu görevler ilgili değildir. Ya da, anothe yöntemine "dışarı" olarak geçirilebilir, bu noktada yöntem normal olarak döndüğünde atanmış olduğunu varsayabiliriz. Bunlar, yerelin atandığı çok açık noktalar ve yerel olarak beyan edilen yöntemle oradalar . Yerliler için kesin atamanın belirlenmesi sadece yerel analiz gerektirir . Yöntemler kısa olma eğilimindedir - bir yöntemde bir milyon satırdan daha az kod - ve bu nedenle tüm yöntemi analiz etmek oldukça hızlıdır.
Peki ya alanlar? Alanlar elbette bir kurucuda başlatılabilir. Veya bir alan başlatıcı. Veya yapıcı, alanları başlatan bir örnek yöntemini çağırabilir. Ya da kurucu , alanları başlatan sanal bir yöntem çağırabilir. Veya kurucu , alanları başlatan başka bir sınıfta , kitaplıkta olabilecek bir yöntemi çağırabilir . Statik alanlar statik yapıcılarda başlatılabilir. Statik alanlar diğer statik yapıcılar tarafından başlatılabilir .
Bir alanın başlatıcısı, henüz yazılmamış kitaplıklarda bildirilecek olan sanal yöntemler dahil olmak üzere tüm programın herhangi bir yerinde olabilir :
// Library written by BarCorp
public abstract class Bar
{
// Derived class is responsible for initializing x.
protected int x;
protected abstract void InitializeX();
public void M()
{
InitializeX();
Console.WriteLine(x);
}
}
Bu kütüphaneyi derlemek bir hata mı? Evetse, BarCorp'un hatayı nasıl düzeltmesi gerekiyor? X'e varsayılan bir değer atayarak? Ancak derleyici zaten bunu yapıyor.
Bu kütüphanenin yasal olduğunu varsayalım. FooCorp yazarsa
public class Foo : Bar
{
protected override void InitializeX() { }
}
olan bu bir hata? Derleyicinin bunu nasıl anlaması gerekiyor? Tek yol, çalışma zamanında sanal yöntemlerin seçimini içeren yollar da dahil olmak üzere , program boyunca mümkün olan her yoldaki her alanın başlatma statikini izleyen bir program analizi yapmaktır . Bu problem keyfi olarak zor olabilir ; milyonlarca kontrol yolunun simüle edilmiş yürütülmesini içerebilir. Lokal kontrol akışlarının analizi mikrosaniye alır ve yöntemin boyutuna bağlıdır. Global kontrol akışlarının analizi saatler alabilir çünkü programdaki her yöntemin ve tüm kütüphanelerin karmaşıklığına bağlıdır .
Öyleyse neden tüm programı analiz etmek zorunda olmayan ve daha da ciddi şekilde daha fazla tahmin eden daha ucuz bir analiz yapmıyorsunuz? Aslında, derleyen doğru bir program yazmayı zorlaştırmayan bir algoritma önerin ve tasarım ekibi bunu düşünebilir. Böyle bir algoritma bilmiyorum.
Şimdi, yorumcu "bir kurucunun tüm alanları başlatmasını zorunlu kıl" öneriyor. Bu kötü bir fikir değil. Aslında, o kadar da kötü bir fikir değildir ki, C #, yapılar için zaten bu özelliğe sahiptir . Ctor normal olarak döndüğü zamana kadar tüm alanları atamak için bir yapı kurucusu gerekir; varsayılan kurucu tüm alanları varsayılan değerlerine sıfırlar.
Sınıflar ne olacak? Eh, nasıl bir yapıcı bir alan başlatıldığını biliyoruz ? Ctor , alanları başlatmak için sanal bir yöntem çağırabilir ve şimdi daha önce bulunduğumuz konuma geri döndük. Yapılar türetilmiş sınıflara sahip değildir; sınıflar olabilir. Soyut bir sınıf içeren bir kütüphanenin tüm alanlarını başlatan bir kurucu içermesi gerekir mi? Soyut sınıf, alanların hangi değerlere başlatılması gerektiğini nasıl biliyor?
John, alanlar başlatılmadan önce bir ctor'daki çağrı yöntemlerinin yasaklanmasını önerir. Özetle, seçeneklerimiz:
Tasarım ekibi üçüncü seçeneği seçti.
bool x;
eşdeğer olmasın ? bool x = false;
Benim yöntem, bool denetimi (sınıf içinde yerine) içinde aynı bool oluşturduğunuzda, "atanmamış yerel değişken denetimi kullanımı" hata alıyorum. Neden?
Çünkü derleyici hata yapmanıza engel olmaya çalışıyor.
false
Bu özel yürütme yolundaki herhangi bir şeyi değiştirmek için değişkeninizi başlatmak mı ? Muhtemelen hayır, düşünmek default(bool)
yine de yanlıştır, ancak bunun gerçekleştiğinin farkında olmanız için sizi zorlamaktadır . .NET ortamı, varsayılan değerlerine herhangi bir değer atayacağından "çöp belleğine" erişmenizi engeller. Ancak yine de bunun bir referans türü olduğunu ve null olmayan bir değer bekleyen bir yönteme başlatılmamış (null) bir değer iletip çalışma zamanında bir NRE elde edeceğinizi düşünün. Derleyici bunu önlemeye çalışıyor ve bunun bazen bool b = false
ifadelerle sonuçlanabileceğini kabul ediyor .
Eric Lippert bunun hakkında bir blog yazısında konuşuyor :
Bunu yasadışı yapmak istememizin nedeni, birçok insanın inandığı gibi değil, çünkü yerel değişken çöp olarak başlatılacak ve sizi çöplerden korumak istiyoruz. Aslında, yerel ayarları varsayılan değerlerine otomatik olarak başlatırız. (C ve C ++ programlama dilleri, başlatılmamış bir yerelden çöpü okumanıza neşeyle izin vermesine rağmen .) Bunun nedeni, böyle bir kod yolunun varlığının muhtemelen bir hata olması ve sizi kalite çukuru; bu hatayı yazmak için çok çalışmalısınız.
Bu neden bir sınıf alanı için geçerli değil? Çizginin bir yere çekilmesi gerektiğini varsayıyorum ve yerel değişkenlerin başlatılması, sınıf alanlarının aksine teşhis ve doğru elde etmek için çok daha kolay. Derleyici bunu yapabilir, ancak bir sınıftaki her alanın başlatılıp başlatılmadığını değerlendirmek için (bazılarının sınıf kodunun kendisinden bağımsız olduğu) yapılması gereken tüm olası kontrolleri düşünün. Derleyici tasarımcısı değilim, ancak dikkate alınan ve vaktinde de yapılması gereken çok sayıda vaka olduğu için kesinlikle daha zor olacağından eminim . Tasarlamanız, yazmanız, test etmeniz ve konuşlandırmanız gereken her özellik için, bu çabanın aksine bunu uygulamanın değeri, değerli ve karmaşık olacaktır.
Yerel değişkenler neden başlatma gerektiriyor, ancak alanlar gerektirmiyor?
Kısa cevap, başlatılmamış yerel değişkenlere erişen kodun derleyici tarafından statik analiz kullanılarak güvenilir bir şekilde algılanabilmesidir. Oysa bu alanların durumu değildir. Böylece derleyici ilk durumu uygular, ancak ikincisini zorlamaz.
Yerel değişkenler neden başlatma gerektirir?
Bu, Eric Lippert'in açıkladığı gibi C # dilinin tasarım kararından başka bir şey değildir . CLR ve .NET ortamı gerektirmez. Örneğin VB.NET, başlatılmamış yerel değişkenlerle gayet iyi derlenecektir ve gerçekte CLR başlatılmamış tüm değişkenleri varsayılan değerlere başlatır.
Aynı şey C # ile de olabilir, ancak dil tasarımcıları bunu yapmamayı seçti. Bunun nedeni, başlatılmış değişkenlerin büyük bir hata kaynağı olmasıdır ve bu nedenle, başlatılmasını zorunlu kılarak, derleyici yanlışlıkla yapılan hataları azaltmaya yardımcı olur.
Alanlar neden başlatma gerektirmiyor?
Öyleyse bu zorunlu açık başlatma neden bir sınıf içindeki alanlarla gerçekleşmiyor? Bu açık başlatma, inşaat sırasında, bir nesne başlatıcısı tarafından çağrılan bir özellik veya olaydan çok sonra çağrılan bir yöntemle gerçekleşebileceğinden dolayı. Derleyici, koddaki her olası yolun değişkenin bizden açıkça başlatılmasına neden olup olmadığını belirlemek için statik analizi kullanamaz. Yanlış yapmak can sıkıcı olurdu, çünkü geliştirici derlenmeyecek geçerli bir kodla bırakılabilir. Bu yüzden C # bunu hiç zorlamaz ve CLR, açıkça ayarlanmadığı takdirde alanları otomatik olarak varsayılan bir değere başlatmak için bırakılır.
Koleksiyon türleri ne olacak?
C # 'ın yerel değişken başlatmanın uygulanması sınırlıdır, bu da genellikle geliştiricileri yakalar. Aşağıdaki dört kod satırını göz önünde bulundurun:
string str;
var len1 = str.Length;
var array = new string[10];
var len2 = array[0].Length;
Başlatılmamış bir dize değişkenini okumaya çalıştığından, ikinci kod satırı derlenmeyecektir. Dördüncü kod satırı, array
başlatıldığı gibi iyi derlenir , ancak yalnızca varsayılan değerlerle derlenir . Bir dizenin varsayılan değeri null olduğundan, çalışma zamanında bir istisna alırız. Burada Stack Overflow üzerinde zaman geçiren herkes, bu açık / örtülü başlatma tutarsızlığının çok sayıda "Neden bir nesnenin örneğine ayarlanmayan nesne referansı alıyorum" hatasına yol açtığını bilecektir. sorular.
public interface I1 { string str {get;set;} }
ve bir yöntem düşünün int f(I1 value) { return value.str.Length; }
. Bu bir kütüphanede varsa, derleyici böylece, ister o kütüphane ile bağlantılı ne olacağını bilemez set
önce denilen olacaktır get
, destek alan açıkça başlatılmak olmayabilir, ama bu tür kodu derlemek için vardır.
f
. Yapıcılar derlenirken üretilirdi. Başlatılmamış bir alanı olan bir kurucu bırakırsanız, bu bir hata olur. Ayrıca, tüm alanlar başlatılmadan önce sınıf yöntemlerini ve alıcıları çağırmak için kısıtlamalar olması gerekebilir.
Yukarıdaki iyi yanıtlar, ancak insanların uzun bir tane (kendim gibi) okumak için tembel olmaları için çok daha basit / kısa bir cevap göndereceğimi düşündüm.
class Foo {
private string Boo;
public Foo() { /** bla bla bla **/ }
public string DoSomething() { return Boo; }
}
Mülkiyet Boo
olabilir veya olabilir değil yapıcı başlatılmış edilmiştir. Bulduğu Yani return Boo;
o değil varsayalım o başlatıldı emin olabiliyoruz. Sadece hatayı bastırır .
public string Foo() {
string Boo;
return Boo; // triggers error
}
{ }
Karakter kod bloğu kapsamını tanımlamaktadırlar. Derleyici, bu { }
blokların dallarını yürüyor . Bunun başlatılmadığını kolayca söyleyebilir Boo
. Hata daha sonra tetiklenir.
Hata, kaynak kodu güvenli hale getirmek için gerekli kod satırı sayısını azaltmak için verilmiştir. Hata olmadan, yukarıdaki gibi görünecektir.
public string Foo() {
string Boo;
/* bla bla bla */
if(Boo == null) {
return "";
}
return Boo;
}
Kılavuzdan:
C # derleyicisi başlatılmamış değişkenlerin kullanımına izin vermez. Derleyici, başlatılmamış olabilecek bir değişkenin kullanımını algılarsa, CS0165 derleyici hatası oluşturur. Daha fazla bilgi için, bkz. Alanlar (C # Programlama Kılavuzu). Bu hatanın, derleyiciniz atanmamış bir değişkenin kullanılmasına neden olabilecek bir yapı ile karşılaştığında, özel kodunuz olmasa bile oluştuğunu unutmayın. Bu, kesin görevlendirme için aşırı karmaşık kuralların gerekliliğini ortadan kaldırır.
Referans: https://msdn.microsoft.com/en-us/library/4y7h161d.aspx