Чому модулі .NET відокремлюють імена файлів модулів від просторів імен?


9

У реалізаціях мови програмування Scheme (стандарт R6RS) я можу імпортувати модуль наступним чином:

(import (abc def xyz))

Система спробує шукати файл, $DIR/abc/def/xyz.slsде $DIRзнаходиться якийсь каталог, де ви зберігаєте свої модулі схеми. xyz.slsє вихідним кодом для модуля, і він компілюється на ходу при необхідності.

Модульні системи Ruby, Python та Perl схожі в цьому відношенні.

C # з іншого боку, трохи більше задіяний.

По-перше, у вас є файли dll, на які ви повинні посилатися на основі кожного проекту. Ви повинні посилатися на кожного прямо. Це більше стосується, ніж скажімо, викидання файлів dll в каталог і C # підбирає їх по імені.

По-друге, між іменем файлу dll та просторами імен, запропонованими dll, не існує відповідності між собою імен. Я можу оцінити цю гнучкість, але вона також може вийти з рук (і є).

Щоб зробити це конкретним, було б непогано, якби я сказав це using abc.def.xyz;, C # спробував знайти файл abc/def/xyz.dllу деякому каталозі, який C # знає шукати (налаштовується на основі проекту).

Мені здається, вишуканіший спосіб обробки модулів Ruby, Python, Perl, Scheme. Здається, що нові мови, як правило, йдуть з більш простим дизайном.

Чому світ .NET / C # робить все таким чином, із додатковим рівнем непрямості?


10
Механізм складання та розв’язання класів .NET працював чудово протягом більше 10 років. Я думаю, вам не вистачає принципового непорозуміння (або недостатньо досліджень) щодо того, чому він створений таким чином - наприклад, для підтримки перенаправлення збірок і т. Д. Під час зв’язування та багатьох інших корисних механізмів вирішення
Kev

Я майже впевнений, що роздільна здатність DLL із використовуваного оператора порушить бічне виконання. Крім того, якби було 1 до 1, вам знадобиться 50 dll для всіх просторів імен в mscorlib, або їм доведеться кинути уявлення про простори імен
Conrad Frix

Додатковий рівень непрямості? Хммммм
користувач541686

@gilles Дякую за редагування та покращення питання!
дхартехтех

Деякі ваші моменти властиві Visual Studio, і порівнювати їх з мовою не зовсім справедливо ( наприклад , посилання DLL у проектах). Кращим порівнянням для повного середовища .NET буде Java + Eclipse.
Росс Паттерсон

Відповіді:


22

Наступна примітка у Рамкових керівних принципах Розділу 3.3. Імена зборів і Dlls пропонують зрозуміти, чому простори імен і збірок є окремими.

BRAD ABRAMS На початку проекту CLR ми вирішили відокремити вигляд розробника платформи (простори імен) від виду упаковки та розгортання платформи (зборок). Цей поділ дозволяє оптимізувати кожного незалежно на основі власних критеріїв. Наприклад, ми можемо розміщувати простори імен на групи типів, які є функціонально пов'язаними (наприклад, усі речі вводу / виводу в System.IO), тоді як збірки можна враховувати для продуктивності (час завантаження), розгортання, обслуговування або версії версій .


Схоже, найавторитетніше джерело досі. Дякую Конрад!
dharmatech

+1 за отримання проклято-авторитетної відповіді. Але також кожне питання " чому C # робить <X> " також потрібно розглядати через об'єктив " Java робить <X> так: ... ", тому що C # була (явно чи ні) реакцією на сонце та Різні програми програми Microsoft для Java для Windows.
Росс Паттерсон

6

Це додає гнучкості та дозволяє завантажувати бібліотеки (те, що ви називаєте модулями у своєму запитанні) на вимогу.

Простір імен, кілька бібліотек:

Однією з переваг є те, що я легко можу замінити одну бібліотеку іншою. Скажімо, у мене є простір імен MyCompany.MyApplication.DALта бібліотека DAL.MicrosoftSQL.dll, яка містить усі запити SQL та інші речі, які можуть бути специфічними для бази даних. Якщо я хочу, щоб програма була сумісною з Oracle, я просто додаю DAL.Oracle.dll, зберігаючи той самий простір імен. Відтепер я можу доставляти додаток з однією бібліотекою для клієнтів, які потребують сумісності з Microsoft SQL Server, та з іншою бібліотекою для клієнтів, які використовують Oracle.

Зміна простору імен на цьому рівні призведе до або повторюваного коду, або до необхідності перейти та змінити всі usings всередині вихідного коду для кожної бази даних.

Одна бібліотека, кілька просторів імен:

Наявність декількох просторів імен в одній бібліотеці також вигідно з точки зору читабельності. Якщо в класі я використовую лише один з просторів імен, я розміщую саме цей у верхній частині файлу.

  • Наявність усіх просторів імен великої бібліотеки було б досить заплутаною як для людини, яка читає вихідний код, так і для самої письменниці, причому Intellisense має занадто багато речей, щоб запропонувати у заданому контексті.

  • Маючи менші бібліотеки, одна бібліотека на файл матиме вплив на продуктивність: кожна бібліотека повинна завантажуватися на вимогу в пам'ять та оброблятися віртуальною машиною під час роботи програми; менше завантаження файлів означає трохи кращу продуктивність.


