注意
本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。
功能規格與已完成實作之間可能有一些差異。 這些差異是在的相關
總結
此提案提供語言建構,以公開目前無法在 C# 中有效率或完全無法有效存取的 IL opcode:ldftn 和 calli。 這些 IL opcode 在高效能程式代碼中很重要,開發人員需要有效率的方式來存取它們。
動機
這項功能的動機和背景,以及潛在的實作,會在下列議題中描述:
這是替代 編譯器內建功能 的設計建議
詳細設計
函式指標
語言會允許使用 delegate* 語法宣告函式指標。 下一節會詳細說明完整的語法,但意在類似於 Func 和 Action 類型宣告所使用的語法。
unsafe class Example
{
void M(Action<int> a, delegate*<int, void> f)
{
a(42);
f(42);
}
}
這些類型是使用ECMA-335中所述的函式指標類型來表示。 這表示 delegate* 的調用將會使用 calli,其中叫用 delegate 會在 callvirt 方法上使用 Invoke。
語法上,這兩個建構的調用是相同的。
方法指標的ECMA-335定義包含呼叫慣例,做為類型簽章的一部分(第7.1節)。
預設的呼叫約定將是 managed。 您可以透過在 unmanaged 語法之後放置 delegate* 關鍵詞來指定 Unmanaged 呼叫慣例,這會使用運行平台的預設值。 接著,您可以在括弧中為 unmanaged 關鍵詞指定特定的 unmanaged 慣例,方法是在 CallConv 命名空間中指定以 System.Runtime.CompilerServices 開頭的任何類型,而不包括 CallConv 前置詞。 這些類型必須來自程式的核心程式庫,而有效的組合取決於平臺。
//This method has a managed calling convention. This is the same as leaving the managed keyword off.
delegate* managed<int, int>;
// This method will be invoked using whatever the default unmanaged calling convention on the runtime
// platform is. This is platform and architecture dependent and is determined by the CLR at runtime.
delegate* unmanaged<int, int>;
// This method will be invoked using the cdecl calling convention
// Cdecl maps to System.Runtime.CompilerServices.CallConvCdecl
delegate* unmanaged[Cdecl] <int, int>;
// This method will be invoked using the stdcall calling convention, and suppresses GC transition
// Stdcall maps to System.Runtime.CompilerServices.CallConvStdcall
// SuppressGCTransition maps to System.Runtime.CompilerServices.CallConvSuppressGCTransition
delegate* unmanaged[Stdcall, SuppressGCTransition] <int, int>;
delegate* 類型之間的轉換是根據其標識完成的,包括呼叫約定。
unsafe class Example {
void Conversions() {
delegate*<int, int, int> p1 = ...;
delegate* managed<int, int, int> p2 = ...;
delegate* unmanaged<int, int, int> p3 = ...;
p1 = p2; // okay p1 and p2 have compatible signatures
Console.WriteLine(p2 == p1); // True
p2 = p3; // error: calling conventions are incompatible
}
}
delegate* 類型是指針類型,表示它具有標準指標類型的所有功能和限制:
- 只有在
unsafe內容中才有效。 - 包含
delegate*參數或傳回型別的方法只能從unsafe環境呼叫。 - 無法轉換成
object。 - 不能當做泛型自變數使用。
- 可以隱含地將
delegate*轉換成void*。 - 可以從
void*明確轉換成delegate*。
限制:
- 自訂屬性無法套用至
delegate*或其任何元素。 - 無法將
delegate*參數標示為params -
delegate*類型具有一般指標類型的所有限制。 - 指標算術無法直接在函式指標類型上執行。
函式指標語法
完整的函式指標語法是以下列文法表示:
pointer_type
: ...
| funcptr_type
;
funcptr_type
: 'delegate' '*' calling_convention_specifier? '<' funcptr_parameter_list funcptr_return_type '>'
;
calling_convention_specifier
: 'managed'
| 'unmanaged' ('[' unmanaged_calling_convention ']')?
;
unmanaged_calling_convention
: 'Cdecl'
| 'Stdcall'
| 'Thiscall'
| 'Fastcall'
| identifier (',' identifier)*
;
funptr_parameter_list
: (funcptr_parameter ',')*
;
funcptr_parameter
: funcptr_parameter_modifier? type
;
funcptr_return_type
: funcptr_return_modifier? return_type
;
funcptr_parameter_modifier
: 'ref'
| 'out'
| 'in'
;
funcptr_return_modifier
: 'ref'
| 'ref readonly'
;
如果未提供任何 calling_convention_specifier,則預設值為 managed。 涵蓋
delegate int Func1(string s);
delegate Func1 Func2(Func1 f);
// Function pointer equivalent without calling convention
delegate*<string, int>;
delegate*<delegate*<string, int>, delegate*<string, int>>;
// Function pointer equivalent with calling convention
delegate* managed<string, int>;
delegate*<delegate* managed<string, int>, delegate*<string, int>>;
函數指標轉換
在不安全的內容中,會擴充一組可用的隱含轉換(隱含轉換),以包含下列隱含指標轉換:
- 現有的轉換 - (§23.5)
- 從 funcptr_type
F0到另一個 funcptr_typeF1,前提是下列所有內容都成立:-
F0和F1擁有相同數目的參數,且D0n中的每個參數F0具有與ref中對應的參數out相同的in、D1n或F1的修飾詞。 - 對於每個值參數(即沒有
ref、out或in修飾詞的參數),從F0中的參數類型到F1中的相應參數類型,都存在一種識別轉換、隱式參考轉換或隱式指標轉換。 - 針對每個
ref、out或in參數,F0中的參數類型與F1中的對應參數類型相同。 - 如果傳回型別是值類型(沒有
ref或ref readonly),則從F1的傳回型別到F0的傳回型別存在同一性、隱式參考或隱式指標轉換。 - 如果返回類型是傳址類型(
ref或ref readonly),則ref的返回類型和F1修飾詞與ref的返回類型和F0修飾詞相同。 -
F0的呼叫慣例與F1的呼叫慣例相同。
-
允許目標方法的位址
方法群組現在可做為表達式位址的自變數。 這類表達式的類型將會是具有與目標方法相等的簽章及受控呼叫慣例的 delegate*:
unsafe class Util {
public static void Log() { }
void Use() {
delegate*<void> ptr1 = &Util.Log;
// Error: type "delegate*<void>" not compatible with "delegate*<int>";
delegate*<int> ptr2 = &Util.Log;
}
}
在不安全的上下文中,如果下列所有條件皆為真,則方法 M 與函式指標類型 F 兼容:
-
M和F參數數目相同,M中的每個參數與ref中對應的參數具有相同的out、in或F修飾詞。 - 對於每個值參數(即沒有
ref、out或in修飾詞的參數),從M中的參數類型到F中的相應參數類型,都存在一種識別轉換、隱式參考轉換或隱式指標轉換。 - 針對每個
ref、out或in參數,M中的參數類型與F中的對應參數類型相同。 - 如果傳回型別是值類型(沒有
ref或ref readonly),則從F的傳回型別到M的傳回型別存在同一性、隱式參考或隱式指標轉換。 - 如果返回類型是傳址類型(
ref或ref readonly),則ref的返回類型和F修飾詞與ref的返回類型和M修飾詞相同。 -
M的呼叫慣例與F的呼叫慣例相同。 這包括呼叫慣例位,以及 Unmanaged 識別符中指定的任何呼叫慣例旗標。 -
M是靜態方法。
在不安全的內容中,如果存在隱含轉換,該轉換會從地址運算式(其目標是方法群組 E)轉換為相容的函式指標類型 F,前提是 E 至少包含一個方法,該方法能以通常形式應用於利用 F的參數類型和修飾詞所構建的參數清單,如下所述。
- 選取單一方法
M與形式E(A)的方法調用相對應,並進行以下修改:- 參數列表
A是一個表達式列表,每個表達式都被分類為變數,並且具有對應於ref的out的型別和修飾詞(in、或F)。 - 候選方法只是其一般格式適用的方法,不適用於其展開格式的方法。
- 候選的方法僅限於靜態的方法。
- 參數列表
- 如果多載解析的演算法產生錯誤,則會發生編譯時期錯誤。 否則,演算法會產生單一最佳方法,
M參數數目與F相同,而且轉換會視為存在。 - 選取的方法
M必須與函式指標類型F相容(如上所述)。 否則,會發生編譯時期錯誤。 - 轉換的結果是 類型為
F的函式指標。
這表示開發人員可以相依於多載解析規則,以便與位址運算元搭配運作:
unsafe class Util {
public static void Log() { }
public static void Log(string p1) { }
public static void Log(int i) { }
void Use() {
delegate*<void> a1 = &Log; // Log()
delegate*<int, void> a2 = &Log; // Log(int i)
// Error: ambiguous conversion from method group Log to "void*"
void* v = &Log;
}
}
使用 ldftn 指令實作取址運算符。
這項功能的限制:
- 僅適用於標示為
static的方法。 - 非
static本機函式無法在&中使用。 這些方法的實作細節語言刻意不加以指定。 這包括它們是靜態方法還是實例方法,以及它們發出時使用的簽章的具體特徵。
函式指標類型的運算符
不安全代碼中有關運算式的部分進行如下修改:
在不安全的上下文中,有數個構造可用於操作所有屬於_pointer_type_s但不是_funcptr_type_s的類型:
*運算子可用於執行指標間接存取(§23.6.2)。->運算子可用來透過指標存取結構的成員($\23.6.3)。[]運算符可以用於指標索引(§23.6.4)。&運算子可用來取得變數的位址('。23.6.5)。++和--運算子可用來遞增和遞減指標(\23.6.6)。+和-運算子可用來執行指標算術(\23.6.7)。==、!=、<、>、<=和=>運算符可用來比較指標()。stackalloc運算子可用來從呼叫堆疊分配記憶體(第23.8節)。fixed陳述句可以用來暫時固定變數,以便取得其位址(§23.7)。在不安全的內容中,有數個建構可用於在所有_funcptr_type_s上運作:
&運算子可用來取得靜態方法的位址(允許尋址目標方法)==、!=、<、>、<=和=>運算符可用來比較指標()。
此外,我們會將 Pointers in expressions 中的所有區段修改為禁止函式指標類型,但 Pointer comparison 和 The sizeof operator除外。
較佳的功能成員
§12.6.4.3 Better function member 將會變更為包含下列這一行:
delegate*比void*更具體
這表示我們可以在 void* 和 delegate* 上進行多載,並且仍然可以合理地使用取址運算子。
類型推斷
在不安全的程式代碼中,會對類型推斷演算法進行下列變更:
輸入類型
已新增以下內容:
如果
E是方法群組的位址,而且T是函式指標類型,則T的所有參數類型都是具有類型E之T的輸入類型。
輸出類型
已新增以下內容:
如果
E是方法群組的位址,且T是函式指標類型,則T的傳回型別是具有類型E之T的輸出類型。
輸出類型推斷
下列項目符號將新增在項目符號 2 和 3 之間:
- 如果
E是方法群組的地址,並且T是具有參數型別T1...Tk和傳回型別Tb的函式指標型別,且對具有型別E的多載解析T1..Tk會產生傳回型別U的單一方法,則會從 到U進行Tb。
從表達式進行更好的轉換
以下子項目將作為例子新增到項目符號 2 中:
V是函式指標類型delegate*<V2..Vk, V1>,U是函式指標類型delegate*<U2..Uk, U1>,而V的呼叫慣例與U相同,而Vi的 refness 與Ui相同。
下限推斷
下列案例將新增到第三點:
V是函式指標類型delegate*<V2..Vk, V1>,而且有函式指標類型delegate*<U2..Uk, U1>,因此U與delegate*<U2..Uk, U1>相同,而V的呼叫慣例與U相同,且Vi的 refness 與Ui相同。
從 Ui 推斷到 Vi 的第一個要點將被修改為:
- 如果
U不是函式指標類型,而且Ui不是參考型別,或者如果U是函式指標類型,而且Ui未知為函式指標類型或參考型別,則會建立 確切的推斷
然後,在從 Ui 推斷的第 3 個項目符號後面新增至 Vi:
- 否則,如果
V為delegate*<V2..Vk, V1>則推斷取決於delegate*<V2..Vk, V1>的第 i 個參數:
- 如果為 V1:
- 如果是按值傳回,則會建立 下限推斷。
- 如果傳回是以傳址方式傳回,則會建立 確切的推斷。
- 如果為 V2..Vk:
- 如果參數是依據值,則會建立 上限推斷。
- 如果參數是參考參數,則會建立 的確切推斷。
上限推斷
下列案例會新增至第二點:
U是函式指標類型delegate*<U2..Uk, U1>,V是與delegate*<V2..Vk, V1>相同的函式指標類型,而U的呼叫慣例與V相同,而Ui的 refness 與Vi相同。
從 Ui 推斷到 Vi 的第一個要點將被修改為:
- 如果
U不是函式指標類型,而且Ui不是參考型別,或者如果U是函式指標類型,而且Ui未知為函式指標類型或參考型別,則會建立 確切的推斷
然後在從 Ui 推斷內容的第 3 個項目符號後面新增至 Vi:
- 否則,如果
U為delegate*<U2..Uk, U1>則推斷取決於delegate*<U2..Uk, U1>的第 i 個參數:
- 如果為 U1:
- 如果傳回是依據值,則會
上限推斷。 - 如果傳回是以傳址方式傳回,則會建立 確切的推斷。
- 如果U2..Uk:
- 如果 參數是依據值,則會建立 下限推斷。
- 如果參數是參考參數,則會建立 的確切推斷。
in、out和 ref readonly 參數和傳回型別的元數據表示法
函式指標簽章沒有參數旗標位置,因此我們必須使用modreqs將參數和傳回型別 in、out或 ref readonly 編碼。
in
我們會重複使用 System.Runtime.InteropServices.InAttribute,以 modreq 套用至參數或傳回型別上的 ref 規範,以表示下列各項:
- 如果套用至參數 ref 規範,這個參數會被視為
in。 - 如果套用至傳回型別 ref 規範,則會將傳回型別視為
ref readonly。
out
我們使用 System.Runtime.InteropServices.OutAttribute,將其作為 modreq 套用到參數類型的 ref 規範上,以表示該參數是 out 參數。
錯誤
- 將
OutAttribute當作 modreq 套用至返回類型是錯誤的。 - 將
InAttribute和OutAttribute同時應用為參數類型的修飾要求是一個錯誤。 - 如果任一通過 modopt 指定,則會忽略它們。
呼叫慣例的元數據表示法
呼叫慣例會透過簽章開頭的 CallKind 旗標和簽章開頭零或多個 modopt的組合,在元數據中以方法簽章編碼。 ECMA-335 目前在 CallKind 旗標中宣告下列元素:
CallKind
: default
| unmanaged cdecl
| unmanaged fastcall
| unmanaged thiscall
| unmanaged stdcall
| varargs
;
其中,C# 中的函式指標將支援除 varargs之外的所有功能。
此外,運行時間(最終為 335)將會更新,以在新平臺上包含新的 CallKind。 這目前沒有正式名稱,但本檔將使用 unmanaged ext 做為佔位符來代表新的可延伸呼叫慣例格式。 沒有 modopt,unmanaged ext 是平台默認呼叫慣例,unmanaged 不含方括弧。
將 calling_convention_specifier 對應至 CallKind
省略或指定為 calling_convention_specifier的 managed,會對應至 defaultCallKind。 這是預設 CallKind 的任何未被歸屬為 UnmanagedCallersOnly的方法。
C# 會辨識 4 個特殊標識符,這些標識符對應至 ECMA 335 中特定現有的 unmanaged CallKind。 為了讓此對應發生,這些標識碼必須獨立指定,不包含其他標識碼,而且此需求會編碼為 unmanaged_calling_convention的規格說明。 這些標識碼 Cdecl、Thiscall、Stdcall和 Fastcall分別對應至 unmanaged cdecl、unmanaged thiscall、unmanaged stdcall和 unmanaged fastcall。 如果指定了多個 identifer,或單一 identifier 不是特殊辨識的標識碼,我們會使用下列規則對標識符執行特殊名稱查閱:
- 我們在字串
identifier前面加上字串CallConv - 我們只會查看
System.Runtime.CompilerServices命名空間中定義的類型。 - 我們只檢視應用程式核心庫中定義的類型,此核心庫定義了
System.Object並且沒有依賴。 - 我們只查看公共類型。
如果在 identifier中指定的所有 unmanaged_calling_convention都查閱成功,我們會將 CallKind 編碼為 unmanaged ext,並在函式指標簽章的開頭,將已解析的每個類型編碼到 modopt的集合中。 請注意,這些規則意味著使用者不能在這些 identifier前加上 CallConv,因為這樣會導致查詢 CallConvCallConvVectorCall。
在解譯元數據時,我們會先查看 CallKind。 如果不是 unmanaged ext以外的任何項目,我們會忽略傳回型別上的所有 modopt,以便判斷呼叫慣例,並且只使用 CallKind。 如果 CallKind 是 unmanaged ext,我們會查看函式指標類型開頭的修飾選項,並採用符合下列需求的所有類型的聯集:
- 定義於核心函式庫中,該函式庫不參考其他函式庫,並且定義了
System.Object。 - 此類型定義於
System.Runtime.CompilerServices命名空間中。 - 此類型以字首
CallConv開頭。 - 此類型為公開。
這些代表在來源中定義函式指標類型時,在 identifier 中執行對 unmanaged_calling_convention的查閱時必須找到的類型。
如果目標執行時期不支援此功能,嘗試將函式指標與 CallKindunmanaged ext 一起使用,就會發生錯誤。 這將藉由確認是否存在 System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind 常數來決定。 如果存在此常數,則會將運行時間視為支援此功能。
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute 是 CLR 用來指出應該使用特定呼叫慣例來執行函式的屬性。 因此,我們引進了以下針對屬性操作的支援:
- 直接從 C# 呼叫以這個屬性標註的方法是錯誤的。 用戶必須取得 方法的函式指標,然後叫用該指標。
- 將屬性套用至一般靜態方法或一般靜態本機函式以外的任何專案是錯誤的。 C# 編譯程式會將任何從元數據匯入的非靜態或靜態非一般方法標示為語言不支援此屬性。
- 將方法標示為屬性的情況下,如果其參數或傳回型別不是
unmanaged_type,這是錯誤的。 - 屬性標示的方法若具有型別參數,即便這些型別參數受到
unmanaged的限制,這樣做還是錯誤的。 - 在泛型類型中,將方法標示為屬性是錯誤的。
- 將標記為屬性的方法轉換成委派類型是錯誤的。
- 在元數據中,若為呼叫慣例
UnmanagedCallersOnly.CallConvs指定任何不符合需求的modopt類型,則為錯誤。
判斷以有效 UnmanagedCallersOnly 屬性標示的方法呼叫慣例時,編譯程式會對 CallConvs 屬性中指定的類型執行下列檢查,以判斷應該用來判斷呼叫慣例的有效 CallKind 和 modopt:
- 如果未指定任何類型,則
CallKind會視為unmanaged ext,且函式指標類型開頭沒有呼叫慣例modopt。 - 如果指定了一個型別,而該型別命名為
CallConvCdecl、CallConvThiscall、CallConvStdcall或CallConvFastcall,則CallKind分別視為unmanaged cdecl、unmanaged thiscall、unmanaged stdcall或unmanaged fastcall,且在函式指標類型開頭沒有任何呼叫慣例modopt。 - 如果指定了多個型別,或單一型別未命名為上述其中一個特別呼叫的類型,則
CallKind會視為unmanaged ext,且指定的型別聯集在函式指標類型開頭視為modopt。
然後,編譯程式會查看這個有效的 CallKind 和 modopt 集合,並使用一般元數據規則來判斷函式指標類型的最終呼叫慣例。
未解決的問題
偵測 unmanaged ext 的執行階段支援
https://github.com/dotnet/runtime/issues/38135 追蹤新增此旗標。 根據審查的意見反應,我們將使用問題中指定的屬性,或利用 UnmanagedCallersOnlyAttribute 的存在作為標誌,以判斷運行時間是否支援 unmanaged ext。
考慮事項
允許實例方法
此提案可以藉由採用 EXPLICITTHIS CLI 呼叫慣例(在 C# 代碼中稱為 instance),來擴充以支持實例方法。 這種形式的 CLI 函式指標會將 this 參數作為函式指標語法的明確第一個參數。
unsafe class Instance {
void Use() {
delegate* instance<Instance, string> f = &ToString;
f(this);
}
}
這是健全的,但為提案帶來了一些複雜性。 特別是因為呼叫約定 instance 和 managed 不同的函式指標會不相容,儘管這兩個案例都是用來呼叫具有相同 C# 簽章的受控方法。 此外,在每個考慮到這會有價值的情況下,都有一個簡單的替代方案:使用 static 的本機函式。
unsafe class Instance {
void Use() {
static string toString(Instance i) => i.ToString();
delegate*<Instance, string> f = &toString;
f(this);
}
}
宣告時不需要使用不安全選項
不需要在每次使用 unsafe時要求 delegate*,只需要在方法群組轉換成 delegate*的時候才要求它。 這是核心安全問題開始發生的地方,因為了解在值仍有效時無法卸除包含的元件。 在其他位置上要求 unsafe 可以視為過多。
這就是設計最初的意圖。 但由此產生的語言規則顯得很別扭。 不可能隱藏這個事實,即這是指標值,即使沒有 unsafe 關鍵詞,它仍會持續查看。 例如,無法允許轉換成 object,它不能是 class的成員,等等。C# 的設計要求所有指標使用 unsafe,因此此設計遵循這一要求。
開發人員仍然能夠以與目前一般指標類型相同的方式,在 值之上呈現 delegate* 包裝函式。 考慮:
unsafe struct Action {
delegate*<void> _ptr;
Action(delegate*<void> ptr) => _ptr = ptr;
public void Invoke() => _ptr();
}
使用委派
不要使用新的語法元素 delegate*,只需使用現有的 delegate 類型,然後在類型後面加上 *。
Func<object, object, bool>* ptr = &object.ReferenceEquals;
使用指定 delegate 值的屬性來標註 CallingConvention 型別,即可處理呼叫慣例。 屬性的缺失將表示受管理的呼叫約定。
在 IL 中編碼這個是有問題的。 基礎值必須以指標表示,但也必須:
- 具有唯一類型,以支持不同類型的函式指標的多載。
- 為了 OHI 的用途,應在組件邊界之間保持等價。
最後一點特別有問題。 這表示使用 Func<int>* 的每個元件都必須在元數據中編碼對等型別,即使 Func<int>* 是在元件中定義,但不會控制。
此外,在非 mscorlib 的元件中,以名稱 System.Func<T> 定義的任何其他類型,其定義必須與在 mscorlib 元件中定義的版本不同。
探索的其中一個選項是發出這類指標,例如 mod_req(Func<int>) void*。 這無法運作,因為 mod_req 無法繫結至 TypeSpec,因此無法以泛型具現化為目標。
具名函式指標
函式指標語法可能很麻煩,特別是在巢狀函式指標等複雜案例中。 開發人員不必每次都輸入簽章,因為可以像使用 delegate一樣,語言可以允許函式指標的具名宣告。
func* void Action();
unsafe class NamedExample {
void M(Action a) {
a();
}
}
此處問題的一部分是基礎 CLI 基本類型沒有名稱,因此這純粹是 C# 發明,而且需要一些元數據工作才能啟用。 這是可行的,但需要大量工作。 它基本上需要 C# 為型別定義表提供一個專門為這些名稱而設的配套。
此外,當我們檢查了具名函數指標的參數時,我們發現這些參數同樣適用於許多其他情況。 例如,宣告具名 tuple 同樣可以方便地減少在各種情況下輸入完整簽名的需求。
(int x, int y) Point;
class NamedTupleExample {
void M(Point p) {
Console.WriteLine(p.x);
}
}
在討論之後,我們決定不允許具名宣告 delegate* 類型。 如果我們發現根據客戶使用反饋有顯著需求,我們會調查適用於函式指標、元組、泛型等的命名解決方案。這可能形式上類似於語言中完整 typedef 支援等其他建議。
未來考慮
靜態委派
這是指 提案 允許宣告只能參考 delegate 成員的 static 型別。 這類 delegate 實例的優點是可以不需配置,並在效能敏感的情境中表現更佳。
如果實作函式指標功能,static delegate 提案可能會關閉。該功能的建議優點是配置自由性質。 不過,最近的研究發現,由於組件卸載,無法實現該目標。 必須有從 static delegate 到所參考方法的強句柄,才能防止元件從其下卸除。
若要維護每個 static delegate 實例,必須分配新的處理器,這與提案目標背道而馳。 有一些設計可以將記憶體配置攤分為每個呼叫點的單一配置,但是這有點複雜,似乎不值得這樣取捨。
這表示開發人員基本上必須決定下列取捨:
- 面對元件卸除時的安全性:這需要配置,因此
delegate已經是足夠的選項。 - 組件卸除時不安全:請使用
delegate*。 這可以包裝在struct中,以允許在程式代碼其餘部分的unsafe內容之外使用。