C# 作用域

2018-09-28 18:42 更新

作用域

名稱的作用域(scope)是一個(gè)程序文本區(qū)域(region),在其中可以用過名稱引用實(shí)體聲明而不對該名稱加以限定條件(qualification)。作用域可嵌套,內(nèi)部作用域可重聲明外部作用域名稱的含義(但這并不會移除在第三章第三節(jié)中對其的限制——在嵌套塊內(nèi)不能聲明與閉包塊(enclosing block)內(nèi)局部變量同名的局部變量)。因此可以說此外部作用域的名稱在該內(nèi)部作用域覆蓋的程序文本區(qū)域內(nèi)是隱藏(hidden)的,只能通過限定名來訪問外部名稱。

  • namespace-member-declaration(第九章第五節(jié))聲明的命名空間成員的作用域——如果沒有其它 namespace-declaration 對其閉包的話——是整個(gè)(entire)程序文本。
  • namespace-declaration 內(nèi)的 namespace-member-declaration 聲明的命名空間成員的作用域是——如果假設(shè)該命名空間成員聲明的完全限定名為 N——完全限定名為 N 或以 N 為始、后跟一個(gè)句號(period)的每個(gè) namespace-declarationnamespace-body。
  • extern-alias-directive 定義的名稱的作用域擴(kuò)展到直接包含其編譯單元或命名空間主體的 using-directives、global-attributesnamespace-member-declarations。extern-alias-directive 并不會為底層聲明空間(underlying declaration space)增加任何成員。換句話說 extern-alias-directive 并不具傳遞性,相反其只會影響到在其內(nèi)出現(xiàn)的 compilation-unitnamespace-body。
  • using-directive(第九章第四節(jié))定義或?qū)氲拿Q的作用域擴(kuò)展到出現(xiàn) using-directivecompilation-unitnamespace-body 的整個(gè) namespace-member-declarationsusing-directive 能使零或多個(gè)命名空間或類型名在特定的 compilation-unitnamespace-body 變得可用,但并不會為底層聲明空間(underlying declaration space)增加任何成員。換句話說 using-directive 并不具傳遞性,相反其只會影響到在其出現(xiàn)的 compilation-unitnamespace-body。
  • 由一個(gè)在 class-declaration(第十章第一節(jié))中的 type-parameter-list 所聲明的類型形參的作用域是該 class-declarationclass-base、type-parameter-constraints-clauses 以及 class-body
  • 由一個(gè)在 struct-declaration(第十一章第一節(jié))中的 type-parameter-list 所聲明的類型形參的作用域是該 struct-declarationstruct-interfaces、type-parameter-constraints-clauses 以及 struct-body
  • 由一個(gè)在 interface-declaration(第十三章第一節(jié))中的 type-parameter-list 所聲明的類型形參的作用域是該 interface-declarationinterface-base、type-parameter-constraints-clauses 以及 interface-body
  • 由一個(gè)在 delegate-declaration(第十五章第一節(jié))中的 type-parameter-list 所聲明的類型形參的作用域是該 delegate-declarationreturn-type、formal-parameter-list 以及 type-parameter-constraints-clauses
  • class-member-declaration(第十章第 1.6 節(jié))所聲明的成員的作用域位于該聲明所出現(xiàn)的 class-body 之內(nèi)。此外,類成員的作用域擴(kuò)展到包含該成員且為可訪問的(accessibility,第三章第 5.2 節(jié))派生類的 class-body。
  • struct-member-declaration(第十一章第二節(jié))聲明的成員的作用域位于該聲明出現(xiàn)的 struct-body 之內(nèi)。
  • enum-member-declaration(第十四章第三節(jié))所聲明的成員的作用域位于該聲明出現(xiàn)的 enum-body 內(nèi)。
  • 位于 method-declaration(第十章第六節(jié))內(nèi)所聲明之參數(shù)的作用域是該 method-declarationmethod-body。
  • 位于 indexer-declaration(第十章第九節(jié))內(nèi)所聲明之參數(shù)的作用域是該 indexer-declarationaccessor-declarations。
  • 位于 operator-declaration(第十章第十節(jié))內(nèi)所聲明之參數(shù)的作用域是該 operator-declaration 的 block。
  • 位于 constructor-declaration(第十章第十一節(jié))內(nèi)所聲明之參數(shù)的作用域是該 constructor-declarationconstructor-initializerblock
  • 位于 lambda-declaration(第七章第十五節(jié))內(nèi)所聲明之參數(shù)的作用域是該 lambda-expressionlambda-expression-body。
  • 位于 anonymous-method-expression(第七章第十五節(jié))內(nèi)所聲明之參數(shù)的作用域是該 anonymous-method-expressionblock
  • 位于 labeled-statement(第八章第四節(jié))內(nèi)所聲明之標(biāo)簽的作用域是該聲明所在的 block。
  • 位于 local-variable-declaration(第八章第 5.1 節(jié))內(nèi)所聲明之局部變量的作用域是該聲明所在的 block。
  • 位于 switch 語句(第八章第 8.3 節(jié))的 switch-block 內(nèi)所聲明的局部變量的作用域是該 switch-block。
  • 位于 for 語句(第八章第 8.3 節(jié))的 for-initializer 內(nèi)所聲明的局部變量的作用域是該語句的 for-initializerfor-condition、for-iterator 以及所含之 statement
  • 位于 local-constant-declaration(第八章第 5.2 節(jié))內(nèi)聲明的局部變量的作用域是該聲明出現(xiàn)的 block。在該局部變量的 constant-declarator 之前的文本位置上引用該局部變量將出現(xiàn)一個(gè)「編譯時(shí)錯誤」。
  • 作為 foreach-statementusing-statement、lock-statementquery-expression 一部分所聲明的變量的作用域由給定構(gòu)造(construct)的擴(kuò)展(expansion)所決定。

