Note
本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。
功能規格與已完成實作之間可能有一些差異。 這些差異已記錄在相關的 語言設計會議(LDM)備忘錄中。
您可以在 規範的文章中深入瞭解將功能規範納入 C# 語言標準的過程。
Champion 期數:https://github.com/dotnet/csharplang/issues/9856
聲明
語法
擴充索引器可透過以下方式擴展文法(相對於 proposals/csharp-14.0/extensions.md)加入擴充宣告中允許的成員集合:
extension_member_declaration
: method_declaration
| property_declaration
| indexer_declaration // new
| operator_declaration
;
與一般索引器類似,擴展索引器沒有識別碼,並以參數列表識別。 擴充索引器可以使用現今一般索引器所支援的完整功能集(存取器實體、表達式實體成員、ref 回傳存取器、 scoped 參數、屬性等)。
由於索引器始終是實例成員,宣告索引器的擴充區塊必須提供命名的接收參數。
現有對擴充成員的限制仍然適用:擴充宣告中的索引器不能指定 abstract、 virtual、 override、 newpartialsealed( protected 或任何相關的可及性修飾符)或init存取器。
public static class BitExtensions
{
extension(int i)
{
public bool this[int index]
{
get => ...;
}
}
}
所有適用於普通索引器的 C# 標準規則都適用於擴充索引器,但擴充成員沒有隱含或顯 this式 。
現有的擴展成員可推論性規則仍然適用:對於每個非方法擴展成員,其擴展區塊的所有型別參數必須被用於該擴展與成員的組合參數集合中。
IndexerName 屬性
IndexerNameAttribute 可用於擴充索引器。 屬性不會在元資料中被輸出,但其值會影響成員間的衝突,決定屬性名稱及元資料中存取者的名稱,並在發出 [DefaultMemberAttribute] 時使用(參見 元資料)。
使用量
索引器存取
索引 器存取 的規則會更新:如果索引器存取的正常處理未找到適用的索引器,則嘗試將該結構作為擴充索引器存取來處理。
- 嘗試使用接收器類型宣告(或繼承)的實例索引器來綁定。 若找到適用候選者,超載解析會從這些實例成員中選擇今日狀態並停止。
- 嘗試使用在接收器類型上宣告(或繼承)的隱含實例索引器來綁定。 若找到適用候選者,超載解析會從這些實例成員中選擇今日狀態並停止。
- 嘗試綁定為擴充索引器存取權限。 若此過程找到適用候選,超載解析會從上述延伸成員中選擇,並停止。
- 嘗試以擴充功能綁定隱含索引器存取。 若此過程找到適用候選,超載解析會從上述延伸成員中選擇,並停止。
注意:元素存取區段處理參數型別 dynamic為 的情況,因此該參數永遠不會被當作索引器存取處理。
擴充索引器的存取
當接收端是 base_access 表達式時,擴展成員(包括擴展索引器)絕不會被考慮。
注意:我們只會在接收端為變數或值時,將 element_access 作為索引器存取處理,因此當接收端為型別時,擴展索引器不會被考慮。
給定一個 element_accessE[A],目標是識別一個擴展索引器。
若擴充索引E器適用於接收者與參數列表A,若其擴展簽名由擴展區塊的型別參數與參數列表結合,且該參數列表結合接收E者與參數列表 。A
我們重複使用擴充方法的 scope-walk:我們遍歷用於調用擴充方法的相同範圍,包括目前及包覆的詞彙範圍與 using 命名空間或 using static 匯入。
逐一檢視每個範圍:
- 在目前範圍內,非通用靜態類別宣告中的擴展區塊會被考慮。
- 這些擴展區塊中的索引器組成候選集。
- 無法取得的候選人將從集合中移除。
- 不適用(如上定義)的候選人會從名單中移除。
- 如果結果的候選索引器集合為空,則繼續前往下一個範圍,或者若到達最後一個範圍,則無法解析擴展索引器的存取(此時我們會繼續嘗試解析為擴展隱含索引器)。
- 否則,將對候選集合套用過載解析。 若無法辨識單一最佳索引器,則擴充索引器的存取方式不明確,並會發生編譯時錯誤。
利用前一步識別的這個最佳索引器,索引器的存取會被處理為靜態方法調用。
根據使用情境不同,索引器的存取會喚用索引器的 get_accessor 或 set_accessor 。
若索引器存取是指派的目標,則會呼叫 set_accessor 靜態實作方法來指派新值。
在其他情況下,會呼叫 get_accessor 靜態實作方法來取得當前值。
無論哪種方式,調用都會使用適用性檢查時推斷出的通用參數,並將接收者作為第一個參數。
擴充隱含索引器存取
若以下情況適用隱含 System.Index (或 System.Range)索引器存取擴充:
-
element_access 有一個類型
System.Index為 (或System.Range)的單一參數,且 - 在接收器類型上找到適用
Length的 ORCount(實例或擴展)屬性,且 - 接收器類型上會找到適用
this[int]的(或)(實Slice(int, int)例或擴展)索引器。
其他元素存取形式
任何會延後元素存取綁定(空條件元素存取或指派、物件初始化器的索引指派,或列表與擴散模式)的結構,都會自動參與上述擴充索引器解析。
表達式樹
擴充索引器無法被捕捉到表達式樹。
XML 文件
CREF 語法允許參考擴充索引器及其存取者,以及其實作方法。
範例:
/// <see cref="E.extension(int).this[string]"/>
/// <see cref="E.extension(int).get_Item(string)"/>
/// <see cref="E.extension(int).get_Item"/>
/// <see cref="E.extension(int).set_Item(string, int)"/>
/// <see cref="E.extension(int).set_Item"/>
/// <see cref="E.get_Item(int, string)"/>
/// <see cref="E.get_Item"/>
/// <see cref="E.set_Item(int, string, int)"/>
/// <see cref="E.set_Item"/>
public static class E
{
extension(int i)
{
/// <summary></summary>
public int this[string s]
{
get => throw null;
set => throw null;
}
}
}
Metadata
延伸指標器遵循與延伸性質相同的降低模型。 對於每個包含至少一個索引器的 CLR 層級擴充群組類型,編譯器會發出:
- 一個名為
Item(或由IndexerNameAttribute提供)的擴充屬性,帶有 的 accessor bodythrow NotImplementedException(),以及[ExtensionMarkerName]一個參考適當擴充標記類型的屬性。 - 在封閉的靜態類別中命名
get_Item/set_Item的實作方法。 這些方法會在參數列表前加上接收參數,並包含使用者定義的實體。 它們與static擴充屬性的實作方法一樣,參與過載解決。
為了鏡像一般索引器的行為,編譯器也會在包含一個或多個擴充索引器的擴充分組類型上發出 [DefaultMemberAttribute] 。 屬性的 等 MemberName 於索引器的元資料名稱(Item 預設為 ,或為 中的 IndexerNameAttribute值)。
Example
原始程式碼:
static class BitExtensions
{
extension<T>(T t)
{
public bool this[int index]
{
get => ...;
set => ...;
}
}
}
已發射的元資料(簡化為類似 C# 的語法):
[Extension]
static class BitExtensions
{
[Extension, SpecialName, DefaultMember("Item")]
public sealed class <G>$T0 // grouping type
{
[SpecialName]
public static class <M>$T_t // marker type
{
[SpecialName]
public static void <Extension>$(T t) { } // marker method
}
[ExtensionMarkerName("<M>$T_t")]
public bool this[int index] // extension indexer
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
}
// accessor implementation methods
public static bool get_Item<T>(T t, int index) => ...;
public static void set_Item<T>(T t, int index, bool value) => ...;
}
未結問題
文件中關於未解決議題的臨時部分,包括對替代設計的討論
處理 params
params如果你有一個擴展索引器,例如 paramsint this[int i, params string[] s] { get; set; },你可以有三種使用方式:
- 延伸索引:
receiver[i: 0, "Alice", "Bob"] - Getter 實作調用:
E.get_Item(receiver, i: 0, "Alice", "Bob") - 設定器實施調用:
E.set_Item(...)
那麼,二傳手實施方法的特徵是什麼?
只有在使用者可調用方法簽名的最後一個參數有 params時才有意義,因此在 E.set_Item(... extension parameter ..., this i, params string[] s, int value)中沒有意義。
以下是一些選項:
- 禁止
params帶有設定器的擴充索引器 - 在設定器實作方法中省略該
[ParamArray]屬性 - 不要做什麼特別的事(發出
[ParamArray])
我會建議選項二,因為它能 params 最大化實用性。 代價只是擴充索引與消歧義語法之間的小差異。
決定(LDM 2026-02-02):發射並 [ParamArray] 驗證對工具無負面影響
賦值對型別推論的影響
int i = 0;
i[42, null] = new object(); // fails inference
E.set_Item(i, 42, null, new object()); // infer `E.set_Item<object>`
public static class E
{
extension<T>(int i)
{
public T this[int j, T t] { set { } }
}
}
#nullable enable
int i = 0;
i[new object()] = null; // infer `E.extension<object!>` and warn on conversion of null literal to `object!`
E.set_Item(i, new object(), null); // infer `E.set_Item<object?>`
public static class E
{
extension<T>(int i)
{
public T this[T t] { set { } }
}
}
決策(LDM 2026-02-02):索引器僅根據接收者及參數列表中的參數推斷(即指派值不具貢獻)。
擴展 Length/Count 性質是否應該讓型別可數?
Length/Count 性質是否應該讓型別可數?提醒一下,綁定 隱含的索引器或範圍索引器時,擴充功能不會被用到:
C c = new C();
_ = c[..]; // Cannot apply indexing with [] to an expression of type 'C'
class C
{
public int Length => 0;
}
static class E
{
public static C Slice(this C c, int i, int j) => null!;
}
因此,我們目前的立場是,擴充屬性不應該被視為清單模式、集合表達式和隱含索引器的「可數屬性」。
如果我們在元素存取情境中暴露 this[Index] 或 this[Range] 擴充索引器,自然會預期目標型別能在清單模式中運作。
然而,列表模式需要 a Length 或 Count 性質。
延伸性質是否應該符合這個要求? (這看起來很自然)
C c = new C();
var x1 = c[^1];
var x2 = c[1..];
if (c is [.., var y1]) { }
if (c is [_, .. var y2]) { }
class C { }
static class E
{
extension(C c)
{
object this[System.Index] => ...;
C this[System.Range] => ...;
int Length => ...;
}
}
但那麼,這些屬性是否也應該對隱含索引器備援(LengthSlice + Count/)有所貢獻,而該備援()則用於缺少明確Index/Range索引器時使用?
C c = new C();
if (c is [var y1, .. var y2]) { }
class C
{
C Slice(int i, int j) => ...;
}
static class E
{
extension(C c)
{
object this[System.Index] => ...;
int Length => ...;
}
}
決定(LDM 2026-02-02):擴充功能應在所有地方都有貢獻,包括可數屬性和隱含索引器備援。
確認擴充索引器的存取權是在隱式索引器之前還是之後
C c = new C();
_ = c[^1];
class C
{
public int Length => ...;
public int this[int i] => ...;
}
static class E
{
extension(C c)
{
public int this[System.Index i] => ...;
}
}
我已經將擴充索引器的存取設定為優先於隱式索引器的存取,但現在認為它們應該放在之後,以避免不必要的相容中斷。
更新(LDM 2026-02-02):這需要進一步調查。 是的,延長應該在非擴展成員之後,但除此之外,我們需要根據上述決定提出具體提案,允許延長對隱含索引器備用機制的貢獻。
判決(LDM 2026-03-09):順序為實例索引器、隱式實例索引器、實擴展索引器,最後是隱式擴展索引器。 請參閱 https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers
數量/長度:名字是優先考,還是非延長還是延長?
我們也有現有的備案: Length 優先於 Count 財產。
延長 Length 應該在非延伸 Count 物業之前還是之後?
決策(LDM 2026-03-09):我們將逐項範圍檢視(從實例範圍開始,逐步到擴展範圍),在每個範圍內我們會先尋找, Length 然後 Count再尋找。
請參閱 https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers
確認隱含索引器的設計建議
我們逐個範圍檢視,從實例範圍開始,然後再到擴展範圍。
在一個範圍內:
- 我們尋找真正的索引者,
- 否則,如果我們有一個正確類型的單一參數,則尋找隱含索引器。
決定(LDM 2026-03-09):不,一旦我們進入擴展範圍,我們將使用全程延伸解析度。
確認擬議的列表模式設計
對於帶有牌差的列表型態,我們將尋找:
- a
Length/Count - 真實或隱含
this[Index] - 真實或隱含
this[Range]
我們會分別尋找。
但當我們尋找隱含 this[Index] 或 this[Range]時,這兩個部分必須來自相同的範圍:
Length/Countthis[int]/Slice(int, int)
注意:當型別可用於列表模式(即可數且可索引)時,模式的非負處理 Length 才會生效。
決定(LDM 2026-03-09):不,我們會依照這個查詢順序進行:
- 列表模式的解析方式就像我們分別尋找長度/數量、索引器和範圍索引器
- 對於指數與範圍索引器,流程如下:a。 僅用實例查找時,盡可能找出「真實」索引 b. 僅使用實例查找時,盡可能找出隱含索引器的各部分 c。 在完整查找(instance+extension)時,盡可能找出「實數」索引 d。 在完整查找(instance+extension)時,盡可能找出隱含索引器的各個部分(每個部分在個別查找中)
延伸 Slice 方法也應該有影響嗎?
Slice 方法也應該有影響嗎?_ = c[1..^1];
static class E
{
extension(C c)
{
public int Length => 3;
}
public static C Slice(this C c, int i, int j) => ...;
}
決定(LDM 2026-03-09):是的,我們對經典與新擴充方法的處理方式完全相同。
延伸 Length 應該有助於擴散優化嗎?
Length 應該有助於擴散優化嗎?C c = new C();
int[] i = [0, .. c]; // Uses Length, if available, to allocate the right size
決策(LDM 2026-03-09):不,擴充功能不太可能以高效能的方式實作,因此對優化沒有幫助。
隱含索引器應該用於陣列型態或字串型別嗎?
我提出隱式索引器不會對這些情境有貢獻,因為只有在畸形的 corlib(缺少實例 Length, GetSubArray 或 GetSubstring)時才有可能。
決定(LDM/Mads 電子郵件 2026-04-07):字串或陣列上不使用擴充索引器。
擴充索引器應該用在陣列類型或字串類型上嗎?
目前陣 列存取 與 字串存取 的規範與基準規則,意味著擴充索引器無法在陣列或字串上運作。
然而,宣告此類延伸索引器的聲明是被允許的。
int[] i = ...;
_ = i[new C()];
public static class E
{
extension(int[] i)
{
public int this[C c] => throw null;
}
}
string s = "...;
_ = s[new C()];
public static class E
{
extension(int[] i)
{
public int this[C c] => throw null;
}
}
注意:指標 元素存取 沒有問題,因為擴充參數可能不是指標類型。
決定(LDM/Mads 電子郵件 2026-04-07):字串或陣列上不使用擴充索引器。