命名空间的设计目的是提供一种让一组名称于其他名称分割开来的方式,在一个命名空间中声明的类的名称与另一个命名空间中声明的相同的类的名称不冲突。

我们举一个计算机系统中的例子,一个文件夹中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。

自定义命名空间

命名空间的定义是以关键字namespace开始,后面跟命名空间的名称,如下所示:

1
2
3
4
namespace namespace_name
{
// 代码声明
}

下面演示了命名空间的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;
namespace first_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside first_space");
}
}
}
namespace second_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside second_space");
}
}
}
class TestClass
{
static void Main(string[] args)
{
first_space.namespace_cl fc = new first_space.namespace_cl();
second_space.namespace_cl sc = new second_space.namespace_cl();
fc.func();
sc.func();
Console.ReadKey();
}
}

上述代码的运行结果如下:

1
2
Inside first_space
Inside second_space

using关键字

using关键字表明程序使用的是给定命名空间中的名称。比如我们在程序中使用System命名空间,其中

您也可以使用 using 命名空间指令,这样在使用的时候就不用在前面加上命名空间名称。该指令告诉编译器随后的代码使用了指定命名空间中的名称。下面的代码演示了命名空间的应用。

让我们使用 using 指定重写上面的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;
using first_space;
using second_space;

namespace first_space
{
class abc
{
public void func()
{
Console.WriteLine("Inside first_space");
}
}
}
namespace second_space
{
class efg
{
public void func()
{
Console.WriteLine("Inside second_space");
}
}
}
class TestClass
{
static void Main(string[] args)
{
abc fc = new abc();
efg sc = new efg();
fc.func();
sc.func();
Console.ReadKey();
}
}

嵌套命名空间

命名空间可以被嵌套,即您可以在一个命名空间内定义另一个命名空间,如下所示:

1
2
3
4
5
6
7
8
namespace namespace_name1 
{
// 代码声明
namespace namespace_name2
{
// 代码声明
}
}

可以使用点(.)运算符访问嵌套的命名空间的成员,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System;
using SomeNameSpace;
using SomeNameSpace.Nested;

namespace SomeNameSpace
{
public class MyClass
{
static void Main()
{
Console.WriteLine("In SomeNameSpace");
Nested.NestedNameSpaceClass.SayHello();
}
}

// 内嵌命名空间
namespace Nested
{
public class NestedNameSpaceClass
{
public static void SayHello()
{
Console.WriteLine("In Nested");
}
}
}
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
In SomeNameSpace
In Nested

在平时的开发中,我们可能会遇到这样的命名空间写法:namespace View.Effect.Pfx这从概念上来讲并不属于嵌套命名空间,但是在使用上来讲这和嵌套命名空间的使用是没有区别的。

namespace在使用中遇到的疑惑

背景

我在使用C++的时候就对namespace的理解十分欠缺。而到了C#中,我发现namespace的作用相较于C++好像发生了变化,我将通过下面这个场景来描述我的疑惑:我现在创建了两个脚本分别为脚本A和脚本B,在这两个脚本中分别定义了类A和类B,我们要在脚本B中定位并使用脚本A中的类A。

C++和C#定位并识别不同脚本中类的方式

对于C#来讲,在类B中访问类A的关键是确保C#编译器能够找到并识别类A,有以下几个步骤:

  1. 确保源代码文件包含公开定义的类A。如果类A不是公共public类型,按照访问修饰符的规定,类B可能无法获取类A,所以应该将类A设置为public。
  2. 如果类B和类A位于同一个命名空间,它们应该能够相互访问,无需额外的操作。
  3. 如果类A和类B位于不同的命名空间,则需要在类B的源文件顶部使用using语句导入类A所在的命名空间
  4. 如果类A没有制定命名空间,那么它属于全局命名空间,这种情况下我们可以直接在类B中使用类A,而无需额外的using语句,当然在实际的项目中,我们通常为类定义合适的命名空间来实现更好的代码组织、避免名称冲突,提高代码的可读性和可维护性。只有在极少数的特定场景下才考虑将类放入全局命名空间。
  5. 如果类A和类B位于不同的项目/程序集中,比如Unity的插件,要确保正确地引用了包含类A的程序集。在Unity编辑器中,可以将包含类A的DLL拖放至Asset目录,然后Unity会自动处理引用关系。而在普通的C#中,可以通过Visual Studio等IDE添加对应的.dll文件作为引用。

对于C++来讲,在类B中能够找到并使用类A涉及以下几个步骤:

  1. 声明:确保类A在对应的头文件中公开声明
  2. 引用:在类B所在的头文件的顶部包含类A的头文件,这里要注意使用正确的文件路径和文件名
  3. 访问
    • 静态成员:通过类名::成员的方式直接访问静态成员
    • 非静态成员:创建一个类A的对象实例,然后使用类名.成员访问非静态成员,如果是指针,则使用->
  4. 如果类B和类A位于不同的项目,需要确保正确地构建和链接它们,例如,将项目A构建为静态库或动态库(.lib.dll),然后将其添加到项目B的链接器输入中。此外,别忘了在类B中包含类A所在的头文件。

所以C++和C#在类B如何找到并使用类A的过程中有一定的相似性,尽管它们分别属于不同的编程范式。下面是一些主要的区别

  1. 文件包含:
    • C++需要使用include语句在头文件中包含对应的头文件,这就是在编译时,将类A的声明引入到类B的头文件中。
    • C#需要使用using语句指定某个命名空间,如果类A和类B不在同一个命名空间的话,而不需要指定具体的文件名,C#会在项目范围内自动处理相关类型的引用
  2. 引用语法
    • C++:类成员的访问语法是对象名.成员或者对象指针->成员
    • C#:类成员访问语法也是对象名.成员,实际上不区分值类型和引用类型
  3. 编译和链接方式:
    • C++:编译器需要在编译阶段处理包含关系,每个源文件在被编译前通过预处理器将相关文件展开、合并,然后形成单独的目标文件。在链接阶段,再将所有目标文件以及第三方库进行链接生成可执行程序。
    • C#:基于.NET平台,源代码在一开始被编译成中间语言,而各自的类分别保存在它们自己的文件中,在运行时,IL会通过即时编译变为本地机器码,整个过程中,相关类型之间的引用在运行时进行解析。

其实我们想要在类B中访问类A的方法或者变量,与命名空间的关系不大。关键是使用正确的访问修饰符以及合适地使用对象或者引用。

有以下两种情况:

  1. 访问静态成员,如果类A中有一个静态方法或或静态变量,我们可以直接使用类名加上成员名进行访问
  2. 访问非静态成员:对于非静态成员,我们需要创建一个类A的对象实例,然后通过该实例引用对应的方法和变量。确保这些方法和变量受到适当的访问修饰符保护。

不过命名空间可能会影响上述过程,如果类A和类B位于同一个命名空间,则无需特别声明;但是如果它们位于不同的命名空间,那么在类B的文件顶部需要添加using语句来引用类A所在的命名空间。所以,理解访问修饰符如何与对象或引用相结合是使用的关键。同时,确保在跨命名空间访问时能正确地使用using语句。

总结:C++和C#在类B定位并使用类A的基本概念相似,都需要先导入声明,然后在其他类中使用。主要的不同点在于包含/导入方式、访问语法以及编译链接思路。