Другий випадок не вимагає, щоб імена файлів були відокремленими від просторів імен (хоча дозволяючи таке розділення значно розширює поділ), оскільки дана збірка може легко мати безліч підпапок і файлів. Крім того, також можна вбудувати кілька збірок в одну DLL (наприклад, за допомогою ILMerge ). Java працює з таким підходом.
Брайан

2

Схоже, ви вирішили перевантажувати термінологію як «простору імен», так і «модуля». Не дивно, що ви бачите речі "непрямими", коли вони не відповідають вашим визначенням.

У більшості мов, які підтримують простори імен, включаючи C #, простір імен не є модулем. Простір імен - це спосіб визначення імен. Модулі - це спосіб визначення поведінки.

Взагалі, хоча час виконання .Net підтримує ідею модуля (дещо інше визначення, ніж те, яке ви використовуєте неявно), воно використовується досить рідко; Я бачив його лише в проектах, побудованих в SharpDevelop, в основному, щоб ви могли створити одну DLL з модулів, побудованих на різних мовах. Натомість ми будуємо бібліотеки, використовуючи динамічно пов'язану бібліотеку.

У C # простори імен вирішуються без будь-якого "шару непрямості" до тих пір, поки вони знаходяться в одному двояковому; будь-яке необхідне опосередкування - це відповідальність компілятора і лінкера, про який вам не потрібно багато думати. Після того як ви почнете створювати проект із кількома залежностями, ви посилаєтесь на зовнішні бібліотеки. Після того, як ваш проект зробив посилання на зовнішню бібліотеку (DLL), компілятор знайде це для вас.

У схемі, якщо вам потрібно завантажити зовнішню бібліотеку, ви повинні зробити щось на кшталт (#%require (lib "mylib.ss"))спочатку або використовувати інтерфейс іноземної функції безпосередньо, як я пам'ятаю. Якщо ви використовуєте зовнішні бінарні файли, у вас є однакова робота над вирішенням зовнішніх бінарних файлів. Швидше за все, ви в основному використовуєте бібліотеки, настільки часто використовувані, що існує схем на основі схеми, який абстрагує це від вас, але якщо вам коли-небудь доведеться написати власну інтеграцію з сторонній бібліотекою, вам, по суті, доведеться виконати певну роботу, щоб "завантажити " бібліотека.

У Ruby, модулях, просторах імен та імен файлів насправді набагато менш пов'язано, ніж ви, напевно, припускаєте; LOAD_PATH робить речі дещо складнішими, і декларації модуля можуть бути де завгодно. Python, ймовірно, ближче до того, щоб робити так, як ви думаєте, як ви бачите в Scheme, за винятком того, що сторонні бібліотеки в C все ще додають (невелику) зморшку.

До того ж, динамічно набрані мови, такі як Ruby, Python та Lisp, як правило, не мають такого ж підходу до "контрактів", як мови, що мають статичний тип. У динамічно набраних мовах ви зазвичай встановлюєте лише своєрідну «джентльменську угоду», що код відповідає певним методам, і якщо вам здається, що ваші класи говорять однією і тією ж мовою, все добре. Статично набрані мови мають додаткові механізми для виконання цих правил під час компіляції. У C # використання такого контракту дозволяє надати принаймні помірно корисні гарантії дотримання цих інтерфейсів, що дозволяє поєднати плагіни та замінники з певним ступенем гарантії спільності, оскільки всі ви складаєте проти того самого контракту. У Ruby або Scheme ви підтверджуєте ці угоди, написавши тести, які працюють під час виконання.

Від цих гарантій часу на компіляцію є помірна користь від продуктивності, оскільки виклик методу не потребує подвійної відправки. Для того, щоб отримати ці переваги у чомусь на зразок Lisp, Ruby, JavaScript або інших місцях, потрібні ще трохи екзотичні механізми щойно вчасно статично складання класів у спеціалізованих віртуальних машинах.

Одне, для чого екосистема C # досі має відносно незрілу підтримку, - це управління цими бінарними залежностями; У Java протягом декількох років Maven працював над тим, щоб переконатись, що у вас є всі необхідні залежності, тоді як C # все ще має досить примітивний MAKE-подібний підхід, який передбачає стратегічне розміщення файлів у потрібному місці достроково.


1
Щодо управління залежностями, ви можете поглянути на NuGet . Ось приємна стаття про це від Філа Хака
Конрада Фрікса

У реалізаціях схеми R6RS, які я використав (наприклад, Ikarus, Chez та Ypsilon), обробляються залежності автоматично, на основі імпорту бібліотеки. Виявляються залежності і, якщо необхідно, складаються та зберігаються в кеш-пам'яті для майбутнього імпорту.
дхартехтех

Знайомий з Nuget, і, таким чином, мій коментар, що він "відносно незрілий"
JasonTrue
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.