Bu soru 20 Eylül 2010'daki blogumun konusuydu . Josh ve Chad'in cevapları ("hiçbir değer katmıyorlar, öyleyse neden gerekli?" Ve "fazlalığı ortadan kaldırmak için") temelde doğrudur. Bunu biraz daha açıklamak için:
Nesne başlatıcıların "daha büyük özelliği" nin bir parçası olarak argüman listesinden çıkmanıza izin verme özelliği, "şekerli" özellikler için çıtamızı karşıladı. Düşündüğümüz bazı noktalar:
- tasarım ve spesifikasyon maliyeti düşüktü
- yine de nesne oluşturmayı işleyen ayrıştırıcı kodunu kapsamlı bir şekilde değiştirecektik; Parametre listesini isteğe bağlı yapmanın ek geliştirme maliyeti, daha büyük özelliğin maliyetine kıyasla yüksek değildi
- test yükü, daha büyük özelliğin maliyetine kıyasla nispeten küçüktü
- dokümantasyon yükü nispeten küçüktü ...
- bakım yükünün küçük olması bekleniyordu; Gönderildiğinden beri bu özellikte bildirilen herhangi bir hatayı hatırlamıyorum.
- özellik, bu alandaki gelecekteki özellikler için hemen açık bir risk oluşturmaz. (Yapmak istediğimiz son şey, gelecekte daha çekici bir özelliği uygulamayı çok daha zor hale getiren ucuz, kolay bir özellik yapmaktır.)
- özellik, dilin sözcüksel, dilbilgisel veya anlambilimsel analizine yeni belirsizlikler eklemez. Siz yazarken IDE'nin "IntelliSense" altyapısı tarafından gerçekleştirilen "kısmi program" analizi türü için sorun oluşturmaz. Ve bunun gibi.
- özellik, daha büyük nesne başlatma özelliği için ortak bir "tatlı noktaya" ulaşır; Nesnenin yapıcısı çünkü o tam da başlatıcısı Bir nesneyi kullanırken genellikle eğer değil istediğiniz özellikleri ayarlamak için izin verir. Bu tür nesnelerin, ilk etapta ctor'da hiçbir parametresi olmayan "özellik çantaları" olması çok yaygındır.
Öyleyse neden bir nesne başlatıcıya sahip olmayan bir nesne oluşturma ifadesinin varsayılan yapıcı çağrısında boş parantezleri isteğe bağlı olarak da yapmadınız ?
Yukarıdaki kriter listesine bir kez daha bakın. Bunlardan biri, değişikliğin bir programın sözcüksel, gramer veya anlamsal analizinde yeni bir belirsizlik getirmemesidir. Sizin önerilen değişiklik yapar bir semantik analiz belirsizlik tanıtmak:
class P
{
class B
{
public class M { }
}
class C : B
{
new public void M(){}
}
static void Main()
{
new C().M(); // 1
new C.M(); // 2
}
}
Satır 1 yeni bir C oluşturur, varsayılan kurucuyu çağırır ve ardından yeni nesne üzerinde örnek yöntemini M çağırır. Satır 2, yeni bir BM örneği oluşturur ve varsayılan kurucusunu çağırır. 1. satırdaki parantezler isteğe bağlı olsaydı, 2. satır belirsiz olurdu. O zaman belirsizliği çözen bir kural bulmalıyız; bunu bir hata yapamadık çünkü o zaman bu, mevcut bir yasal C # programını bozuk bir programa dönüştüren bir son değişiklik olur.
Bu nedenle, kuralın çok karmaşık olması gerekir: esasen parantezler yalnızca belirsizliklere neden olmadıkları durumlarda isteğe bağlıdır. Belirsizlikleri ortaya çıkaran tüm olası durumları analiz etmemiz ve ardından bunları tespit etmek için derleyiciye kod yazmamız gerekir.
O ışıkta geri dönün ve bahsettiğim tüm maliyetlere bakın. Şimdi kaç tanesi büyüdü? Karmaşık kuralların büyük tasarım, özellik, geliştirme, test ve dokümantasyon maliyetleri vardır. Karmaşık kuralların, gelecekte özelliklerle beklenmedik etkileşimlerle sorunlara neden olma olasılığı çok daha yüksektir.
Hepsi ne için? Dile yeni bir temsil gücü eklemeyen, ancak onunla karşılaşan zavallı, şüphesiz bir ruha "anladım" diye bağırmayı bekleyen çılgın köşe vakaları ekleyen küçük bir müşteri avantajı. Bunun gibi özellikler hemen kesilir ve "bunu asla yapma" listesine eklenir.
Bu belirsizliği nasıl belirlediniz?
Bu hemen anlaşıldı; Noktalı bir adın ne zaman beklendiğini belirlemek için C #'daki kurallara oldukça aşinayım.
Yeni bir özelliği değerlendirirken herhangi bir belirsizliğe yol açıp açmadığını nasıl belirlersiniz? Elle, resmi kanıtla, makine analiziyle, ne?
Her üçü. Çoğunlukla, yukarıda yaptığım gibi, sadece özelliklerine ve üzerindeki erişteye bakarız. Örneğin, C # için "frob" adında yeni bir önek operatörü eklemek istediğimizi varsayalım:
x = frob 123 + 456;
(GÜNCELLEME: frob
elbette await
; buradaki analiz, esasen tasarım ekibinin eklerken uyguladığı analizdir await
.)
"frob" burada "yeni" veya "++" gibidir - bir tür ifadeden önce gelir. İstenilen önceliği ve çağrışımı vb. Hesaplar ve ardından "programın zaten frob adı verilen bir türü, alanı, özelliği, olayı, yöntemi, sabiti veya yerel'i varsa?" Gibi sorular sormaya başlarız. Bu hemen aşağıdaki gibi durumlara yol açar:
frob x = 10;
Bu, "x = 10 sonucu üzerinde frob işlemi yapmak mı, yoksa x adında frob türünde bir değişken oluşturup ona 10 atamak" anlamına mı geliyor? (Frobbing bir değişkeni üretir Ya da, bu 10 ila bir atama olabilir frob x
. Sonuçta, *x = 10;
ayrıştırır ve eğer yasal x
olduğunu int*
.)
G(frob + x)
Bu, "x üzerindeki tekli artı operatörünün sonucu" veya "x'e frob ifadesi ekle" anlamına mı geliyor?
Ve bunun gibi. Bu belirsizlikleri çözmek için sezgisel yöntemler sunabiliriz. "Var x = 10" dediğinizde; bu belirsiz; "x'in türünü anlamak" anlamına gelebilir veya "x, var türünde" anlamına gelebilir. Öyleyse bir buluşsal yöntemimiz var: önce var adlı bir türe bakmaya çalışırız ve yalnızca biri yoksa, x türünü çıkarırız.
Veya sözdizimini belirsiz olmaması için değiştirebiliriz. C # 2.0'ı tasarladıklarında şu sorunu yaşadılar:
yield(x);
Bu, "yineleyicide x ver" veya "x bağımsız değişkeniyle getiri yöntemini çağır" anlamına mı geliyor? Olarak değiştirerek
yield return(x);
şimdi belirsiz değil.
Bir nesne başlatıcıda isteğe bağlı parensler söz konusu olduğunda, ortaya çıkan belirsizliklerin olup olmadığına dair akıl yürütmek basittir çünkü {ile başlayan bir şeyin sunulmasına izin verilen durumların sayısı çok küçüktür . Temelde sadece çeşitli ifade bağlamları, ifade lambdaları, dizi başlatıcılar ve hepsi bu. Tüm vakalar arasında mantık yürütmek ve belirsizlik olmadığını göstermek kolaydır. IDE'nin verimli kalmasını sağlamak biraz daha zordur, ancak çok fazla sorun yaşamadan yapılabilir.
Spesifikasyonla bu tür uğraşmak genellikle yeterlidir. Özellikle zor bir özellikse, daha ağır aletler çıkarırız. Örneğin, LINQ tasarlarken, her ikisi de ayrıştırıcı teorisinde bir geçmişe sahip olan derleyici elemanlarından biri ve IDE görevlilerinden biri, belirsizlikleri arayan gramerleri analiz edebilen bir ayrıştırıcı oluşturucu oluşturdu ve ardından sorgu anlamaları için önerilen C # gramerlerini besledi. ; bunu yapmak, sorguların belirsiz olduğu birçok durum buldu.
Veya, C # 3.0'da lambdalar üzerinde gelişmiş tip çıkarımı yaptığımızda, tekliflerimizi yazdık ve daha sonra bunları havuzun üzerinden Cambridge'deki Microsoft Research'e gönderdik, burada dil ekibinin tür çıkarım önerisinin geçerli olduğuna dair resmi bir kanıt oluşturacak kadar iyi teorik olarak sağlam.
Bugün C # 'de belirsizlikler var mı?
Elbette.
G(F<A, B>(0))
C # 1'de bunun ne anlama geldiği açıktır. Şununla aynıdır:
G( (F<A), (B>0) )
Yani, G'yi bools olan iki argümanla çağırır. C # 2'de bu, C # 1'de ne anlama geldiğini ifade edebilir, ancak aynı zamanda "A ve B tipi parametrelerini alan ve ardından F'nin sonucunu G'ye ileten genel yöntem F'ye 0 geçirme" anlamına da gelebilir. Ayrıştırıcıya, muhtemelen iki durumdan hangisini kastettiğinizi belirleyen karmaşık bir buluşsal yöntem ekledik.
Benzer şekilde, yayınlar C # 1.0'da bile belirsizdir:
G((T)-x)
Bu "-x'i T'ye atma" mı yoksa "T'den x'i çıkarma" mı? Yine, iyi bir tahmin yapan bir buluşsal yöntemimiz var.