命名空間
C#程序是利用命名空間組織起來的。命名空間既用做程序的“內部”組織系統,也用做向“外部”公開的組織系統(即一種向其他程序公開自己擁有的程序元素的方法)。
編譯單元與命名空間編譯單元定義了源文件的總體結構。一個C#程序由一個或多個編譯單元組成,每個編譯單元都用一個單獨的源文件來保存。編譯C#程序時,所有這些編譯單元一起進行處理。因此,這些編譯單元間可以互相依賴,甚至以循環方式互相依賴。
注意 如果一個程序包含多個源文件,編譯程序時,需要將所有的源文件放在一起編譯,命令格式為:
csc <source file1> <source file2> ... <source file n>
使用如下的命令可以顯示csc的所有參數的意義:
csc /?
如果使用Visual Studio 2005,則可以創建一個項目,將所有的源文件都放在項目中。
為了最大限度地避免類名沖突,C#使用命名空間來組織類。在同一個命名空間內,類名不能重名,但是在不同的命名空間中,可以使用相同的類名。
一個編譯單元可以包括零個或多個命名空間定義。命名空間是可以嵌套的,即在一個命名空間內部還可以定義其他的命名空間。
命名空間聲明可以作為頂級聲明出現在編譯單元中,或者作為成員聲明出現在另一個命名空間聲明內。當命名空間聲明作為頂級聲明出現在編譯單元中時,該命名空間成為全局命名空間的一個成員。當一個命名空間聲明出現在另一個命名空間聲明內時,該內部命名空間就成為包含著它的外部命名空間的一個成員。無論是哪種情況,一個命名空間的名稱在它所屬的命名空間內必須是唯一的。
用關鍵字namespace來聲明命名空間,方式如下:
namespace 【命名空間名】『.【命名空間名】』
{
『using指令』
『命名空間體』
}『;』
命名空間隱式地為public,而且在命名空間的聲明中不能包含任何訪問修飾符。
在『命名空間體』內,可選用零個或者多個using指令來導入其他命名空間和類型的名稱,這樣就可以直接地而不是通過限定名來引用它們。請注意,所有的using指令都必須出現在任何成員聲明之前。
命名空間名稱可以是單個標識符或者是由“.”標記分隔的標識符序列。後一種形式允許一個程序直接定義一個嵌套命名空間,而不必按詞法嵌套若干個命名空間聲明。例如,
namespace N1.N2
{
class A {}
class B {}
}
在語義上等效於:
namespace N1
{
namespace N2
{
class A {}
class B {}
}
}
完全限定名每個命名空間和類型都具有一個完全限定名(fully qualifIEd name),該名稱在所有其他命名空間或類型中唯一標識該命名空間或類型。
命名空間或類型N的完全限定名按下面的方法確定:
— 如果N是全局命名空間的成員,則它的完全限定名為 N。
— 否則,它的完全限定名為S.N,其中S是聲明了N的命名空間或類型的完全限定名。
N的完全限定名是從全局命名空間開始通向N的標識符的完整分層路徑。由於命名空間或類型的每個成員都必須具有唯一的名稱,因此,如果將這些成員名稱置於命名空間或類型的完全限定名之後,這樣構成的成員完全限定名一定符合唯一性。
下面的示例演示了若干命名空間和類型聲明及其關聯的完全限定名。
class A {} // A
namespace X // X
{
class B // X.B
{
class C {} // X.B.C
}
namespace Y // X.Y
{
class D {} // X.Y.D
}
}
namespace X.Y // X.Y
{
class E {} // X.Y.E
}
命名空間是可擴充的,兩個具有相同的完全限定名的命名空間實際上是同一個命名空間,在其中聲明的成員都位於同一個命名空間中。比如,
namespace N1.N2
{
class A {}
}
和
namespace N1.N2
{
class B {}
}
兩個命名空間聲明為同一個命名空間提供成員,它們分別聲明了具有完全限定名N1.N2.A和N1.N2.B的兩個類。由於兩個聲明為同一命名空間提供成員,因此如果它們分別包含一個同名成員的聲明,就會出現錯誤。
using指令
using指令(using directive)方便了對在其他命名空間中定義的命名空間和類型的使用。using指令影響命名空間或者類型名稱的解析過程,但是using指令不會聲明任何類型,即using指令不會在使用它們的編譯單元或命名空間中定義新成員。
using的基本形式為:
using 【命名空間名】;
命名空間名稱可以是單個標識符或者是由“.”標記分隔的標識符序列。using指令將【命名空間名】所標識的命名空間內的類型成員導入當前編譯單元中,從而可以直接使用每個被導入的類型的標識符,而不必加上它們的完全限定名。
在包含using指令的編譯單元或命名空間體中的成員聲明內,可以直接引用包含在給定命名空間中的那些類型。例如:
namespace N1.N2
{
class A {}
}
namespace N3
{
using N1.N2; // 引入了命名空間N1.N2
class B
{
void f()
{
A oA = new A(); // 直接引用N1.N2命名空間內的A
}
}
}
上面的示例中,在N3命名空間中的成員聲明內,N1.N2的類型成員是直接可用的,所以可以直接引用A。
using指令導入包含在給定的命名空間中的類型,但是,它不導入該命名空間所嵌套的命名空間。例如:
namespace N1.N2
{
class A {}
}
namespace N3
{
using N1; // 僅僅引入N1命名空間
class B
{
void f()
{
A oA = new A(); // 錯誤:不能直接引用N1.N2命名空間內的A
}
}
}
在上面的例子中,using指令導入了包含在N1中的類型,但是不導入嵌套在N1內的命名空間。因此,不能直接在B中引用N2.A。
using指令還可以使用指定別名的形式:
using 【別名】 = 【命名空間或類型名】;
在當前編譯單元內,【別名】標識符可以用來完全替代【命名空間或類型名】。例如:
namespace N1.N2
{
class A {}
}
namespace N3
{
using A = N1.N2.A; // 將A指定為N1.N2.A的別名
class B
{
void f()
{
A oA = new A(); // A等價於N1.N2.A
}
}
}
上面的示例中,在N3命名空間中的聲明成員內,A是N1.N2.A的別名,因此方法f()內創建的A對象就是創建了N1.N2.A的對象。
using指令中的【別名】標識符在直接包含該using指令的編譯單元或命名空間的聲明空間內必須是唯一的。例如:
namespace N3
{
class A {} // A已經定義在命名空間N3中
}
namespace N3
{
using A = N1.N2.A; // 錯誤, A已經存在於命名空間N3中
}
上例中,N3已包含了成員A,因此在using指令中使用A作為標識符會導致一個編譯時錯誤。同樣,如果同一個編譯單元或命名空間體中的兩個或更多using指令用相同名稱聲明別名,也會導致一個編譯時錯誤。
注意 上述兩個例子的差別在哪裡?
using指令指定的別名是不可傳遞的,它僅影響使用它的的編譯單元或命名空間體,而不會影響具有相同限定名的命名空間。
比如:
namespace N3
{
using R = N1.N2; // R只能用於這個命名空間體內
}
namespace N3
{
class B
{
void f()
{
R.A oA = new R.A(); // 錯誤,R是未知的
}
}
}
using指令的順序並不重要。using指令可以為任何命名空間或類型創建別名,包括它所處的命名空間,以及嵌套在該命名空間中的其他任何命名空間或類型。
對一個命名空間或類型進行訪問時,無論用它的別名,還是用它的所聲明的名稱,結果是完全相同的。例如,
namespace N1.N2
{
class A {}
}
namespace N3
{
using R1 = N1;
using R2 = N1.N2;
class B
{
N1.N2.A a; // 引用N1.N2.A
R1.N2.A b; // 引用N1.N2.A
R2.A c; // 引用N1.N2.A
}
}
上面的代碼中,名稱N1.N2.A、R1.N2.A和R2.A是等效的,它們都引用完全限定名為N1.N2.A的類。
注意,using指令導入的名稱會被包含該指令的編譯單元或命名空間體中具有相同名稱的成員所隱藏。例如:
namespace N1.N2
{
class A
{
// 其他代碼
}
class B
{
// 其他代碼
}
}
namespace N3
{
using N1.N2;
class A // N3.A會隱藏N1.N2.A
{
// 其他代碼
}
class RA
{
void F()
{
A a = new A(); // A 引用 N3.A 而不是 N1.N2.A
}
// 其他代碼
}
}
上面的代碼中,在N3命名空間中的成員聲明內,A引用N3.A而不是N1.N2.A。
當由同一編譯單元或命名空間體中的using指令導入多個命名空間時,如果它們所包含的類型中有重名的,則直接引用該名稱就被認為是不明確的。請看下面的示例:
namespace N1
{
class A {}
}
namespace N2
{
class A {}
}
namespace N3
{
using N1;
using N2;
class B
{
void f()
{
A oA = new A(); // 錯誤, A的意義不明確
}
}
}
其中,N1和N2都包含一個成員A,而由於N3將兩者都導入,所以在N3中引用A會導致一個編譯時錯誤。
有兩種方法能夠解決這種沖突:使用限定名來引用A,或者利用using指令的別名形式為想要引用的某個特定的A引入一個別名。例如:
namespace N1
{
class A {}
}
namespace N2
{
class A {}
}
namespace N3
{
using N1;
using N2;
using A = N1.A;
class B
{
void f()
{
A oA = new A(); // A 是指 N1.A
}
}
}