Ukazatele funkcí
Poznámka
Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.
Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v příslušných poznámkách ze schůzky návrhu jazyka (LDM) .
Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .
Shrnutí
Tento návrh poskytuje jazykové konstrukce, které v současné době v jazyce C# zpřístupňují opcode IL, které momentálně nemohou být efektivně přístupné nebo vůbec: ldftn
a calli
. Tyto opkódy IL můžou být důležité v kódu s vysokým výkonem a vývojáři potřebují efektivní způsob, jak k nim získat přístup.
Motivace
Motivace a pozadí této funkce jsou popsány v následujícím problému (stejně jako potenciální implementace funkce):
Toto je alternativní návrh návrhu vnitřních objektů kompilátoru
Podrobný návrh
Ukazatele na funkce
Jazyk umožní deklaraci ukazatelů funkce pomocí syntaxe delegate*
. Úplná syntaxe je podrobně popsaná v další části, ale má se podobat syntaxi používané Func
a deklarací typu Action
.
unsafe class Example
{
void M(Action<int> a, delegate*<int, void> f)
{
a(42);
f(42);
}
}
Tyto typy jsou reprezentovány pomocí typu ukazatele funkce, jak je uvedeno v ECMA-335. To znamená vyvolání delegate*
použije calli
, kdy vyvolání delegate
použije callvirt
v metodě Invoke
.
Syntakticky je vyvolání pro obě konstrukce stejné.
Definice ukazatelů metody ECMA-335 zahrnuje konvenci volání jako součást podpisu typu (oddíl 7.1).
Výchozí konvence volání bude managed
. Nespravované konvence volání je možné zadat vložením klíčového slova unmanaged
po syntaxi delegate*
, což bude používat výchozí nastavení platformy modulu runtime. Konkrétní nespravované konvence je pak možné zadat v hranatých závorkách klíčového slova unmanaged
zadáním libovolného typu začínajícího CallConv
v oboru názvů System.Runtime.CompilerServices
a ponecháním předpony CallConv
. Tyto typy musí pocházet z základní knihovny programu a sada platných kombinací je závislá na platformě.
//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>;
Převody mezi typy delegate*
se provádějí na základě jejich signatury, včetně konvence volání.
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
}
}
Typ delegate*
je typ ukazatele, což znamená, že má všechny možnosti a omezení standardního typu ukazatele:
- Platné pouze v kontextu
unsafe
. - Metody, které obsahují parametr
delegate*
nebo návratový typ, lze volat pouze zunsafe
kontextu. - Nelze převést na
object
. - Nelze použít jako obecný argument.
- Lze implicitně převést
delegate*
navoid*
. - Lze explicitně převést z
void*
nadelegate*
.
Omezení:
- Vlastní atributy nelze použít na
delegate*
ani na žádný z jeho prvků. - Parametr
delegate*
nelze označit jakoparams
- Typ
delegate*
má všechna omezení normálního typu ukazatele. - Aritmetika ukazatele nelze provést přímo u typů ukazatelů funkce.
Syntaxe ukazatele funkce
Syntaxe ukazatele na celou funkci je reprezentována následující gramatikou:
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'
;
Pokud není k dispozici žádná calling_convention_specifier
, výchozí hodnota je managed
. Přesné kódování metadat calling_convention_specifier
a platnost identifier
v unmanaged_calling_convention
je popsáno v reprezentaci konvencí pro volání.
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>>;
Převody ukazatelů funkce
V nebezpečném kontextu je sada dostupných implicitních převodů (implicitní převody) rozšířena tak, aby zahrnovala následující implicitní převody ukazatelů:
- existující převody - (§23.5)
- Od funcptr_type
F0
k jinému funcptr_typeF1
, pokud jsou pravdivé všechny následující skutečnosti:-
F0
aF1
mají stejný počet parametrů a každý parametrD0n
vF0
má stejnýref
,out
nebo modifikátoryin
jako odpovídajícíD1n
parametru vF1
. - Pro každý parametr hodnoty (parametr bez
ref
,out
nebo modifikátoruin
), převod identity, implicitní převod odkazu nebo implicitní převod ukazatele existuje z typu parametru vF0
na odpovídající typ parametru vF1
. - Pro každý parametr
ref
,out
neboin
je typ parametru vF0
stejný jako odpovídající typ parametru vF1
. - Pokud je návratový typ podle hodnoty (bez
ref
neboref readonly
), existuje identita, implicitní odkaz nebo implicitní převod ukazatele z návratového typuF1
na návratový typF0
. - Pokud je návratový typ pomocí odkazu (
ref
neboref readonly
), pak návratový typ a modifikátoryref
uF1
jsou stejné jako návratový typ a modifikátoryref
uF0
. - Konvence volání
F0
je stejná jako konvence voláníF1
.
-
Povolit adresní metody cíli
Skupiny metod budou nyní povoleny jako argumenty pro adresu výrazu. Typ takového výrazu bude delegate*
s ekvivalentním podpisem cílové metody a spravovanou konvencí volání.
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;
}
}
V nezabezpečeném kontextu je metoda M
kompatibilní s typem ukazatele funkce F
, pokud jsou splněny všechny následující podmínky:
-
M
aF
mají stejný počet parametrů a každý parametr vM
má stejnýref
,out
nebo modifikátoryin
jako odpovídající parametr vF
. - Pro každý parametr hodnoty (parametr bez
ref
,out
nebo modifikátoruin
), převod identity, implicitní převod odkazu nebo implicitní převod ukazatele existuje z typu parametru vM
na odpovídající typ parametru vF
. - Pro každý parametr
ref
,out
neboin
je typ parametru vM
stejný jako odpovídající typ parametru vF
. - Pokud je návratový typ podle hodnoty (bez
ref
neboref readonly
), existuje identita, implicitní odkaz nebo implicitní převod ukazatele z návratového typuF
na návratový typM
. - Pokud je návratový typ odkazem (
ref
neboref readonly
), pak návratový typ a modifikátoryref
uF
jsou stejné jako návratový typ a modifikátoryref
uM
. - Konvence volání
M
je stejná jako konvence voláníF
. To zahrnuje bit konvence volání i všechny příznaky konvence volání zadané v nespravovaném identifikátoru. -
M
je statická metoda.
V nebezpečném kontextu existuje implicitní převod z výrazu adresa, jehož cílem je skupina metod E
, na kompatibilní typ ukazatele na funkci F
, pokud E
obsahuje alespoň jednu metodu, která je v normální formě použitelná pro seznam argumentů vytvořený pomocí typů a modifikátorů parametrů F
, jak je popsáno dále.
- Jedna metoda
M
je vybrána odpovídající metodě vyvolání formulářeE(A)
s následujícími úpravami:- Seznam argumentů
A
je seznam výrazů, z nichž každý je klasifikován jako proměnná a má typ a modifikátor (ref
,out
neboin
), které odpovídají seznamu parametru funkce funcptr_parameter_list v rámciF
. - Kandidátské metody jsou pouze metody, které jsou použitelné ve své normální podobě, nikoli v rozšířené podobě.
- Kandidátské metody jsou pouze metody, které jsou statické.
- Seznam argumentů
- Pokud algoritmus řešení přetížení vytvoří chybu, dojde k chybě v době kompilace. Jinak algoritmus vytvoří jednu nejlepší metodu
M
se stejným počtem parametrů jakoF
a převod se považuje za existující. - Vybraná metoda
M
musí být kompatibilní (jak je definováno výše) s typem ukazatele funkceF
. V opačném případě dojde k chybě kompilace. - Výsledkem převodu je ukazatel funkce typu
F
.
To znamená, že vývojáři můžou záviset na pravidlech překladu přetížení, která fungují ve spojení s operátorem address-of:
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;
}
}
Operátor adresy se implementuje pomocí ldftn
instrukce.
Omezení této funkce:
- Platí pouze pro metody označené jako
static
. - V
&
nelze použít jiné nežstatic
místní funkce. Podrobnosti implementace těchto metod nejsou záměrně specifikovány jazykem. To zahrnuje, jestli jsou statické nebo instance, a přesně s jakým podpisem jsou vyvolány.
Operátory pro typy funkčních ukazatelů
Oddíl o výrazech v nebezpečném kódu je upraven takto:
V nebezpečném kontextu je k dispozici několik konstruktů pro práci se všemi _pointer_type_s, které nejsou _funcptr_type_s:
- Operátor
*
lze použít k provádění nepřímých ukazatelů (§23.6.2).- Operátor
->
lze použít pro přístup ke členu struktury ukazatelem (§23.6.3).- Operátor
[]
lze použít k indexování ukazatele (§23.6.4).- Operátor
&
lze použít k získání adresy proměnné (§23.6.5).- Operátory
++
a--
lze použít k inkrementování a dekrementování ukazatelů (§23.6.6).- Operátory
+
a-
lze použít k provádění aritmetik ukazatele (§23.6.7).- Operátory
==
,!=
,<
,>
,<=
a=>
lze použít k porovnání ukazatelů (§23.6.8).- Operátor
stackalloc
lze použít k přidělení paměti ze zásobníku volání (§23.8).- Příkaz
fixed
lze použít k dočasné opravě proměnné, aby bylo možné získat její adresu (§23.7).V nebezpečném kontextu je k dispozici několik konstrukcí pro práci se všemi _funcptr_type_s:
- Operátor
&
lze použít k získání adresy statických metod (Povolit adresu cílové metody)- Operátory
==
,!=
,<
,>
,<=
a=>
lze použít k porovnání ukazatelů (§23.6.8).
Kromě toho upravíme všechny oddíly v Pointers in expressions
tak, aby se zakázaly typy ukazatelů funkce s výjimkou Pointer comparison
a The sizeof operator
.
Lepší funkční člen
§ 12.6.4.3 Lepší člen funkce bude změněn tak, aby zahrnoval následující řádek:
delegate*
je konkrétnější nežvoid*
To znamená, že je možné přetížit void*
a delegate*
a stále rozumně používat adresu operátoru.
Odvození typu
V nebezpečném kódu jsou v algoritmech odvození typu provedeny následující změny:
Vstupní typy
Přidá se následující:
Pokud
E
je skupina metod adres aT
je typ ukazatele funkce, pak všechny typy parametrůT
jsou vstupními typyE
s typemT
.
Výstupní typy
Přidá se následující:
Pokud
E
je skupina metod s adresou aT
je typ ukazatele na funkci, pak návratový typT
je výstupní datový typE
s typemT
.
Odvození výstupního typu
Mezi odrážky 2 a 3 se přidá následující odrážka:
- Pokud
E
je skupina metod adresy aT
je typ ukazatele funkce s typy parametrůT1...Tk
a návratovým typemTb
a přetížit rozlišeníE
s typyT1..Tk
vrací jedinou metodu s návratovým typemU
, pak dolní mez odvozování je vyroben zU
doTb
.
Lepší převod z výrazu
Následující dílčí odrážka se přidá v rámci odrážky 2:
V
je typ ukazatele funkcedelegate*<V2..Vk, V1>
,U
je typ ukazatele funkcedelegate*<U2..Uk, U1>
a volací konvenceV
je shodná sU
a refnessVi
je shodná sUi
.
Odvození dolní meze
Do odrážky 3 se přidá následující případ:
V
je typ ukazatele na funkcidelegate*<V2..Vk, V1>
a existuje typ ukazatele na funkcidelegate*<U2..Uk, U1>
, tak, žeU
je totožný sdelegate*<U2..Uk, U1>
, a volací konvenceV
je totožná sU
, a refnessVi
je totožná sUi
.
První odrážka odvozování z Ui
do Vi
je upravena takto:
- Pokud
U
není typ ukazatele funkce aUi
není známo, že se jedná o typ odkazu, nebo pokudU
je typ ukazatele funkce aUi
není známo, že se jedná o typ ukazatele funkce nebo typ odkazu, provede se přesná odvození
Potom se přidá za třetí odrážku odvozování z Ui
do Vi
:
- Pokud je
V
delegate*<V2..Vk, V1>
pak odvození závisí na i-tém parametrudelegate*<V2..Vk, V1>
:
- Pokud V1:
- Pokud je návrat podle hodnoty, provede se odvození v dolní hranici .
- Pokud je návrat odkazem, provede se přesné odvození.
- Pokud V2..Vk:
- Pokud je parametr předán hodnotou, dojde k odvození horní hranice.
- Pokud je parametr odkazem, provede se přesné odvození.
Odvození horní hranice
Do odrážky 2 se přidá následující případ:
U
je typ ukazatele na funkcidelegate*<U2..Uk, U1>
aV
je typ ukazatele na funkci, který je shodný sdelegate*<V2..Vk, V1>
, a volací konvenceU
je shodná sV
, a charakteristika odkazuUi
je shodná sVi
.
První bod odvozování z Ui
do Vi
se změní na:
- Pokud
U
není typ ukazatele funkce aUi
není známo, že se jedná o typ odkazu, nebo pokudU
je typ ukazatele funkce aUi
není známo, že se jedná o typ ukazatele funkce nebo typ odkazu, provede se přesná odvození
Potom se přidá za třetí odrážku odvozování z Ui
do Vi
:
- Pokud je
U
delegate*<U2..Uk, U1>
pak odvození závisí na i-tém parametrudelegate*<U2..Uk, U1>
:
- Pokud U1:
- Pokud je vrácen hodnotou, probíhá odvození horní hranice .
- Pokud je návrat odkazem, provede se přesné odvození.
- Pokud U2..Uk:
- Pokud je parametr podle hodnoty, vytvoří se odvození v dolní hranici.
- Pokud je parametr odkazem, provede se přesná odvození.
Reprezentace metadat in
, out
a ref readonly
parametrů a návratových typů
Podpisy ukazatelů funkce nemají žádné umístění příznaků parametrů, takže musíme kódovat, zda parametry a návratový typ jsou in
, out
nebo ref readonly
pomocí modreqs.
in
Znovu použijeme System.Runtime.InteropServices.InAttribute
, v roli modreq
u specifikátoru ref u parametru nebo návratového typu, s následujícím významem:
- Pokud je použit u specifikátoru ref parametru, je tento parametr považován za
in
. - Pokud je použit na specifikátor ref návratového typu, považuje se návratový typ za
ref readonly
.
out
Používáme System.Runtime.InteropServices.OutAttribute
, použité jako modreq
specifikátoru ref u typu parametru, aby to znamenalo, že parametr je out
parametr.
Chyby
- Je chybou použít
OutAttribute
jako modreq pro návratový typ. - Jedná se o chybu použití
InAttribute
iOutAttribute
jako modreq pro typ parametru. - Pokud je některý z nich zadán prostřednictvím modoptu, budou ignorovány.
Reprezentace metadat konvencí volání
Konvence volání jsou kódovány v podpisu metody v metadatech kombinací příznaku CallKind
v podpisu a nula nebo více modopt
s na začátku podpisu. ECMA-335 v současné době deklaruje následující prvky v příznaku CallKind
:
CallKind
: default
| unmanaged cdecl
| unmanaged fastcall
| unmanaged thiscall
| unmanaged stdcall
| varargs
;
Z těchto možností budou ukazatele funkcí v jazyce C# podporovat všechny kromě varargs
.
Modul runtime (a nakonec 335) se navíc aktualizuje tak, aby zahrnoval nový CallKind
na nových platformách. Tento dokument v současné době nemá formální název, ale tento dokument bude používat unmanaged ext
jako zástupný symbol pro nový rozšiřitelný formát konvence volání. Bez modopt
s unmanaged ext
je výchozí konvence volání platformy unmanaged
bez hranatých závorek.
Mapování calling_convention_specifier
na CallKind
calling_convention_specifier
, který je buď vynechán, nebo zadán jako managed
, se namapuje na default
CallKind
. Toto je výchozí CallKind
jakékoli metody, která není přiřazena UnmanagedCallersOnly
.
Jazyk C# rozpozná 4 speciální identifikátory, které se mapují na konkrétní existující nespravované CallKind
s z ECMA 335. Aby k tomuto mapování mohlo dojít, musí být tyto identifikátory zadány samostatně, bez dalších identifikátorů a tento požadavek je kódován do specifikace pro unmanaged_calling_convention
s. Tyto identifikátory jsou Cdecl
, Thiscall
, Stdcall
a Fastcall
, které odpovídají unmanaged cdecl
, unmanaged thiscall
, unmanaged stdcall
a unmanaged fastcall
. Pokud je zadáno více než jeden identifer
, nebo pokud identifier
nepatří mezi speciálně rozpoznané identifikátory, provedeme speciální vyhledávání názvů pro tento identifikátor podle následujících pravidel:
- Předzálohovali jsme
identifier
řetězcemCallConv
- Podíváme se jenom na typy definované v oboru názvů
System.Runtime.CompilerServices
. - Podíváme se jenom na typy definované v základní knihovně aplikace, což je knihovna, která definuje
System.Object
a nemá žádné závislosti. - Podíváme se jenom na veřejné typy.
Pokud vyhledávání proběhne úspěšně na všech identifier
zadaných v unmanaged_calling_convention
, zakódujeme CallKind
jako unmanaged ext
a zakódujeme každý z vyřešených typů v sadě modopt
na počátku podpisu ukazatele na funkci. Upozornění: Tato pravidla znamenají, že uživatelé nemohou přidávat tyto identifier
jako předponu k CallConv
, protože by to mělo za následek vyhledání CallConvCallConvVectorCall
.
Při interpretaci metadat se nejprve podíváme na CallKind
. Pokud je to cokoli jiného než unmanaged ext
, ignorujeme všechny modopt
na návratovém typu pro účely určení konvence volání a použijeme pouze CallKind
. Pokud je CallKind
unmanaged ext
, podíváme se na modopty na začátku deklarace typu ukazatele funkce, provedeme sjednocení všech typů, které splňují následující požadavky:
- Definice se nachází v základní knihovně, která neodkazuje na žádné jiné knihovny a definuje
System.Object
. - Typ je definován v oboru názvů
System.Runtime.CompilerServices
. - Typ začíná předponou
CallConv
. - Typ je veřejný.
Tyto typy představují typy, které musí být nalezeny při vyhledávání na identifier
s v unmanaged_calling_convention
při definování typu ukazatele funkce ve zdroji.
Jedná se o chybu, pokud se pokusíte použít ukazatel na funkci s parametrem CallKind
nastaveným na unmanaged ext
a cílové runtime prostředí tuto vlastnost nepodporuje. To bude určeno vyhledáním přítomnosti System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind
konstanty. Pokud je tato konstanta přítomná, modul runtime se považuje za podporu této funkce.
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
je atribut používaný CLR k označení, že metoda by měla být volána s konkrétní konvencí volání. Z tohoto důvodu představujeme následující podporu pro práci s atributem:
- Jedná se o chybu při přímém volání metody anotované pomocí tohoto atributu z jazyka C#. Uživatelé musí získat ukazatel funkce na metodu a pak tento ukazatel vyvolat.
- Jedná se o chybu použití atributu na cokoli jiného, než je běžná statická metoda nebo běžná statická místní funkce. Kompilátor jazyka C# označí všechny statické nebo nestatické neběžné metody importované z metadat s tímto atributem jako jazykem nepodporované.
- Je chybou, když metoda označená atributem má parametr nebo návratový typ, který není
unmanaged_type
. - Jedná se o chybu pro metodu označenou atributem, která má parametry typu, i když jsou tyto parametry typu omezeny na
unmanaged
. - Jedná se o chybu pro metodu v obecném typu, která má být označena atributem.
- Jedná se o chybu při převodu metody označené atributem na typ delegáta.
- Jedná se o chybu při zadávání všech typů pro
UnmanagedCallersOnly.CallConvs
, které nesplňují požadavky na konvenci volánímodopt
s v metadatech.
Při určování konvence volání metody označené platným atributem UnmanagedCallersOnly
kompilátor provádí následující kontroly typů zadaných v CallConvs
vlastnosti k určení efektivní CallKind
a modopt
, které by se měly použít k určení konvence volání:
- Pokud nejsou zadány žádné typy,
CallKind
je považován zaunmanaged ext
, bez konvencí volánímodopt
na začátku typu ukazatele funkce. - Je-li zadán jeden typ a tento typ má název
CallConvCdecl
,CallConvThiscall
,CallConvStdcall
neboCallConvFastcall
,CallKind
je považován zaunmanaged cdecl
,unmanaged thiscall
,unmanaged stdcall
nebounmanaged fastcall
, bez konvence volánímodopt
s na začátku typu ukazatele funkce. - Pokud je zadáno více typů nebo jediný typ není pojmenován jako jeden ze speciálně zmíněných typů výše,
CallKind
je považován zaunmanaged ext
, a sjednocení zadaných typů se bere jakomodopt
na začátku typu ukazatele funkce.
Kompilátor se pak podívá na tuto efektivní CallKind
a modopt
kolekci a použije normální pravidla metadat k určení konečné konvence volání typu ukazatele funkce.
Otevřené otázky
Detekce podpory modulu runtime pro unmanaged ext
https://github.com/dotnet/runtime/issues/38135 monitoruje přidání tohoto příznaku. V závislosti na zpětné vazbě z recenze buď využijeme vlastnost uvedenou v problému, nebo použijeme přítomnost UnmanagedCallersOnlyAttribute
jako příznak, který určuje, zda běhová prostředí podporují unmanaged ext
.
Úvahy
Povolit metody instance
Návrh lze rozšířit tak, aby podporoval metody instancí tím, že využívá EXPLICITTHIS
konvenci volání rozhraní příkazového řádku (pojmenované instance
v kódu jazyka C#). Tato forma ukazatelů funkce rozhraní příkazového řádku umístí parametr this
jako explicitní první parametr syntaxe ukazatele funkce.
unsafe class Instance {
void Use() {
delegate* instance<Instance, string> f = &ToString;
f(this);
}
}
To je rozumné, ale přidává to do návrhu určitou komplikaci. Zejména proto, že ukazatele funkcí, které se liší podle konvence volání instance
a managed
, by byly nekompatibilní, i když se oba případy používají k vyvolání spravovaných metod se stejným podpisem jazyka C#. Také v každém uvažovaném případě, kde by bylo užitečné mít podobné řešení, existovalo jednoduché alternativní řešení: použijte static
místní funkci.
unsafe class Instance {
void Use() {
static string toString(Instance i) => i.ToString();
delegate*<Instance, string> f = &toString;
f(this);
}
}
Nepožadujte nebezpečné při deklaraci
Namísto vyžadování unsafe
při každém použití delegate*
je vyžadováno pouze v okamžiku, kdy je skupina metod převedena na delegate*
. V tomto případě přicházejí do hry základní bezpečnostní problémy (s vědomím, že obsahující sestavení nelze uvolnit, když je hodnota aktivní). Vyžadování unsafe
na jiných místech může být považováno za nadměrné.
Takto byl návrh původně zamýšlen. Výsledná jazyková pravidla působila velmi neohrabaně. Skutečnost, že se jedná o hodnotu ukazatele, není možné skrýt a stále se objevovala, a to i bez klíčového slova unsafe
. Například převod na object
nelze povolit, nemůže být členem class
atd. Návrh jazyka C# vyžaduje unsafe
pro všechny použití ukazatele, a proto tento návrh následuje za tímto účelem.
Vývojáři budou mít stále možnost prezentovat bezpečné obálky nad delegate*
hodnotami stejným způsobem jako dnes pro normální typy ukazatelů. Uvažovat:
unsafe struct Action {
delegate*<void> _ptr;
Action(delegate*<void> ptr) => _ptr = ptr;
public void Invoke() => _ptr();
}
Používání delegátů
Místo abyste použili nový prvek syntaxe delegate*
, jednoduše použijte existující typy delegate
s následujícím typem *
.
Func<object, object, bool>* ptr = &object.ReferenceEquals;
Řešení konvence volání lze realizovat anotací typů delegate
pomocí atributu, který určuje hodnotu CallingConvention
. Nepřítomnost atributu by indikovala spravovanou konvenci volání.
Kódování v IL je problematické. Podkladová hodnota musí být reprezentována jako ukazatel, ale musí také:
- Mít jedinečný typ, který umožňuje přetížení s různými typy funkčních ukazatelů.
- Být ekvivalentní pro účely OHI napříč hranicemi sestavení.
Poslední bod je obzvláště problematický. To znamená, že každé sestavení, které používá Func<int>*
, musí kódovat ekvivalentní typ v metadatech, i když Func<int>*
je definován v nějakém sestavení, které neřídí.
Kromě toho jakýkoli jiný typ, který je definován s názvem System.Func<T>
v sestavení, které není mscorlib, musí být jiné než verze definovaná v mscorlib.
Jednou z možností, kterou bylo prozkoumáno, bylo emitovat takový ukazatel jako mod_req(Func<int>) void*
. To ale nefunguje, protože mod_req
nemůže vytvořit vazbu na TypeSpec
, a proto nemůže cílit na obecné instance.
Pojmenované ukazatele na funkce
Syntaxe ukazatele funkce může být těžkopádná, zejména v složitých případech, jako jsou vnořené ukazatele funkce. Místo toho, aby vývojáři museli pokaždé zadávat signaturu, by mohl jazyk umožnit pojmenované deklarace ukazatelů na funkce, jak je tomu u delegate
.
func* void Action();
unsafe class NamedExample {
void M(Action a) {
a();
}
}
Součástí tohoto problému je, že primitivní rozhraní příkazového řádku nemá názvy, a proto by to byla čistě záležitost jazyka C# a umožnění by vyžadovalo určitou práci s metadaty. To je možné, ale je to značné množství práce. V podstatě vyžaduje, aby jazyk C# měl čistě pro tyto názvy doprovodný prvek k definiční tabulce typů.
Také když byly prozkoumány argumenty pojmenovaných ukazatelů funkcí, zjistili jsme, že by mohly být stejně dobře aplikovány na řadu dalších scénářů. Například by bylo stejně tak pohodlné deklarovat pojmenované ntice, aby se snížila nutnost zadávat úplný podpis ve všech případech.
(int x, int y) Point;
class NamedTupleExample {
void M(Point p) {
Console.WriteLine(p.x);
}
}
Po diskuzi jsme se rozhodli nepovolit pojmenovanou deklaraci delegate*
typů. Pokud zjistíme, že je to podle zpětné vazby od zákazníků podstatně potřebné, prozkoumáme řešení pojmenování, které funguje pro ukazatele funkcí, n-tice, generické typy atd. Pravděpodobně to bude v podobě podobné jiným návrhům, jako je úplná podpora typedef
v jazyce.
Důležité informace o budoucnosti
statický delegát
To se týká návrhu umožnit deklaraci typů delegate
, které mohou odkazovat pouze na členy static
. Výhodou je, že takové instance delegate
nevyžadují přidělování a lépe si vedou ve scénářích náročných na výkon.
Pokud je funkce ukazatele implementovaná, návrh static delegate
nejspíše bude uzavřen. Navrhovaná výhoda této funkce je povaha bez nutnosti přidělování paměti. Nedávná šetření však zjistila, že kvůli odstraňování sestavení není možné toho dosáhnout. Musí existovat silná reference z static delegate
na metodu, na kterou odkazuje, aby sestavení nebylo uvolněno z pod ní.
Aby bylo možné udržovat každou instanci static delegate
, bylo by nutné přidělit nový popisovač, což neodpovídá cílům návrhu. V některých návrzích bylo možné alokaci amortizovat na jedno přidělení na call-site, ale to bylo trochu složité a nestál za to.
To znamená, že vývojáři se v podstatě musí rozhodnout mezi následujícími kompromisy:
- Bezpečnost při demontáži sestavy: z toho vyplývá potřeba přidělení, a proto je
delegate
již dostatečnou možností. - Není zajištěna bezpečnost při uvolňování sestavy: použijte
delegate*
. To se dá zabalit dostruct
, aby se ve zbytku kódu povolilo použití mimo kontextunsafe
.
C# feature specifications