Почему модули .NET отделяют имена файлов модулей от пространств имен?

9

В реализациях языка программирования Scheme (стандарт R6RS) я могу импортировать модуль следующим образом:

(import (abc def xyz))

Система попытается найти файл, в $DIR/abc/def/xyz.slsкотором $DIRнаходится какой-то каталог, в котором вы храните свои модули Scheme. 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 # работает таким образом, с дополнительным уровнем косвенности?

dharmatech
источник
10
Механизм сборки и разрешения классов в .NET прекрасно работал уже более 10 лет. Я думаю, что вам не хватает фундаментального недопонимания (или недостаточно исследований) о том, почему он спроектирован таким образом - например, для поддержки перенаправления сборок и т. Д. Во время привязки и многих других полезных механизмов разрешения
Кев
Я почти уверен, что разрешение DLL из оператора using нарушит параллельное выполнение. Также, если бы было 1 к 1, вам понадобилось бы 50 dll для всех пространств имен в mscorlib, или им пришлось бы отбросить идею пространств имен
Конрад Фрикс
Дополнительный уровень косвенности? Хммммм .... dmst.aueb.gr/dds/pubs/inbook/beautiful_code/html/Spi07g.html
user541686
@gilles Спасибо за редактирование и улучшение вопроса!
dharmatech
Некоторые из ваших замечаний свойственны Visual Studio, и сравнение их с языком не совсем справедливо ( например , ссылки на DLL в проектах). Лучшее сравнение для полной среды .NET было бы Java + Eclipse.
Росс Паттерсон

Ответы:

22

Следующая аннотация в разделе « Руководство по разработке структуры» 3.3. Имена сборок и библиотек дают представление о том, почему пространства имен и сборки разделены.

BRAD ABRAMS В начале разработки CLR мы решили отделить представление платформы для разработчиков (пространства имен) от представления упаковки и развертывания платформы (сборок). Такое разделение позволяет оптимизировать каждый из них независимо от своих критериев. Например, мы можем разложить пространства имен на группы типов, которые функционально связаны (например, все компоненты ввода-вывода в System.IO), в то время как сборки могут быть учтены с точки зрения производительности (времени загрузки), развертывания, обслуживания или управления версиями. ,

Конрад Фрикс
источник
Похоже, самый авторитетный источник до сих пор. Спасибо, Конрад!
dharmatech
+1 за получение проклятого-авторитетного ответа. Но также каждый вопрос « почему C # делает <X> » также должен рассматриваться через призму « Java делает <X> следующим образом: ... », потому что C # был (явно или нет) реакцией на Sun и Различные программы Microsoft для Java на Windows.
Росс Паттерсон
6

Это добавляет гибкости и позволяет загружать библиотеки (что вы называете модулями в вашем вопросе) по запросу.

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

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

Изменение пространства имен на этом уровне приведет либо к дублированию кода, либо к необходимости перейти и изменить все usingэлементы внутри исходного кода для каждой базы данных.

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

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

  • Наличие всех пространств имен большой библиотеки будет довольно запутанным как для человека, который читает исходный код, так и для самого автора, поскольку у Intellisense есть слишком много вещей, которые можно предложить в данном контексте.

  • При наличии меньших библиотек одна библиотека на файл будет влиять на производительность: каждая библиотека должна загружаться по требованию в память и обрабатываться виртуальной машиной при запуске приложения; меньше файлов для загрузки означает немного лучшую производительность.

Арсений Мурзенко
источник
Во втором случае не требуется, чтобы имена файлов были отделены от пространств имен (хотя такое разделение значительно облегчает разделение), поскольку в данной сборке легко может быть множество подпапок и файлов. Кроме того, также возможно встраивать несколько сборок в одну и ту же DLL (например, используя ILMerge ). В Java работает такой подход.
Брайан,
2

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

В большинстве языков, которые поддерживают пространства имен, включая C #, пространство имен не является модулем. Пространство имен - это способ определения имен. Модули - это способ определения поведения.

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

В C # пространства имен разрешаются без какого-либо «уровня косвенности», если они все находятся в одном двоичном файле; Ответственность за любое косвенное обращение лежит на компиляторе и компоновщике, о котором вам не нужно много думать. Как только вы начинаете создавать проект с несколькими зависимостями, вы затем ссылаетесь на внешние библиотеки. Как только ваш проект сделал ссылку на внешнюю библиотеку (DLL), компилятор найдет ее для вас.

В Схеме, если вам нужно загрузить внешнюю библиотеку, вы должны (#%require (lib "mylib.ss"))сначала сделать что-то подобное или использовать интерфейс сторонней функции напрямую, насколько я помню. Если вы используете внешние двоичные файлы, у вас есть такой же объем работы для разрешения внешних двоичных файлов. Скорее всего, вы в основном использовали библиотеки, которые так часто используются, что есть схема, основанная на Scheme, которая абстрагирует вас от этого, но если вам когда-нибудь понадобится написать собственную интеграцию со сторонней библиотекой, вам, по сути, придется поработать над «загрузкой». " библиотека.

В Ruby Модули, Пространства имен и Имена файлов на самом деле гораздо менее связаны, чем кажется; LOAD_PATH делает вещи немного сложнее, и объявления модулей могут быть где угодно. Python, вероятно, ближе к тому, чтобы делать вещи так, как вы думаете в Scheme, за исключением того, что сторонние библиотеки в C по-прежнему добавляют (маленькую) складку.

Кроме того, языки с динамической типизацией, такие как Ruby, Python и Lisp, обычно не имеют такой же подход к «контрактам», как языки со статической типизацией. В динамически типизированных языках вы обычно устанавливаете своего рода «соглашение Джентльмена», что код будет реагировать на определенные методы, и, если кажется, что ваши классы говорят на одном языке, все хорошо. Языки со статической типизацией имеют дополнительные механизмы для обеспечения соблюдения этих правил во время компиляции. В C # использование такого контракта позволяет вам предоставлять как минимум умеренно полезные гарантии соблюдения этих интерфейсов, что позволяет связывать плагины и замены с некоторой степенью гарантии общности, поскольку все вы компилируете по одному и тому же контракту. В Ruby или Scheme вы проверяете эти соглашения путем написания тестов, которые работают во время выполнения.

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

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

JasonTrue
источник
1
Что касается управления зависимостями, вы можете взглянуть на NuGet . Вот хорошая статья об этом от Фила Хаака
Конрад Фрикс
В использованных мною реализациях схемы R6RS (например, Ikarus, Chez и Ypsilon) зависимости обрабатываются автоматически на основе импорта библиотеки. Зависимости найдены и при необходимости скомпилированы и кэшированы для будущего импорта.
Дхарматех
Знаком с Nuget и, таким образом, мой комментарий, что это «относительно незрелый»
JasonTrue