在 C#、Java 和 Python 这三种语言中,命名空间和导入机制都是用来组织和管理代码及其依赖的工具,但它们在具体实现和使用方式上存在一些差异。
1、问题背景
在 Java 和 Python 世界中,我们可以通过查看源文件来了解所有导入的来源(即我们知道导入类的定义所在文件)。例如:
在 Java 中:
java">import javafoo.Bar;public class MyClass {private Bar myBar = new Bar();
}
我们可以立即看到 Bar 类是从 javafoo 导入的。因此,Bar 在 /javafoo/Bar.java 中声明。
在 Python 中:
python">import pythonbaz
from pythonfoo import Barmy_bar = Bar()
my_other = pythonbaz.Other()
这里,很明显,Bar 来自 pythonfoo 包,而 Other 显然来自 pythonbaz。
在 C# 中:
using foo
using baz
using anothernamespace
...public class MyClass
{private Bar myBar = new Bar();
}
有两个问题:
- 我如何知道 Bar 类在哪里声明?它来自 foo 命名空间、bar 命名空间还是 anothernamespace 命名空间?(编辑:不使用 Visual Studio)
- 在 Java 中,包名对应于目录名(或者说,这是一种非常强的约定)。因此,当您看到一个类的来源包时,您就知道它在文件系统中的目录。在 C# 中,似乎没有这样的命名空间约定,或者我遗漏了什么?那么,我不知道要查找哪个目录和文件(在弄清楚该类来自哪个命名空间之后)。
澄清编辑:我知道 Python 和/或 Java 允许使用通配符导入,但这些语言中的“文化”不赞同它们(至少在 Python 中是这样,我不确定 Java 中是否是这样)。此外,在 Java 中,IDE 通常会帮助您创建最小导入(正如 Mchl. 在下面评论的那样)。
2、解决方案
答案 1:
- 实际上,您也可以在 Java 中执行相同的操作:
java">import java.util.*;
import java.io.*;...InputStream x = ...;
InputStream 来自 java.util 还是 java.io?当然,您可以选择不使用该功能。
现在,从理论上讲,这意味着当您使用文本编辑器查看时,您无法分辨 C# 中的类型来自哪里…但在实践中,我发现这不是问题。您实际查看代码并无法使用 Visual Studio 的频率是多少?
- 当然,您也可以在 .NET 中使用相同的约定 - 而且我确实这样做了,尽管我没让空目录向上延伸…因此,如果我创建一个默认命名空间为 X.Y 的项目,那么 X.Y.Foo 将位于 Foo.cs 中,而 X.Y.Z.Bar 将位于 Z\Bar.cs 中。
Visual Studio 默认也会这样做 - 如果您创建一个子文件夹,它将使用基于项目默认值和文件夹结构的命名空间创建新类。
当然,您也可以在任何旧文件中声明类型 - 但大多数人都会遵循使用与相应文件名相对应的类型声明的常规约定。在泛型使委托声明变得更少见之前,我曾经有一个 Delegates.cs 文件,其中包含特定命名空间的所有委托声明(而不是有一堆单一声明文件),但现在这不再成为问题。
答案 2:
- 你是对的。乍一看没有“直接”的方法来知道您的类来自哪里,但是,正如您所说,您可以在 IDE 中跳转到它。但以这种方式声明类只是最短的方式来做到这一点。如果您愿意,假设您的 Bar 类来自 Foo 类,您可以声明它
private foo.Bar myBar = new foo.Bar();
这样可以帮助您在第一时间就知道您的类来自哪里。
- 当您添加对类的引用时,“添加引用”窗口会为您提供您正在寻找的信息。
如果您想在声明它之后了解它们来自哪里,则有一个名为“解决方案资源管理器”的窗口,您可以在“引用”树节点下找到这些信息。
您可以将其设置为始终可见(默认情况下它是可见的)
答案 3:
对于 Java 和 Python,这确实是一个约定问题 - 导入您需要的类,而不是使用通配符导入整个包。
在 C# 中,您不能对所需的特定类执行 using 指令,因为它只适用于命名空间(如下面的错误所示)。看起来 C# 仍然忠于 C++ 的命名空间概念,并将其与 #include 指令合并为一种引用外部类的简单方法。
using System.Net.Sockets.Socket; // Gives the following error:// A using namespace directive can only be applied to namespaces;
// 'System.Net.Sockets.Socket' is a type not a namespace
关于双重 Bar 声明,很简单 - 如果编译器没有办法知道,它会给出错误:
using Foo; // Has class Bar {}
using Goo; // Has class Bar {}Bar b = new Bar(); // Gives the following error:
// 'Bar' is an ambiguous reference between 'Foo.Bar' and 'Goo.Bar'
答案 4:
您如何知道 Bar 类在哪里声明?它来自 foo 命名空间、bar 命名空间还是 anothernamespace 命名空间?当然,Visual Studio 允许我跳转到那里,但如果我只是在编辑器中快速查看源文件怎么办?
从本质上讲,您不知道 - 但 IntelliSense 正在提供帮助。您实际上无法通过快速浏览代码来确定,但您可以用光标将鼠标悬停在符号上,例如。但这在 Python 中也是可能的:
python">from foobar import *
from bazbaz import *a_bar = Bar()
现在 Bar 从哪里来?
在 C# 中,似乎没有这样的命名空间约定,或者我遗漏了什么?那么,我知道要查找哪个目录和文件(在弄清楚该类来自哪个命名空间之后)。
不,程序集不对应于目录结构,我认为这是一件好事。解决方案资源管理器提供了一个对添加到项目的所有引用的视图。这些引用是程序集,它们以 PE 文件的形式具体表示在计算机上的某个位置。您可以轻松查看引用的属性,以查看物理文件位于何处。编辑:为了不与本主题中的其他答案相矛盾并造成混淆:我说程序集名称不对应目录名称的意思是它实际上并没有被强制执行。
答案 5:
我不知道其他项目的情况,但我敢肯定,在我参与的每一个 .NET 项目中,我们都使用了这个约定,即命名空间始终对应于文件夹名称(除了最外层的命名空间对应于该命名空间所属的程序集)。
答案 6:
通常,当您将鼠标悬停在类型名称上时,工具提示会显示一些额外信息。否则,您始终可以右键单击类型名称,然后“转到定义”。
这几种语言虽然在命名空间和导入机制的实现细节上各有特色,但它们共同的目标是提高代码的可管理性、可重用性和组织性。