在命名空間、類、結(jié)構(gòu)或枚舉成員的作用域內(nèi),可以在位于該成員聲明之前的文本位置上引用該成員。比方說

class A
{
    void F() {
        i = 1;
    }
    int i = 0;
}

在此,F(xiàn) 在 i 聲明之前引用它是合法的。

在局部變量的作用域內(nèi),當(dāng)引用局部變量的文本位置(textual position)在該局部變量聲明之前(local-variable-declarator)會出現(xiàn)一個(gè)「編譯時(shí)錯誤」。比方說

class A
{
    int i = 0;
    void F() {
        i = 1;   // 錯誤,在使用前先聲明
        int i;
        i = 2;
    }
    void G() {
        int j = (j = 1);     // 有效
    }
    void H() {
        int a = 1, b = ++a;  // 有效
    }
}

在上面方法 F 中,第一個(gè)對 i 的賦值實(shí)際上并不會引用外部作用域的字段。相反,它引用了本地局部變量,而其結(jié)果是引發(fā)一個(gè)「編譯時(shí)錯誤」,因?yàn)樵谖谋旧弦笙嚷暶髯兞?。在方?G 中,為 j 進(jìn)行聲明的同時(shí)在其初始化器內(nèi)使用 j 是有效的,這是因?yàn)椴]有在 local-variable-declarator 之前使用。在方法 H 中,后面的 local-variable-declarator 正確引用了局部變量(該局部變量在同一個(gè) local-variable-declarator 內(nèi)前面那個(gè) local-variable-declarator 中聲明了)。

本地布局變量的作用域規(guī)則被設(shè)計(jì)為保證(guarantee)表達(dá)式上下文(expression context)中使用的名稱的含義與在塊中使用的含義是相同的。如果局部變量的作用域只從其聲明之處其,到塊尾部截止,那么在上例中,第一個(gè)賦值將分配給實(shí)例變量(instance variable),第二次賦值將分配給局部變量,如果塊中的語句之后重新排列,會引發(fā)「編譯時(shí)錯誤」。

塊(block)中名稱的含義可能因名稱的使用上下文的不同而有所不同。在下例中,

using System;
class A {}
class Test
{
    static void Main() {
        string A = "hello, world";
        string s = A;         // 表達(dá)式上下文
        Type t = typeof(A);   // 類型上下文
        Console.WriteLine(s); // 輸出 "hello, world"
        Console.WriteLine(t); // 輸出 "A"
    }
}

在表達(dá)式上下文中的名稱 A 引用本地變量 A,在類型上下文中引用類型 A。

名稱隱藏

實(shí)體(entity)的作用域(scope)往往比實(shí)體聲明空間包含(encompasses)更多的程序文本。具體來說,實(shí)體的作用域可能會引入新的聲明空間,而其中或許會包含與該實(shí)體同名的實(shí)體。這類聲明導(dǎo)致原始實(shí)體變?yōu)?strong>隱藏的(hidden)。相反,如果實(shí)體沒有被隱藏,那么我們說它是可見的(visible)。

