为什么.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#世界以额外的间接级别以这种方式执行操作?


10
.NET的汇编和类解析机制已经运行了十多年。我认为您对这种方式的设计缺乏基本的误解(或研究不足),例如在绑定时支持程序集重定向等,以及许多其他有用的解决机制
Kev 2012年

我非常确定using语句的DLL解析会中断并行执行。另外,如果存在
一对一的关系

更高级别的间接?
user541686 2012年

@gilles感谢您对问题的编辑和改进!
dharmatech

您的某些要点是Visual Studio特有的,将它们与一种语言进行比较并不公平(例如,Projects中的DLL引用)。完整.NET环境的更好比较是Java + Eclipse。
罗斯·帕特森

Answers:


22

框架设计指南第3.3节中的以下注释。程序集和Dll的名称提供了为什么命名空间和程序集是分开的见解。

BRAD ABRAMS在CLR设计的早期,我们决定将平台的开发人员视图(命名空间)与平台(程序集)的包装和部署视图区分开。这种分离允许每个基于其自己的标准进行独立优化。例如,我们可以自由地将名称空间分解为与功能相关的类型的组(例如,System.IO中的所有I / O内容),而可以出于性能(加载时间),部署,服务或版本控制的原因而对程序集进行分解。


似乎是迄今为止最权威的资料。谢谢康拉德!
dharmatech

+1获得该死的权威性答案。而且,每个“ 为什么C#做<X> ”问题也都需要通过“ Java做<X>的镜头如下:…… ”来解决,因为C#是(明确地)对Sun的反应。微软针对Windows上Java的不同议程。
罗斯·帕特森

6

它增加了灵活性,并允许按需加载库(在问题中称为模块)。

一个名称空间,多个库:

优点之一是,我可以轻松地将一个库替换为另一个库。假设我有一个命名空间MyCompany.MyApplication.DAL和一个库DAL.MicrosoftSQL.dll,其中包含所有SQL查询和其他特定于数据库的内容。如果我希望该应用程序与Oracle兼容,则只需添加DAL.Oracle.dll,保持相同的名称空间即可。从现在开始,我可以为需要与Microsoft SQL Server兼容的客户提供带有一个库的应用程序,为使用Oracle的客户提供与另一个库的应用程序。

在此级别更改名称空间将导致重复代码,或者需要更改using每个数据库的源代码中的所有s。

一个库,多个名称空间:

就可读性而言,在一个库中具有多个名称空间也是有利的。如果在一个类中,我仅使用一个名称空间,则将其仅放在文件顶部。

  • 对于阅读源代码的人和编写者本人来说,拥有大型库的所有名称空间都会使他们感到困惑,而Intellisense在给定的上下文中有太多的建议可言。

  • 如果库较小,则每个文件一个库将对性能产生影响:每个库必须按需加载到内存中,并在运行应用程序时由虚拟机进行处理;更少的文件加载意味着更好的性能。


第二种情况不需要文件名与名称空间分开(尽管允许这种分隔使分隔大大简化了),因为给定的程序集可以轻松地具有许多子文件夹和文件。此外,还可以将多个程序集嵌入到同一DLL中(例如,使用ILMerge)。Java Works采用这种方法。
布赖恩

2

看来您正在选择重载“名称空间”和“模块”的术语。当事物不符合您的定义时,您认为事物是“间接的”也就不足为奇了。

在大多数支持名称空间的语言(包括C#)中,名称空间不是模块。命名空间是定义名称的一种方式。模块是范围行为的一种方式。

通常,.Net运行时支持模块的概念(与隐式使用的模块定义稍有不同),但很少使用。我只看到它在SharpDevelop中构建的项目中使用,主要是这样,您可以从使用不同语言构建的模块构建单个DLL。相反,我们使用动态链接库来构建库。

在C#中,只要名称空间都在同一个二进制文件中,它们就可以在没有任何“间接层”的情况下进行解析。不需要考虑的任何间接调用都是编译器和链接器的责任。一旦开始构建具有多个依赖项的项目,就可以引用外部库。一旦您的项目引用了外部库(DLL),编译器就会为您找到它。

在Scheme中,如果需要加载外部库,则必须先做类似的事情(#%require (lib "mylib.ss")),或者像我记得的那样直接使用外部函数接口。如果您使用的是外部二进制文件,则需要花费相同的工作量来解析外部二进制文件。可能是您最常用的库是如此常用,以至于有一个基于Scheme的shim将从您那里抽象出来,但是如果您不得不编写自己的与3rd party库的集成,则实际上您需要做一些工作来“加载” “ 图书馆。

在Ruby中,模块,命名空间和文件名的连接实际上比您想象的要少得多。LOAD_PATH使事情变得有些复杂,并且模块声明可以在任何地方。Python可能更接近以您在Scheme中看到的方式来做事,除了C中的3rd party库仍然会增加(小的)折痕。

此外,诸如Ruby,Python和Lisp之类的动态类型语言通常没有与静态类型语言相同的“合同”方法。在动态类型的语言中,通常只建立一种“绅士协议”,代码将对某些方法做出响应,并且如果您的类似乎在讲相同的语言,则一切都很好。静态类型语言具有在编译时强制执行这些规则的其他机制。在C#中,使用这样的协定至少可以为您提供遵守这些接口的适度有用的保证,这可以使您将插件和替换捆绑在一起,并在某种程度上保证其通用性,因为您都可以针对同一协定进行编译。在Ruby或Scheme中,您可以通过编写在运行时可以运行的测试来验证这些协议。

这些编译时间保证带来了可衡量的性能优势,因为方法调用不需要双重调度。为了在Lisp,Ruby,JavaScript或其他地方获得这些好处,现在仍需要在专用VM中进行即时静态编译类的机制,这种机制现在仍然有些奇怪。

C#生态系统仍然对相对不成熟的支持是对这些二进制依赖项的管理。Java已经使用Maven来解决确保拥有所有必要依赖项的问题,而C#仍然具有相当原始的类似于MAKE的方法,该方法涉及在策略上提前将文件放置在正确的位置。


1
关于依赖项的管理,您可能需要看一下NuGet。这是Phil Haack 的一篇不错的文章
Conrad Frix 2012年

在我使用的R6RS Scheme实现中(例如Ikarus,Chez和Ypsilon),依赖关系是根据库导入自动处理的。找到依赖项,并在必要时编译和缓存以供将来导入。
dharmatech

熟悉Nuget,因此我评论说它“相对不成熟”
JasonTrue 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.