檔案內部類型
注意
本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。
功能規格與已完成實作之間可能有一些差異。 這些差異是在的相關
總結
允許在最上層類型宣告中加入 file
修飾詞。 類型只存在於宣告它的檔案中。
// File1.cs
namespace NS;
file class Widget
{
}
// File2.cs
namespace NS;
file class Widget // different symbol than the Widget in File1
{
}
// File3.cs
using NS;
var widget = new Widget(); // error: The type or namespace name 'Widget' could not be found.
動機
我們的主要動機來自程式碼生成器。 來源產生器的運作方式是將檔案新增至用戶的編譯。
- 這些檔案應該能夠包含實作詳細數據,這些詳細數據會隱藏於編譯的其餘部分,但可在宣告所在的檔案中使用。
- 我們想要減少產生器「搜尋」類型名稱的需求,這些類型名稱不會與其他產生器程序代碼中的宣告相衝突。
詳細設計
當類型具有 file
修飾詞時,據說它是 檔案作用域 類型。
可及性
file
修飾詞未分類為輔助功能修飾詞。 在型別上,任何存取修飾詞都無法與 file
一起使用。
file
被視為與無障礙獨立的概念。 由於檔案本地類型無法嵌套,因此只有預設的存取權限 internal
可用於 file
類型。
public file class C1 { } // error
internal file class C2 { } // error
file class C3 { } // ok
命名
實作可確保在不同檔案中,具有相同名稱的檔案區域類型在執行期間會被區分開來。 中繼資料中的型別可訪問性和名稱是由實作定義的。 其意圖是允許編譯程式在運行時間中採用任何適合此功能的未來存取限制功能。 預期在初始實作階段,將會使用 internal
無障礙功能,並且會使用依賴於類型宣告所在檔案的難以辨識的自動生成名稱。
查詢
我們修改 成員查詢 章節,內容如下(粗體的新文字):
- 接下來,如果
K
為零,則會移除宣告包含類型參數的所有巢狀類型。 如果K
不是零,則會移除具有不同類型參數數目的所有成員。 當K
為零時,不會移除具有類型參數的方法,因為類型推斷程式 (1.6.3) 可能會推斷類型自變數。- 接下來,讓編譯單位 F 包含發生成員查找的表達式。 在 F 中未宣告的所有屬於檔案本地類型的成員都會從集合中移除。
- 下一步,如果一組可存取的成員包含檔案本機類型,則所有不是檔案本機類型的成員都會從集合中移除。
備註
這些規則不允許在聲明的檔案之外使用文件本地類型。
這些規則也允許檔案區域類型 隱藏 命名空間或非檔案區域類型:
// File1.cs
class C
{
public static void M() { }
}
// File2.cs
file class C
{
public static void M() { }
}
class Program
{
static void Main()
{
C.M(); // refers to the 'C' in File2.cs
}
}
請注意,我們不會更新規格 範圍的 區段。這是因為,如同規格所述:
名稱
範圍是程式文字的區域,其中可以參考名稱所宣告的實體,而不需限定名稱。
實際上,範圍只會影響對於非限定名稱的查找。 這不太是我們可以利用的正確概念,因為我們還需要影響合格名稱的查詢。
// File1.cs
namespace NS1
{
file class C
{
public static void M() { }
}
}
namespace NS2
{
class Program
{
public static void M()
{
C.M(); // error: C is not in scope
NS1.C.M(); // ok: C can be accessed through NS1.
}
}
}
// File2.cs
namespace NS1
{
class Program
{
C.M(); // error
NS1.C.M(); // error
}
}
因此,我們不會以類型所包含的範疇來指定功能,而是將其作為成員查找中的額外「篩選規則」。
屬性
檔案區域類別可以是屬性類型,並且可以在檔案區域類型和非檔案區域類型中用作屬性,正如該屬性類型是非檔案區域類型一樣。 檔案本機屬性類型的元數據名稱仍會經歷與其他檔案本機類型相同的名稱產生策略。 這表示透過硬編碼的字串名稱來偵測文件範圍的類型是否存在,可能不切實際,因為這需要依賴編譯器的內部名稱生成策略,而這種策略可能會隨時間而改變。 不過,透過 typeof(MyFileLocalAttribute)
偵測是可行的。
using System;
using System.Linq;
file class MyFileLocalAttribute : Attribute { }
[MyFileLocalAttribute]
public class C
{
public static void Main()
{
var attribute = typeof(C).CustomAttributes.Where(attr => attr.AttributeType == typeof(MyFileLocalAttribute)).First();
Console.Write(attribute); // outputs the generated name of the file-local attribute type
}
}
簽章中的使用方式
一般需要防止檔案區域類型出現在成員參數、返回值和類型參數條件約束中,而在成員使用點時,檔案區域類型可能不在有效範圍內。
注意,可以允許非檔案本機類型實作檔案本機介面,這類似於類型可以實作存取權限較低的介面。 根據介面成員中存在的類型,可能會導致違反以下章節中的規則。
只允許檔案內部類型成員使用簽名。
或許最簡單的方式是要求檔案本地類型只能出現在簽名中或作為其他檔案本地類型的基底類型:
file class FileBase
{
}
public class Derived : FileBase // error
{
private FileBase M2() => new FileBase() // error
}
file class FileDerived : FileBase // ok
{
private FileBase M2() => new FileBase(); // ok
}
請注意,這確實限制了在明確實作時的使用方式,即使這類用法是安全的。 為了簡化這項功能初始版本的規則,我們會這麼做。
file interface I
{
void M(I i);
}
class C : I
{
void I.M(I i) { } // error
}
global using static
在 global using static
指示詞中使用檔案區域類型是編譯時期錯誤,亦即
global using static C; // error
file class C
{
public static void M() { }
}
實現/覆寫
檔案範圍的類型宣告可以像一般類型宣告一樣,實作介面、覆寫虛擬方法等。
file struct Widget : IEquatable<Widget>
{
public bool Equals(Widget other) => true;
}