C # 3.0 nesne başlatıcı yapıcı parantezleri neden isteğe bağlıdır?


114

Görünüşe göre C # 3.0 nesne başlatıcı sözdizimi, parametresiz bir kurucu varken yapıcıdaki parantezlerin aç / kapat çiftinin dışlanmasına izin veriyor gibi görünüyor. Misal:

var x = new XTypeName { PropA = value, PropB = value };

Aksine:

var x = new XTypeName() { PropA = value, PropB = value };

Yapıcı açma / kapama parantez çiftinin burada neden isteğe bağlı olduğunu merak ediyorum XTypeName?


9
Bunu bir yana, geçen hafta bir kod incelemesinde bulduk: var list = new List <Foo> {}; Bir şey suistimal edilebilirse ...
blu

@blu Bu soruyu sormak istememin bir nedeni bu. Kodumuzdaki tutarsızlığı fark ettim. Genel olarak tutarsızlık beni rahatsız ediyor, bu yüzden söz dizimindeki isteğe bağlılığın arkasında iyi bir mantık olup olmadığını göreceğimi düşündüm. :)
James Dunne

Yanıtlar:


143

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: frobelbette 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 xolduğ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.


3
Ah üzgünüm, unuttum ... Yarasa sinyali yaklaşımı, işe yarıyor gibi görünse de, doğrudan bir iletişim aracına (IMO) tercih edilir, böylece bir kişi, halk eğitimi için istenen kamuya açıklanmayı bir SO gönderisi şeklinde elde edemezdi. indekslenebilir, aranabilir ve kolayca başvurulabilir. Bunun yerine sahnelenmiş bir SO post / answer dansının koreografisini yapmak için doğrudan iletişime geçmeli miyiz? :)
James Dunne

5
Bir gönderi sahnelemekten kaçınmanızı tavsiye ederim. Bu, soruyla ilgili ek bilgi sahibi olabilecek başkaları için adil olmaz. Daha iyi bir yaklaşım, soruyu göndermek ve ardından katılımı isteyen bir bağlantıyı e-posta ile göndermek olabilir.
chilltemp

1
@James: Sonraki sorunuza yanıt vermek için cevabımı güncelledim.
Eric Lippert

8
@Eric, bu "bunu asla yapma" listesi hakkında blog yazabilir misin? Asla C # dilinin bir parçası olmayacak diğer örnekleri görmeyi merak ediyorum :)
Ilya Ryzhenkov

2
@Eric: Bana karşı sabrınız için gerçekten minnettarım :) Teşekkürler! Çok bilgilendirici.
James Dunne

12

Çünkü dil böyle belirlendi. Hiçbir değer katmazlar, öyleyse neden onları dahil edelim?

Aynı zamanda implicity tipli dizilere çok benzer

var a = new[] { 1, 10, 100, 1000 };            // int[]
var b = new[] { 1, 1.5, 2, 2.5 };            // double[]
var c = new[] { "hello", null, "world" };      // string[]
var d = new[] { 1, "one", 2, "two" };         // Error

Referans: http://msdn.microsoft.com/en-us/library/ms364047%28VS.80%29.aspx


1
Orada niyetin ne olduğunun açık olması gerektiği anlamında hiçbir değer katmazlar, ancak şu anda iki farklı nesne oluşturma sözdizimine sahip olduğumuz için tutarlılıktan kopar, biri parantezli (ve içinde virgülle ayrılmış argüman ifadeleri) ve diğeri de parantezsiz .
James Dunne

1
@James Dunne, aslında dolaylı olarak yazılan dizi sözdizimine çok benzer sözdizimi, düzenlememe bakın. Tür yok, kurucu yok ve niyet açık, bu yüzden bunu bildirmeye gerek yok
CaffGeek

7

Bu, nesnelerin yapımını basitleştirmek için yapıldı. Dil tasarımcıları (bildiğim kadarıyla), C # Sürüm 3.0 Spesifikasyon sayfasında açıkça belirtilmesine rağmen, bunun neden yararlı olduğunu düşündüklerini özellikle söylemediler :

Bir nesne oluşturma ifadesi, bir nesne veya koleksiyon başlatıcı içermesi koşuluyla yapıcı bağımsız değişken listesini ve parantezleri dahil etmeyebilir. Yapıcı bağımsız değişken listesinin çıkarılması ve parantezlerin kapatılması, boş bir bağımsız değişken listesi belirtmeye eşdeğerdir.

Sanırım bu durumda geliştiricinin amacını göstermek için parantezin gerekli olmadığını düşündüklerini, çünkü nesne başlatıcı bunun yerine nesnenin özelliklerini oluşturma ve ayarlama niyetini gösteriyor.


4

İlk örneğinizde, derleyici varsayılan kurucuyu çağırdığınız sonucuna varır (C # 3.0 Dil Belirtimi, parantez sağlanmazsa varsayılan kurucunun çağrıldığını belirtir).

İkincisinde, varsayılan kurucuyu açıkça çağırırsınız.

Değerleri yapıcıya açıkça iletirken özellikleri ayarlamak için bu sözdizimini de kullanabilirsiniz. Aşağıdaki sınıf tanımına sahipseniz:

public class SomeTest
{
    public string Value { get; private set; }
    public string AnotherValue { get; set; }
    public string YetAnotherValue { get; set;}

    public SomeTest() { }

    public SomeTest(string value)
    {
        Value = value;
    }
}

Üç ifade de geçerlidir:

var obj = new SomeTest { AnotherValue = "Hello", YetAnotherValue = "World" };
var obj = new SomeTest() { AnotherValue = "Hello", YetAnotherValue = "World"};
var obj = new SomeTest("Hello") { AnotherValue = "World", YetAnotherValue = "!"};

Sağ. Örneğinizdeki birinci ve ikinci durumda bunlar işlevsel olarak aynı, doğru mu?
James Dunne

1
@James Dunne - Doğru. Dil spesifikasyonu tarafından belirtilen kısım budur. Boş parantezler gereksizdir, ancak bunları yine de sağlayabilirsiniz.
Justin Niessner

1

Ben Eric Lippert değilim, bu yüzden kesin olarak söyleyemem, ama bunun, ilklendirme yapısını çıkarmak için derleyici tarafından boş paranteze ihtiyaç duyulmadığı için olduğunu varsayabilirim. Bu nedenle gereksiz bilgi haline gelir ve gerekli değildir.


Doğru, gereksiz, ama neden aniden tanıtılmasının isteğe bağlı olduğunu merak ediyorum? Dil sözdizimi tutarlılığından kopuyor gibi görünüyor. Bir başlatıcı bloğunu belirtmek için açık küme parantezim yoksa, bu geçersiz sözdizimi olmalıdır. Bay Lippert'ten bahsetmeniz ne tuhaf, ben ve başkaları boş bir meraktan yararlansın diye onun cevabını alenen bekliyordum. :)
James Dunne
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.