Dlaczego moduły .NET oddzielają nazwy plików modułów od przestrzeni nazw?

9

We wdrożeniach języka programowania Scheme (standard R6RS) mogę zaimportować moduł w następujący sposób:

(import (abc def xyz))

System spróbuje wyszukać plik, w $DIR/abc/def/xyz.slsktórym $DIRznajduje się katalog, w którym przechowywane są moduły Scheme. xyz.slsjest kodem źródłowym modułu i w razie potrzeby jest on kompilowany w locie.

Systemy modułów Ruby, Python i Perl są pod tym względem podobne.

Z drugiej strony C # jest nieco bardziej zaangażowany.

Po pierwsze, masz pliki dll, do których musisz się odwoływać dla poszczególnych projektów. Musisz wyraźnie się do nich odwoływać. Jest to bardziej zaangażowane niż powiedzmy, upuszczając pliki dll do katalogu i każąc C # odbierać je według nazwy.

Po drugie, między nazwą pliku dll a przestrzeniami nazw oferowanymi przez dll nie ma żadnej relacji między nazwami. Mogę docenić tę elastyczność, ale może również wymknąć się spod kontroli (i ma).

Aby uczynić to konkretnym, byłoby miło, gdyby, gdy to powiem using abc.def.xyz;, C # spróbowałby znaleźć plik abc/def/xyz.dllw jakimś katalogu, w którym C # wie, że może go szukać (konfigurowalny dla każdego projektu).

Uważam, że sposób obsługi modułów Ruby, Python, Perl, Scheme jest bardziej elegancki. Wydaje się, że nowe języki zwykle mają prostszy wygląd.

Dlaczego świat .NET / C # robi to w ten sposób, z dodatkowym poziomem pośrednictwa?

dharmatech
źródło
10
Mechanizm montażu i rozwiązywania klas .NET działa dobrze od ponad 10 lat. Myślę, że brakuje ci fundamentalnego nieporozumienia (lub niewystarczającej liczby badań) na temat tego, dlaczego został zaprojektowany w ten sposób - np. W celu obsługi przekierowywania zestawu itp. W czasie wiązania i wielu innych przydatnych mechanizmów rozwiązywania problemów
Kev
Jestem prawie pewien, że rozdzielczość DLL z instrukcji using zepsuje wykonanie obok siebie. Również jeśli
byłby
Dodatkowy poziom pośredni? Hmmmmm .... dmst.aueb.gr/dds/pubs/inbook/beautiful_code/html/Spi07g.html
user541686
@gilles Dziękujemy za edycję i ulepszenie pytania!
dharmatech
Niektóre z twoich punktów są charakterystyczne dla Visual Studio, a porównywanie ich z językiem nie jest całkiem uczciwe ( np. Odwołania do DLL w projektach). Lepszym porównaniem pełnego środowiska .NET byłoby Java + Eclipse.
Ross Patterson

Odpowiedzi:

22

Następująca adnotacja w Wytycznych w sprawie projektu ramowego, sekcja 3.3. Nazwy zestawów i bibliotek dll zapewniają wgląd w to, dlaczego przestrzenie nazw i zestawy są oddzielne.

BRAD ABRAMS Na wczesnym etapie projektowania CLR postanowiliśmy oddzielić widok programisty platformy (przestrzeni nazw) od widoku pakowania i wdrożenia platformy (zespołów). Ten rozdział pozwala na niezależną optymalizację każdego z nich na podstawie własnych kryteriów. Na przykład możemy dowolnie przypisywać przestrzenie nazw do typów grup, które są funkcjonalnie powiązane (np. Wszystkie operacje we / wy w System.IO), podczas gdy zestawy mogą być uwzględniane ze względu na wydajność (czas ładowania), wdrożenie, obsługę lub wersjonowanie .

