Yerel değişkenler neden başlatma gerektiriyor, ancak alanlar gerektirmiyor?


140

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?


Yorumlar uzun tartışmalar için değildir; bu görüşme sohbete taşındı .
Martijn Pieters

14
Soru belirsiz. "Spesifikasyon böyle söylediği için" kabul edilebilir bir cevap olur mu?
Eric Lippert

4
Çünkü kopyalarken Java'da böyle yapıldı. : P
Alvin Thompson

Yanıtlar:


177

Yuval ve David'in cevapları temel olarak doğrudur; özetliyor:

  • Atanmamış bir yerel değişkenin kullanılması olası bir hatadır ve bu derleyici tarafından düşük maliyetle algılanabilir.
  • Atanmamış bir alanın veya dizi öğesinin kullanımının bir hata olması daha az olasıdır ve derleyicideki durumu algılamak daha zordur. Bu nedenle derleyici, alanlar için başlatılmamış bir değişkenin kullanımını saptama girişiminde bulunmaz ve bunun yerine program davranışını belirleyici hale getirmek için varsayılan değere başlatmaya dayanır.

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:

  • Ortak, güvenli, sık kullanılan programlama deyimlerini yasadışı hale getirin.
  • Muhtemelen orada olmayan hataları aramak için derlemenin saatler sürmesini sağlayan pahalı bir tam program analizi yapın.
  • Varsayılan değerlere otomatik başlatmaya güvenin.

Tasarım ekibi üçüncü seçeneği seçti.


1
Her zamanki gibi harika bir cevap. Gerçi bir sorum var: Neden otomatik olarak yerel değişkenlere varsayılan değerleri atamıyorsunuz? Başka bir deyişle, neden bir yöntemin içinde bilebool x; eşdeğer olmasın ? bool x = false;
durron597

8
@ durron597: Çünkü deneyim yerel bir değer atamayı unutmanın muhtemelen bir hata olduğunu gösterdi. Muhtemelen bir hata ve ucuz ve tespit edilmesi kolaysa, davranışı yasadışı veya uyarı yapmak için iyi bir teşvik vardır.
Eric Lippert

27

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.

falseBu ö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 = falseifadelerle 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.


"bunun bir referans türü olduğunu hayal edin ve bu başlatılmamış nesneyi başlatılmış olanı bekleyen bir yönteme geçirirsiniz" Bunu mu demek istediniz: "bunun bir referans türü olduğunu ve bir referansın yerine varsayılan (null) iletildiğini düşünün nesne"?
Tekilleştirici

@ Dupliclicator Evet. Boş olmayan bir değer bekleyen bir yöntem. Bu bölümü düzenledi. Umarım şimdi daha açıktır.
Yuval Itzchakov

Bunun çizilmiş çizgi yüzünden olduğunu sanmıyorum. Her sınıf bir kurucuya, en azından varsayılan kurucuya sahip olduğunu varsayar. Bu nedenle, varsayılan kurucuya sadık kaldığınızda varsayılan değerleri alırsınız (sessiz saydam). Bir kurucu tanımlarken, içinde ne yaptığınızı ve varsayılan değerlerin bilgisi de dahil olmak üzere hangi alanlara hangi yollarla başlatmak istediğinizi bilmeniz ya da bilmesi beklenir.
Peter

Tersi: Bir yöntem içindeki bir alan, farklı yürütme yollarına değerler bildirilebilir ve atanabilir. Kullanabileceğiniz bir çerçevenin belgelerine veya kodun diğer bölümlerinde bakamayacağınız sürece kolayca denetlenmesi gereken istisnalar olabilir. Bu çok karmaşık bir yürütme yolu getirebilir. Bu nedenle derleyiciler ipucu verir.
Peter

@Peter İkinci yorumunuzu gerçekten anlamadım. Birincisi ile ilgili olarak, bir kurucu içindeki herhangi bir alanın başlatılması gerekmez. Bu yaygın bir uygulamadır . Derleyicilerin işi böyle bir uygulamayı zorlamak değildir. Çalışmakta olan bir kurucu uygulamasına güvenemez ve "tamam, tüm alanlar gitmek iyidir" diyemezsiniz. Eric, bir sınıfın bir alanını nasıl başlatabileceğine dair cevabını bolca detaylandırdı ve tüm mantıksal yolların başlatılmasının hesaplanmasının çok uzun zaman .
Yuval Itzchakov

25

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ı, arraybaş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.


"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." Bunun doğru olduğuna ikna olmadım. Statik analize dirençli bir program örneği gönderebilir misiniz?
John Kugelman

@JohnKugelman, basit durumunu 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.
David Arno

Bu doğru, ancak derleme sırasında hata oluşmasını beklemiyordum 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.
John Kugelman

@JohnKugelman: Ortaya koyduğunuz sorunu tartışan bir cevap göndereceğim.
Eric Lippert

4
Adil değil. Burada bir anlaşmazlık yapmaya çalışıyoruz!
John Kugelman

10

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.

Sınıf

class Foo {
    private string Boo;
    public Foo() { /** bla bla bla **/ }
    public string DoSomething() { return Boo; }
}

Mülkiyet Booolabilir 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 .

fonksiyon

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 neden var?

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.