Wydaje się, że składnia inicjatora obiektu C # 3.0 pozwala wykluczyć parę nawiasów otwierających / zamykających w konstruktorze, gdy istnieje konstruktor bez parametrów. Przykład:
var x = new XTypeName { PropA = value, PropB = value };
W przeciwieństwie do:
var x = new XTypeName() { PropA = value, PropB = value };
Jestem ciekawy, dlaczego para nawiasów otwierających / zamykających konstruktora jest tutaj opcjonalna XTypeName
?
c#
syntax
types
language-design
initializer
James Dunne
źródło
źródło
Odpowiedzi:
To pytanie było tematem mojego bloga 20 września 2010 roku . Odpowiedzi Josha i Chada („nie dodają żadnej wartości, więc po co ich wymagać?” I „aby wyeliminować nadmiarowość”) są w zasadzie prawidłowe. Aby to nieco bardziej wyrazić:
Możliwość usunięcia listy argumentów jako części „większej funkcji” inicjatorów obiektów spełniła naszą poprzeczkę dla „słodkich” funkcji. Rozważaliśmy kilka kwestii:
Spójrz jeszcze raz na powyższą listę kryteriów. Jednym z nich jest to, że zmiana nie wprowadza żadnej nowej dwuznaczności w analizie leksykalnej, gramatycznej czy semantycznej programu. Twój Proponowana zmiana ma wprowadzić analizy semantycznej dwuznaczności:
Linia 1 tworzy nowe C, wywołuje domyślny konstruktor, a następnie wywołuje metodę wystąpienia M na nowym obiekcie. Linia 2 tworzy nową instancję BM i wywołuje jej domyślny konstruktor. Gdyby nawiasy w linii 1 były opcjonalne, to linia 2 byłaby niejednoznaczna. Musielibyśmy wtedy wymyślić regułę rozwiązującą niejednoznaczność; nie moglibyśmy uczynić tego błędem, ponieważ byłaby to przełomowa zmiana, która zmienia istniejący legalny program C # w uszkodzony program.
Dlatego reguła musiałaby być bardzo skomplikowana: zasadniczo nawiasy są opcjonalne tylko w przypadkach, w których nie wprowadzają niejednoznaczności. Musielibyśmy przeanalizować wszystkie możliwe przypadki, które wprowadzają niejasności, a następnie napisać kod w kompilatorze, aby je wykryć.
W tym świetle wróć i spójrz na wszystkie wymienione przeze mnie koszty. Ile z nich staje się teraz dużych? Skomplikowane reguły wiążą się z dużymi kosztami projektowania, specyfikacji, rozwoju, testowania i dokumentacji. Skomplikowane reguły znacznie częściej spowodują problemy z nieoczekiwanymi interakcjami z funkcjami w przyszłości.
Wszystko za co? Drobna korzyść dla klienta, która nie dodaje językowi nowej mocy reprezentacyjnej, ale dodaje szalone przypadki, które tylko czekają, by krzyknąć „złapać” na jakąś biedną, niczego nie podejrzewającą duszę, która wpadnie na niego. Takie funkcje są natychmiast usuwane i umieszczane na liście „nigdy tego nie rób”.
To było od razu jasne; Jestem dość dobrze zaznajomiony z regułami języka C # dotyczącymi określania, kiedy oczekiwana jest kropkowana nazwa.
Wszystkie trzy. Przeważnie patrzymy tylko na specyfikację i makaron, tak jak zrobiłem powyżej. Na przykład załóżmy, że chcemy dodać nowy operator prefiksu do C # o nazwie „frob”:
(AKTUALIZACJA:
frob
jest oczywiścieawait
; analiza tutaj jest zasadniczo analizą, którą przeszedł zespół projektowy podczas dodawaniaawait
).„frob” jest tutaj jak „nowy” lub „++” - pojawia się przed jakimś wyrażeniem. Wypracowywalibyśmy pożądany priorytet i łączność itd., A następnie zaczynalibyśmy zadawać pytania typu „a co, jeśli program ma już typ, pole, właściwość, zdarzenie, metodę, stałą lub lokalną o nazwie frob?” To natychmiast doprowadziłoby do takich przypadków, jak:
czy to oznacza "wykonaj operację frob na wyniku x = 10, czy utwórz zmienną typu frob o nazwie x i przypisz jej 10?" (Lub, jeśli frobbing tworzy zmienną, może to być przypisanie od 10 do
frob x
. W końcu*x = 10;
analizuje i jest poprawne, jeśli takx
jestint*
.)Czy to oznacza „frob wynik jednoargumentowego operatora plus na x” lub „dodaj wyrażenie frob do x”?
I tak dalej. Aby rozwiązać te dwuznaczności, możemy wprowadzić heurystykę. Kiedy mówisz „var x = 10;” to jest niejednoznaczne; mogłoby to oznaczać „wywnioskować typ x” lub „x jest typu zmiennego”. Mamy więc heurystykę: najpierw próbujemy znaleźć typ o nazwie var i tylko jeśli takiego nie ma, wnioskujemy o typie x.
Lub możemy zmienić składnię, aby nie była niejednoznaczna. Kiedy projektowali C # 2.0, mieli następujący problem:
Czy to oznacza „yield x w iteratorze” czy „wywołanie metody yield z argumentem x?” Zmieniając go na
teraz jest to jednoznaczne.
W przypadku opcjonalnych parenów w inicjatorze obiektu łatwo jest wnioskować o tym, czy są wprowadzone niejasności, czy nie, ponieważ liczba sytuacji, w których dopuszczalne jest wprowadzenie czegoś, co zaczyna się od {, jest bardzo mała . Zasadniczo tylko różne konteksty instrukcji, wyrażenia lambda, inicjatory tablic i to wszystko. Łatwo jest uzasadnić wszystkie przypadki i pokazać, że nie ma dwuznaczności. Upewnienie się, że IDE pozostaje wydajne, jest nieco trudniejsze, ale można to zrobić bez większych problemów.
Ten rodzaj majstrowania przy specyfikacji zwykle jest wystarczający. Jeśli jest to szczególnie trudna funkcja, wyciągamy cięższe narzędzia. Na przykład, podczas projektowania LINQ, jeden z kompilatorów i jeden z IDE, którzy obaj mają doświadczenie w teorii parsera, zbudowali sobie generator parsera, który mógł analizować gramatyki w poszukiwaniu niejednoznaczności, a następnie wprowadzał proponowane gramatyki C # do zrozumienia zapytań ; w ten sposób znaleziono wiele przypadków, w których zapytania były niejednoznaczne.
Lub, kiedy przeprowadzaliśmy zaawansowane wnioskowanie o typie na lambdach w C # 3.0, pisaliśmy nasze propozycje, a następnie wysyłaliśmy je przez staw do Microsoft Research w Cambridge, gdzie zespół językowy był wystarczająco dobry, aby opracować formalny dowód, że propozycja wnioskowania o typie była teoretycznie rozsądne.
Pewnie.
W C # 1 jest jasne, co to oznacza. To jest to samo co:
Oznacza to, że wywołuje G z dwoma argumentami, które są bools. W języku C # 2 może to oznaczać to, co oznaczało w języku C # 1, ale może również oznaczać „przekazanie 0 do ogólnej metody F, która przyjmuje parametry typu A i B, a następnie przekazanie wyniku F do G”. Dodaliśmy skomplikowaną heurystykę do parsera, która określa, który z dwóch przypadków prawdopodobnie miałeś na myśli.
Podobnie rzuty są niejednoznaczne nawet w C # 1.0:
Czy to „rzut -x na T” czy „odejmij x od T”? Znowu mamy heurystykę, która pozwala na dobre przypuszczenie.
źródło
Ponieważ tak określono język. Nie dodają żadnej wartości, więc po co je uwzględniać?
Jest również bardzo podobny do tablic o typie implicity
Źródła: http://msdn.microsoft.com/en-us/library/ms364047%28VS.80%29.aspx
źródło
Zrobiono to w celu uproszczenia konstrukcji obiektów. Projektanci języka nie powiedzieli (według mojej wiedzy) konkretnie, dlaczego uważają, że jest to przydatne, chociaż jest to wyraźnie wymienione na stronie specyfikacji C # w wersji 3.0 :
Przypuszczam, że w tym przypadku uważali, że nawiasy nie są konieczne, aby pokazać zamiary programisty, ponieważ inicjator obiektu pokazuje zamiar skonstruowania i ustawienia właściwości obiektu.
źródło
W pierwszym przykładzie kompilator wnioskuje, że wywołujesz domyślny konstruktor (specyfikacja języka C # 3.0 stwierdza, że jeśli nie podano nawiasów, wywoływany jest konstruktor domyślny).
W drugim jawnie wywołujesz domyślny konstruktor.
Możesz również użyć tej składni do ustawiania właściwości podczas jawnego przekazywania wartości do konstruktora. Gdybyś miał następującą definicję klasy:
Wszystkie trzy stwierdzenia są ważne:
źródło
Nie jestem Ericem Lippertem, więc nie mogę powiedzieć na pewno, ale zakładam, że dzieje się tak, ponieważ kompilator nie potrzebuje pustego nawiasu, aby wywnioskować konstrukcję inicjalizacyjną. Dlatego informacje stają się zbędne i niepotrzebne.
źródło