Conrad Frix
źródło
Wydaje się, że jest to jak dotąd najbardziej wiarygodne źródło. Dzięki Conrad!
dharmatech
+1 za uzyskanie cholernie autorytatywnej odpowiedzi. Ale także każde pytaniedlaczego C # robi <X> ” musi być również postrzegane przez obiektyw „ Java robi <X> w następujący sposób: ... ”, ponieważ C # było (jawnie lub nie) reakcją na Sun i Różne programy Microsoft dla Java w systemie Windows.
Ross Patterson
6

Dodaje elastyczność i pozwala na ładowanie bibliotek (które nazywacie modułami w pytaniu) na żądanie.

Jedna przestrzeń nazw, wiele bibliotek:

Jedną z zalet jest to, że mogę łatwo zastąpić jedną bibliotekę inną. Powiedzmy, że mam przestrzeń nazw MyCompany.MyApplication.DALi bibliotekę DAL.MicrosoftSQL.dll, która zawiera wszystkie zapytania SQL i inne rzeczy, które mogą być specyficzne dla bazy danych. Jeśli chcę, aby aplikacja była zgodna z Oracle, po prostu dodaję DAL.Oracle.dll, zachowując tę ​​samą przestrzeń nazw. Od teraz mogę dostarczać aplikację z jedną biblioteką dla klientów, którzy potrzebują zgodności z Microsoft SQL Server, oraz z drugą biblioteką dla klientów korzystających z Oracle.

Zmiana przestrzeni nazw na tym poziomie prowadziłaby do zduplikowania kodu lub konieczności przejścia i zmiany wszystkich usingznaków wewnątrz kodu źródłowego dla każdej bazy danych.

Jedna biblioteka, wiele przestrzeni nazw:

Posiadanie kilku przestrzeni nazw w jednej bibliotece jest również korzystne pod względem czytelności. Jeśli w klasie używam tylko jednej przestrzeni nazw, umieszczam tylko tę na górze pliku.

  • Posiadanie wszystkich przestrzeni nazw dużej biblioteki byłoby raczej mylące zarówno dla osoby czytającej kod źródłowy, jak i dla samej pisarki, ponieważ Intellisense ma zbyt wiele rzeczy do zasugerowania w danym kontekście.

  • Mając mniejsze biblioteki, jedna biblioteka na plik miałaby wpływ na wydajność: każda biblioteka musi być ładowana na żądanie do pamięci i przetwarzana przez maszynę wirtualną podczas działania aplikacji; mniej plików do załadowania oznacza nieco lepszą wydajność.

Arseni Mourzenko
źródło
Drugi przypadek nie wymaga oddzielenia nazw plików od przestrzeni nazw (chociaż umożliwienie takiego rozdzielenia znacznie ułatwia separację), ponieważ dany zestaw może łatwo zawierać wiele podfolderów i plików. Ponadto możliwe jest również osadzanie wielu zestawów w tej samej bibliotece DLL (np. Przy użyciu ILMerge ). Java działa przy takim podejściu.
Brian
2

Wygląda na to, że wybierasz przeciążenie terminologii zarówno „przestrzeni nazw”, jak i „modułu”. Nie powinno być zaskoczeniem, że postrzegasz rzeczy jako „pośrednie”, gdy nie pasują do twoich definicji.

W większości języków obsługujących przestrzenie nazw, w tym C #, przestrzeń nazw nie jest modułem. Przestrzeń nazw to sposób określania nazw. Moduły są sposobem zachowania zakresu.

Ogólnie rzecz biorąc, podczas gdy środowisko uruchomieniowe .Net obsługuje ideę modułu (z nieco inną definicją niż ta, której używasz domyślnie), jest on raczej rzadko używany; Widziałem go tylko w projektach zbudowanych w SharpDevelop, głównie po to, aby można było zbudować pojedynczą bibliotekę DLL z modułów zbudowanych w różnych językach. Zamiast tego budujemy biblioteki za pomocą dynamicznie połączonej biblioteki.