當(dāng)作用域通過嵌套交叉(overlap)或當(dāng)作用域通過繼承交叉,那么會導(dǎo)致名稱隱藏。兩個(gè)被隱藏的類型的特征在下面兩節(jié)中進(jìn)行介紹。

通過嵌套隱藏

在命名空間內(nèi)嵌套命名空間或類型,或者在類或結(jié)構(gòu)內(nèi)嵌套類型,以及性參與局部變量的聲明,都將導(dǎo)致嵌套隱藏(hiding through nesting)名稱。舉個(gè)例子。

class A
{
    int i = 0;
    void F() {
        int i = 1;
    }
    void G() {
        i = 1;
    }
}

在方法 F 內(nèi),實(shí)例變量 i 被局部變量 i 所隱藏,但在方法 G 內(nèi),i 依舊引用實(shí)例變量。

當(dāng)內(nèi)部作用域的名稱隱藏了外部作用域的名稱時(shí),它將隱藏該名稱的所有重載。下例中,

class Outer
{
    static void F(int i) {}
    static void F(string s) {}
    class Inner
    {
        void G() {
            F(1);        // 調(diào)用 Outer.Inner.F
            F("Hello");  // 錯誤
        }
        static void F(long l) {}
    }
}

調(diào)用(call)F(1) 將調(diào)用(invokes)內(nèi)部聲明的 F,這是因?yàn)樗型獠砍霈F(xiàn)的 F 都被內(nèi)部聲明所隱藏。同樣的,調(diào)用 F("Hello") 的結(jié)果是出現(xiàn)「編譯時(shí)錯誤」。

通過繼承隱藏

當(dāng)類或結(jié)構(gòu)重新聲明(redeclare)從基類繼承的名稱時(shí),會發(fā)生通過繼承隱藏(hiding through inheritance)其名稱。這種類型的名稱隱藏采取以下形式:

  • 常量、字段、屬性、事件或類型引入類或結(jié)構(gòu)后,會隱藏所有基類中的同名成員。
  • 方法引入類或結(jié)構(gòu)將隱藏所有同名的非方法基類成員(non-method base class members)和所有同簽名(方法名、參數(shù)數(shù)量、修飾符與類型)的基類方法。
  • 索引器引入類或結(jié)構(gòu)將隱藏所有同簽名(參數(shù)數(shù)量與類型)的基類索引。

管理運(yùn)算符聲明(governing operator declarations,第十章第十節(jié))的規(guī)則使其不可能在派生類中聲明一個(gè)與其基類內(nèi)同簽名的運(yùn)算符。因此,操作符不能相互隱藏。

與從作用域外部對名稱進(jìn)行隱藏相反,通過繼承的作用域隱藏名稱可訪問性會報(bào)出警告。下例中,

class Base
{
    public void F() {}
}
class Derived: Base
{
    public void F() {}  // 警告,隱藏了繼承到的名稱
}

派生類 DerivedF 的聲明會導(dǎo)致一個(gè)警告。隱藏所繼承的名稱實(shí)際上不是一個(gè)錯誤,因?yàn)檫@會妨礙基類自身的單獨(dú)改進(jìn)。比方說,上述情況可能會發(fā)生因?yàn)?Base 的后續(xù)版本可能會引入一個(gè) F 方法(而這個(gè)方法在之前版本中沒有)。如果上述情況是錯誤的,那么對單獨(dú)版本控制的基類的任何變化都會潛在導(dǎo)致派生類變得無效。

因隱藏繼承到的名字而引發(fā)的警告可通過 new 修飾符消除:

class Base
{
    public void F() {}
}
class Derived: Base
{
    new public void F() {}
}

修飾符 new 表示派生類 Derived 中的 F 是「新的」,它將隱藏所繼承到的(同簽名)成員。新成員的聲明將隱藏其所繼承到的,當(dāng)且僅當(dāng)位于新成員的作用域內(nèi)。

class Base
{
    public static void F() {}
}
class Derived: Base
{
    new private static void F() {} // 只會隱藏 Derived 內(nèi)的 Base.F
}
class MoreDerived: Derived
{
    static void G() { F(); }       // 調(diào)用 Base.F
}

上例中,派生類中的 F 聲明隱藏了(hide)其從基類繼承到的 F。但由于派生類中新 F 的可訪問性是 private,因此它的作用域達(dá)不到 MoreDerived。因此 MoreDerived.G 調(diào)用(call) F() 方法依舊合法,它將調(diào)用 Base.F。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號