W języku C # przestrzenie nazw są rozwiązywane bez „warstwy pośredniej”, o ile wszystkie są w tym samym pliku binarnym; każda wymagana pośrednia odpowiedzialność spoczywa na kompilatorze i linkerze, o której nie trzeba się wiele zastanawiać. Gdy zaczniesz budować projekt z wieloma zależnościami, odwołujesz się do bibliotek zewnętrznych. Gdy Twój projekt znajdzie odniesienie do biblioteki zewnętrznej (DLL), kompilator znajdzie go dla Ciebie.

W schemacie, jeśli chcesz załadować bibliotekę zewnętrzną, musisz (#%require (lib "mylib.ss"))najpierw zrobić coś takiego lub bezpośrednio użyć obcego interfejsu funkcji, jak pamiętam. Jeśli korzystasz z zewnętrznych plików binarnych, masz tyle samo pracy do rozwiązania zewnętrznych plików binarnych. Możliwe, że najczęściej korzystałeś z bibliotek tak często używanych, że istnieje podkładka oparta na schemacie, która wyodrębnia to z ciebie, ale jeśli kiedykolwiek będziesz musiał napisać własną integrację z biblioteką innej firmy, w zasadzie będziesz musiał trochę popracować, aby „załadować” " Biblioteka.

W Rubim moduły, przestrzenie nazw i nazwy plików są w rzeczywistości znacznie mniej połączone niż się wydaje; ścieżka LOAD_PATH komplikuje sprawy, a deklaracje modułów mogą być wszędzie. Python jest prawdopodobnie bliżej robienia rzeczy tak, jak myślisz, że widzisz w Schemacie, z tym wyjątkiem, że biblioteki stron trzecich w C nadal dodają (małe) zmarszczki.

Ponadto języki dynamicznie typowane, takie jak Ruby, Python i Lisp, zwykle nie mają takiego samego podejścia do „kontraktów”, jak języki statycznie typowane. W dynamicznie wpisywanych językach zwykle ustanawia się tylko „umowę dżentelmena”, zgodnie z którą kod będzie reagował na określone metody, a jeśli twoje klasy wydają się mówić tym samym językiem, wszystko jest dobrze. Języki o typie statycznym mają dodatkowe mechanizmy do egzekwowania tych reguł w czasie kompilacji. W języku C # korzystanie z takiej umowy umożliwia dostarczenie co najmniej umiarkowanie użytecznych gwarancji zgodności z tymi interfejsami, co pozwala łączyć wtyczki i zastępowania z pewnym stopniem gwarancji wspólności, ponieważ wszyscy kompilują się w oparciu o tę samą umowę. W Ruby lub Scheme weryfikujesz te umowy, pisząc testy, które działają w czasie wykonywania.

Wymierne korzyści wydajnościowe wynikają z tych gwarancji czasu kompilacji, ponieważ wywołanie metody nie wymaga podwójnej wysyłki. Aby uzyskać te korzyści w czegoś takiego jak Lisp, Ruby, JavaScript lub gdzie indziej, potrzebne są obecnie nieco egzotyczne mechanizmy statycznego kompilowania klas na czas w wyspecjalizowanych maszynach wirtualnych.

Jedną rzeczą, dla której ekosystem C # nadal ma stosunkowo niedojrzałe wsparcie, jest zarządzanie tymi zależnościami binarnymi; Java ma Maven od kilku lat, aby upewnić się, że masz wszystkie wymagane zależności, podczas gdy C # nadal ma dość prymitywne podejście podobne do MAKE, które obejmuje strategiczne umieszczanie plików we właściwym miejscu przed czasem.

JasonTrue
źródło
1
Jeśli chodzi o zarządzanie zależnościami, możesz rzucić okiem na NuGet . Oto ładny artykuł na ten temat od Phila Haacka
Conrada Frixa
W zastosowanych przeze mnie implementacjach schematu R6RS (na przykład Ikarus, Chez i Ypsilon) zależności są obsługiwane automatycznie, na podstawie importu biblioteki. Zależności zostały znalezione i, jeśli to konieczne, skompilowane i zapisane w pamięci podręcznej na potrzeby przyszłego importu.
dharmatech
Znajomy Nuget, a zatem mój komentarz, że jest „stosunkowo niedojrzały”
JasonTrue