Functie-aanwijzers
Notitie
Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.
Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante LDM-notities (Language Design Meeting).
Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.
Samenvatting
Dit voorstel bevat taalconstructies waarmee IL-opcodes worden weergegeven die momenteel niet efficiënt of helemaal niet kunnen worden geopend in C# vandaag: ldftn
en calli
. Deze IL-opcodes kunnen belangrijk zijn in code met hoge prestaties en ontwikkelaars hebben een efficiënte manier nodig om ze te openen.
Motivatie
De motivaties en achtergrond voor deze functie worden beschreven in het volgende probleem (zoals een mogelijke implementatie van de functie):
Dit is een alternatief ontwerpvoorstel voor compiler intrinsiek
Gedetailleerd ontwerp
Functieaanwijzers
De taal maakt de declaratie van functieaanwijzers mogelijk met behulp van de delegate*
syntaxis. De volledige syntaxis wordt gedetailleerd beschreven in de volgende sectie, maar is bedoeld om te lijken op de syntaxis die wordt gebruikt door Func
en Action
typedeclaraties.
unsafe class Example
{
void M(Action<int> a, delegate*<int, void> f)
{
a(42);
f(42);
}
}
Deze typen worden weergegeven met behulp van het functiepointertype, zoals wordt beschreven in ECMA-335. Dit betekent dat het aanroepen van een delegate*
calli
gebruikt waarbij het aanroepen van een delegate
callvirt
op de methode Invoke
gebruikt.
Syntactisch hoewel aanroep identiek is voor beide constructies.
De ECMA-335-definitie van methodepointers omvat de aanroepconventie als onderdeel van de typehandtekening (sectie 7.1).
De standaardconventie voor oproepen wordt managed
. Niet-beheerde aanroepconventies kunnen worden gespecificeerd door een unmanaged
trefwoord na de delegate*
syntaxis te plaatsen, waarbij de standaard van het runtimeplatform wordt gebruikt. Specifieke niet-beheerde conventies kunnen vervolgens tussen vierkante haken worden opgegeven voor het trefwoord unmanaged
door elk type op te geven dat begint met CallConv
in de System.Runtime.CompilerServices
naamruimte, waarbij het CallConv
voorvoegsel wordt weggelaten. Deze typen moeten afkomstig zijn van de kernbibliotheek van het programma en de set geldige combinaties is afhankelijk van het 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>;
Conversies tussen delegate*
typen worden uitgevoerd op basis van hun kenmerk, inclusief de aanroepconventie.
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
}
}
Een delegate*
-type is een pointertype, wat betekent dat het alle mogelijkheden en beperkingen van een standaard-pointertype heeft.
- Alleen geldig in een
unsafe
context. - Methoden die een
delegate*
parameter of retourtype bevatten, kunnen alleen worden aangeroepen vanuit eenunsafe
context. - Kan niet worden geconverteerd naar
object
. - Kan niet worden gebruikt als een algemeen argument.
- Kan impliciet
delegate*
converteren naarvoid*
. - Kan expliciet converteren van
void*
naardelegate*
.
Beperkingen:
- Aangepaste kenmerken kunnen niet worden toegepast op een
delegate*
of een van de elementen. - Een parameter voor
delegate*
kan niet worden gemarkeerd alsparams
- Een
delegate*
type heeft alle beperkingen van een normaal aanwijzertype. - Pointer-arithmetica kan niet rechtstreeks worden uitgevoerd op functiepointertypen.
Syntaxis van functiepointer
De syntaxis van de volledige functie aanwijzer wordt vertegenwoordigd door de volgende grammatica:
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'
;
Als er geen calling_convention_specifier
is opgegeven, wordt de standaardwaarde managed
. De exacte metagegevenscodering van de calling_convention_specifier
en welke identifier
geldig zijn in de unmanaged_calling_convention
wordt behandeld in metagegevensweergave van aanroepende conventies.
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>>;
Conversies van functie-aanwijzers
In een onveilige context wordt de set beschikbare impliciete conversies (impliciete conversies) uitgebreid met de volgende impliciete aanwijzerconversies:
- bestaande conversies - (§23,5)
- Van funcptr_type
F0
tot een andere funcptr_typeF1
, mits aan alle volgende voorwaarden wordt voldaan:-
F0
enF1
hetzelfde aantal parameters hebben en elke parameterD0n
inF0
dezelfderef
,out
ofin
modifiers heeft als de bijbehorende parameterD1n
inF1
. - Voor elke waardeparameter (een parameter zonder
ref
,out
ofin
modifier), bestaat er een identiteitsconversie, impliciete verwijzingsconversie of impliciete aanwijzerconversie van het parametertype inF0
naar het bijbehorende parametertype inF1
. - Voor elke
ref
,out
ofin
parameter is het parametertype inF0
hetzelfde als het bijbehorende parametertype inF1
. - Als het retourtype op waarde is (geen
ref
ofref readonly
), bestaat er een identiteit, impliciete verwijzing of impliciete aanwijzerconversie van het retourtypeF1
naar het retourtype vanF0
. - Als het retourtype op basis van verwijzing (
ref
ofref readonly
) is, zijn het retourtype enref
modifiers vanF1
hetzelfde als het retourtype enref
modifiers vanF0
. - De oproepconventie van
F0
is hetzelfde als de oproepconventie vanF1
.
-
Adres-van-naar-doelmethoden toestaan
Methodegroepen worden nu toegestaan als argumenten voor een adres-of-expressie. Het type van een dergelijke expressie is een delegate*
met de equivalente handtekening van de doelmethode en een beheerde aanroepconventie:
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;
}
}
In een onveilige context is een methode M
compatibel is met een functieaanwijzertype F
als alle volgende waar zijn:
-
M
enF
hetzelfde aantal parameters hebben en elke parameter inM
dezelfderef
,out
ofin
modifiers heeft als de bijbehorende parameter inF
. - Voor elke waardeparameter (een parameter zonder
ref
,out
ofin
modifier), bestaat er een identiteitsconversie, impliciete verwijzingsconversie of impliciete aanwijzerconversie van het parametertype inM
naar het bijbehorende parametertype inF
. - Voor elke
ref
,out
ofin
parameter is het parametertype inM
hetzelfde als het bijbehorende parametertype inF
. - Als het retourtype op waarde is (geen
ref
ofref readonly
), bestaat er een identiteit, impliciete verwijzing of impliciete aanwijzerconversie van het retourtypeF
naar het retourtype vanM
. - Als het retourtype op basis van verwijzing (
ref
ofref readonly
) is, zijn het retourtype enref
modifiers vanF
hetzelfde als het retourtype enref
modifiers vanM
. - De oproepconventie van
M
is hetzelfde als de oproepconventie vanF
. Dit omvat zowel het bit van de aanroepconventie als eventuele aanroepconventievlaggen die zijn opgegeven in de niet-beheerde identificator. -
M
is een statische methode.
In een onveilige context bestaat een impliciete conversie van een adres-of-expressie waarvan het doel een methodegroep is E
naar een compatibel functieaanwijzertype F
als E
ten minste één methode bevat die in de normale vorm van toepassing is op een argumentenlijst die is samengesteld met behulp van de parametertypen en modifiers van F
, zoals beschreven in het volgende.
- Eén methode
M
wordt geselecteerd die overeenkomt met een methode-aanroep van het formulierE(A)
met de volgende wijzigingen:- De argumentenlijst
A
is een lijst met uitdrukkingen, die elk zijn geclassificeerd als een variabele, met het type en de modificator (ref
,out
ofin
) van de bijbehorende funcptr_parameter_list vanF
. - De kandidaatmethoden zijn alleen methoden die van toepassing zijn in hun normale vorm, niet methoden die van toepassing zijn in hun uitgevouwen vorm.
- De kandidaatmethoden zijn alleen methoden die statisch zijn.
- De argumentenlijst
- Als het algoritme van overbelastingsresolutie een fout veroorzaakt, treedt er een compilatietijdfout op. Anders produceert het algoritme één beste methode
M
met hetzelfde aantal parameters alsF
en de conversie wordt beschouwd als bestaan. - De geselecteerde methode
M
moet compatibel zijn (zoals hierboven gedefinieerd) met het functiepointertypeF
. Anders treedt er een compilatietijdfout op. - Het resultaat van de conversie is een functiewijzer van het type
F
.
Dit betekent dat ontwikkelaars afhankelijk kunnen zijn van regels voor overbelastingsresolutie om te werken in combinatie met het adres van de operator:
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;
}
}
Het adres van de operator wordt geïmplementeerd met behulp van de ldftn
instructie.
Beperkingen van deze functie:
- Alleen van toepassing op methoden die zijn gemarkeerd als
static
. - Niet-
static
lokale functies kunnen niet worden gebruikt in&
. De implementatiedetails van deze methoden worden bewust niet opgegeven door de taal. Dit omvat of ze statisch versus exemplaar zijn of precies met welke handtekening ze worden verzonden.
Operators op functiepointertypen
De sectie in onveilige code voor expressies wordt als volgt gewijzigd:
In een onveilige context zijn verschillende constructies beschikbaar voor gebruik op alle _pointer_type_s die niet _funcptr_type_s zijn:
- De operator
*
kan worden gebruikt om aanwijzer indirectie uit te voeren (§23.6.2).- De
->
operator kan worden gebruikt voor toegang tot een lid van een struct via een aanwijzer (§23.6.3).- De operator
[]
kan worden gebruikt om een aanwijzer te indexeren (§23.6.4).- De operator
&
kan worden gebruikt om het adres van een variabele te verkrijgen (§23.6.5).- De operatoren
++
en--
kunnen worden gebruikt voor het verhogen en verlagen van aanwijzers (§23.6.6).- De operatoren
+
en-
kunnen worden gebruikt om rekenkundige bewerkingen met aanwijzers uit te voeren (§23.6.7).- De
==
,!=
,<
,>
,<=
en=>
operatoren kunnen worden gebruikt om aanwijzers te vergelijken (§23.6.8).- De operator
stackalloc
kan worden gebruikt om geheugen toe te wijzen vanuit de aanroepstack (§23.8).- De
fixed
instructie kan worden gebruikt om een variabele tijdelijk te herstellen, zodat het adres ervan kan worden verkregen (§23.7).In een onveilige context zijn verschillende constructies beschikbaar voor gebruik op alle _funcptr_type_s:
- De operator
&
kan worden gebruikt om het adres van statische methoden te verkrijgen (Adresadres van doelmethoden toestaan)- De
==
,!=
,<
,>
,<=
en=>
operatoren kunnen worden gebruikt om aanwijzers te vergelijken (§23.6.8).
Daarnaast wijzigen we alle secties in Pointers in expressions
om functiepointertypen te verbieden, met uitzondering van Pointer comparison
en The sizeof operator
.
Beter functielid
§12.6.4.3 Het betere functielid wordt gewijzigd om de volgende regel op te nemen:
Een
delegate*
is specifieker danvoid*
Dit betekent dat het mogelijk is om te overbelasten op void*
en een delegate*
en toch gevoelig gebruik te maken van het adres van de operator.
Type inferentie
In onveilige code worden de volgende wijzigingen aangebracht in de algoritmen voor typedeductie:
Invoertypen
Het volgende wordt toegevoegd:
Als
E
een adres-of-methodegroep is enT
een functieaanwijzer is, zijn alle parametertypen vanT
invoertypen vanE
met het typeT
.
Uitvoertypen
Het volgende wordt toegevoegd:
Als
E
een adres-of-methodegroep is enT
een functieaanwijzer is, is het retourtypeT
een uitvoertype vanE
met het typeT
.
Deducties van het uitvoertype
Het volgende opsommingsteken wordt toegevoegd tussen opsommingstekens 2 en 3:
- Als
E
een adressmethodegroep is enT
een functieaanwijzer is met parametertypenT1...Tk
en retourtypeTb
, en overbelastingsoplossing vanE
met de typenT1..Tk
één methode oplevert met retourtypeU
, wordt er een ondergrensinferentie gemaakt vanU
totTb
.
Betere conversie van uitdrukking
Het volgende sub-opsommingsteken wordt toegevoegd als een hoofdletter aan opsommingsteken 2:
V
is een functiepointertypedelegate*<V2..Vk, V1>
enU
is een functiepointertypedelegate*<U2..Uk, U1>
, en de aanroepconventie vanV
is identiek aanU
en de refness vanVi
is identiek aanUi
.
Ondergrensdeducties
Het volgende geval wordt toegevoegd aan punt 3:
V
is een functie aanwijzertypedelegate*<V2..Vk, V1>
en er is een functiepointertypedelegate*<U2..Uk, U1>
zodanig datU
identiek is aandelegate*<U2..Uk, U1>
en dat de aanroepende conventie vanV
identiek is aanU
en de refness vanVi
identiek is aanUi
.
Het eerste opsommingsteken van afleiding van Ui
naar Vi
is gewijzigd in:
- Als
U
geen functieaanwijzertype is enUi
geen verwijzingstype is, of alsU
een functieaanwijzer is enUi
geen functieaanwijzer of een verwijzingstype is, wordt er een exacte deductie gemaakt
Vervolgens toegevoegd na het 3e opsommingsteken van deductie van Ui
naar Vi
:
- Anders, als
V
delegate*<V2..Vk, V1>
is, hangt deductie af van de i-de parameter vandelegate*<V2..Vk, V1>
:
- Als V1:
- Als het retour op waarde is, wordt er een ondergrensafleiding gemaakt.
- Als de retour wordt verwezen, wordt er een exacte deductie gemaakt.
- Als V2..Vk:
- Als de parameter een waarde heeft, wordt er een bovengrensinferentie gemaakt.
- Als de parameter ter referentie is, wordt er een exacte deductie gemaakt.
Bovengrensinferenties
Het volgende geval wordt toegevoegd aan punt 2:
U
is een functiepointertypedelegate*<U2..Uk, U1>
enV
is een functiepointertype dat identiek is aandelegate*<V2..Vk, V1>
, en de aanroepconventie vanU
is identiek aanV
, en de refness vanUi
is identiek aanVi
.
Het eerste opsommingsteken van deductie van Ui
naar Vi
is aangepast naar:
- Als
U
geen functieaanwijzertype is enUi
geen verwijzingstype is, of alsU
een functieaanwijzer is enUi
geen functieaanwijzer of een verwijzingstype is, wordt er een exacte deductie gemaakt
Vervolgens toegevoegd na het derde punt van deductie tussen Ui
en Vi
:
- Anders, als
U
delegate*<U2..Uk, U1>
is, hangt de deductie af van de i-de parameter vandelegate*<U2..Uk, U1>
:
- Als U1:
- Als het resultaat op basis van waarde is, wordt er een bovengrensafleiding gemaakt.
- Als de retour wordt verwezen, wordt er een exacte deductie gemaakt.
- Als U2..Uk:
- Als de parameter op waarde is, wordt er een ondergrensinferentie gemaakt.
- Als de parameter ter referentie is, wordt er een exacte deductie gemaakt.
Metagegevensweergave van in
, out
en ref readonly
parameters en retourtypen
Functieaanwijzerhandtekeningen hebben geen locatie voor parametervlaggen, dus we moeten coderen of parameters en het retourtype in
, out
of ref readonly
zijn met behulp van "modreqs".
in
We hergebruiken System.Runtime.InteropServices.InAttribute
, toegepast als een modreq
op de verwijzingsaanduiding voor een parameter of retourtype, om het volgende te betekenen:
- Als deze parameter wordt toegepast op een parameter ref-specificeerder, wordt deze parameter behandeld als
in
. - Als dit wordt toegepast op de verwijzingsaanduiding van het retourtype, wordt het retourtype behandeld als
ref readonly
.
out
We gebruiken System.Runtime.InteropServices.OutAttribute
, toegepast als een modreq
op de verwijzingsaanduiding voor een parametertype, om te betekenen dat de parameter een out
parameter is.
Fouten
- Het is een fout om
OutAttribute
toe te passen als een modreq op een retourtype. - Het is een fout om zowel
InAttribute
alsOutAttribute
toe te passen als een modreq op een parametertype. - Als een van beide via modopt wordt opgegeven, worden ze genegeerd.
Metagegevensweergave van aanroepconventies
Aanroepconventies worden gecodeerd in een methode-signatuur in metagegevens door een combinatie van de CallKind
-indicator in de signatuur en nul of meer modopt
's aan het begin van de signatuur. ECMA-335 declareert momenteel de volgende elementen in de CallKind
vlag:
CallKind
: default
| unmanaged cdecl
| unmanaged fastcall
| unmanaged thiscall
| unmanaged stdcall
| varargs
;
Van deze, functie aanwijzers in C# worden alle behalve varargs
ondersteund.
Daarnaast wordt de runtime (en uiteindelijk 335) bijgewerkt met een nieuwe CallKind
op nieuwe platforms. Dit heeft momenteel geen formele naam, maar in dit document wordt unmanaged ext
gebruikt als tijdelijke aanduiding voor de nieuwe uitbreidbare aanroepconventieindeling. Zonder modopt
s is unmanaged ext
de standaardconventie voor platformgesprekken, unmanaged
zonder vierkante haken.
Het toewijzen van de calling_convention_specifier
aan een CallKind
Een calling_convention_specifier
die wordt weggelaten of opgegeven als managed
, wordt toegewezen aan de default
CallKind
. Dit is standaard CallKind
van een methode die niet is toegeschreven aan UnmanagedCallersOnly
.
C# herkent 4 speciale id's die zijn toegewezen aan specifieke bestaande onbeheerde CallKind
van ECMA 335. Om deze toewijzing te kunnen uitvoeren, moeten deze id's afzonderlijk worden opgegeven, zonder andere id's, en deze vereiste wordt gecodeerd in de specificatie voor unmanaged_calling_convention
s. Deze id's zijn respectievelijk Cdecl
, Thiscall
, Stdcall
en Fastcall
, die overeenkomen met respectievelijk unmanaged cdecl
, unmanaged thiscall
, unmanaged stdcall
en unmanaged fastcall
. Als er meer dan één identifer
is opgegeven of als de enkele identifier
niet van de speciaal herkende id's is, voeren we een speciale naamzoekactie uit op de id met de volgende regels:
- We hebben de
identifier
voorafgegaan door de tekenreeksCallConv
- We kijken alleen naar typen die zijn gedefinieerd in de
System.Runtime.CompilerServices
naamruimte. - We kijken alleen naar typen die zijn gedefinieerd in de kernbibliotheek van de toepassing. Dit is de bibliotheek die
System.Object
definieert en geen afhankelijkheden heeft. - We kijken alleen naar openbare typen.
Als opzoeken slaagt voor alle identifier
die zijn opgegeven in een unmanaged_calling_convention
, coderen we de CallKind
als unmanaged ext
en coderen we elk van de opgeloste typen in de set van modopt
aan het begin van de functiepointersignatuur. Let op: deze regels betekenen dat gebruikers deze identifier
niet kunnen voorafvoegen met CallConv
, waardoor ze CallConvCallConvVectorCall
kunnen opzoeken.
Bij het interpreteren van metagegevens kijken we eerst naar de CallKind
. Als het iets anders is dan unmanaged ext
, negeren we alle modopt
op het retourtype voor het bepalen van de oproepconventie en gebruiken we alleen de CallKind
. Als de CallKind
unmanaged ext
is, kijken we naar de modopts aan het begin van het functiepointertype, waarbij we de verzameling maken van alle typen die aan de volgende vereisten voldoen:
- Het is gedefinieerd in de kernbibliotheek, de bibliotheek die verwijst naar geen andere bibliotheken en
System.Object
definieert. - Het type wordt gedefinieerd in de
System.Runtime.CompilerServices
naamruimte. - Het type begint met het voorvoegsel
CallConv
. - Het type is openbaar.
Deze vertegenwoordigen de typen die moeten worden gevonden bij het uitvoeren van zoekacties op de identifier
in een unmanaged_calling_convention
bij het definiëren van een functieaanwijzertype in de bron.
Het is een fout om een functieaanwijzer te gebruiken met een CallKind
van unmanaged ext
als de runtime van het doelsysteem deze voorziening niet ondersteunt. Dit wordt bepaald door te zoeken naar de aanwezigheid van de System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind
constante. Als deze constante aanwezig is, wordt de runtime beschouwd als ondersteuning voor de functie.
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
is een kenmerk dat door de CLR wordt gebruikt om aan te geven dat een methode moet worden aangeroepen met een specifieke aanroepconventie. Daarom introduceren we de volgende ondersteuning voor het werken met het kenmerk:
- Het is een fout om rechtstreeks een methode aan te roepen die is geannoteerd met dit kenmerk vanuit C#. Gebruikers moeten een functiepointer voor de methode verkrijgen en die aanwijzer vervolgens aanroepen.
- Het is een fout om het kenmerk toe te passen op iets anders dan een gewone statische methode of gewone statische lokale functie. De C#-compiler markeert alle niet-statische of statische niet-gewone methoden die zijn geïmporteerd uit metagegevens met dit kenmerk als niet-ondersteund door de taal.
- Het is een fout voor een methode die is gemarkeerd met het kenmerk dat een parameter of retourtype heeft dat geen
unmanaged_type
is. - Het is een fout voor een methode die is gemarkeerd met het kenmerk om typeparameters te hebben, zelfs als deze typeparameters zijn beperkt tot
unmanaged
. - Het is een fout als een methode in een generiek type gemarkeerd is met het kenmerk.
- Het is een fout om een methode te converteren die is gemarkeerd met het kenmerk naar een gemachtigdentype.
- Het is een fout om typen op te geven voor
UnmanagedCallersOnly.CallConvs
die niet voldoen aan de vereisten voor het aanroepen van conventiesmodopt
s in metagegevens.
Bij het bepalen van de aanroepconventie van een methode die is gemarkeerd met een geldig UnmanagedCallersOnly
kenmerk, voert de compiler de volgende controles uit op de typen die zijn opgegeven in de eigenschap CallConvs
om de effectieve CallKind
en modopt
s te bepalen die moeten worden gebruikt om de aanroepende conventie te bepalen:
- Als er geen typen zijn opgegeven, wordt de
CallKind
behandeld alsunmanaged ext
, zonder aanroepende conventiemodopt
s aan het begin van het functie aanwijzertype. - Als er één type is opgegeven en dit type
CallConvCdecl
,CallConvThiscall
,CallConvStdcall
ofCallConvFastcall
is, wordt deCallKind
beschouwd alsunmanaged cdecl
,unmanaged thiscall
,unmanaged stdcall
ofunmanaged fastcall
, zonder aanroepconventiemodopt
s aan het begin van het functiepunttype. - Als er meerdere typen worden opgegeven of als het ene type niet een van de hierboven genoemde typen wordt genoemd, wordt de
CallKind
behandeld alsunmanaged ext
, met de samenvoeging van de typen die zijn opgegeven alsmodopt
s aan het begin van het functie-aanwijzertype.
De compiler kijkt vervolgens naar deze effectieve CallKind
en modopt
verzameling en gebruikt normale metagegevensregels om de uiteindelijke aanroepconventie van het functie-aanwijzertype te bepalen.
Openstaande vragen
Runtime-ondersteuning voor unmanaged ext
detecteren
https://github.com/dotnet/runtime/issues/38135 houdt het bij dat deze vlag wordt toegevoegd. Afhankelijk van de feedback van de beoordeling gebruiken we de opgegeven eigenschap in het probleem, of we gebruiken de aanwezigheid van UnmanagedCallersOnlyAttribute
als indicatie die bepaalt of de runtimes unmanaged ext
ondersteunen.
Overwegingen
Exemplaarmethoden toestaan
Het voorstel kan worden uitgebreid ter ondersteuning van exemplaarmethoden door gebruik te maken van de EXPLICITTHIS
CLI-aanroepconventie (met de naam instance
in C#-code). Deze vorm van CLI-functieaanwijzers plaatst de this
parameter als een expliciete eerste parameter van de syntaxis van de functieaanwijzer.
unsafe class Instance {
void Use() {
delegate* instance<Instance, string> f = &ToString;
f(this);
}
}
Dit klinkt goed, maar voegt wat complicatie toe aan het voorstel. Met name omdat functie-aanwijzers die afwijken van de aanroepende conventie instance
en managed
niet compatibel zijn, ook al worden beide gevallen gebruikt om beheerde methoden aan te roepen met dezelfde C#-handtekening. Ook in elk geval waarin dit waardevol zou zijn, was er een eenvoudige oplossing: gebruik een lokale functie static
.
unsafe class Instance {
void Use() {
static string toString(Instance i) => i.ToString();
delegate*<Instance, string> f = &toString;
f(this);
}
}
Geen onveilige declaratie vereisen
In plaats van unsafe
bij elk gebruik van een delegate*
te vereisen, hoeft u deze alleen te vereisen op het moment waarop een methodegroep wordt geconverteerd naar een delegate*
. Dit is waar de kernveiligheidskwesties in het spel komen (omdat men weet dat de omvattende assembly niet kan worden verwijderd terwijl de waarde actief is). Het vereisen van unsafe
op de andere locaties kan als buitensporig worden beschouwd.
Dit is de manier waarop het ontwerp oorspronkelijk was bedoeld. Maar de resulterende taalregels voelden erg onhandig. Het is onmogelijk om te verbergen dat dit een pointerwaarde is en het bleef doorschemeren, zelfs zonder het unsafe
trefwoord. Bijvoorbeeld kan de conversie naar object
niet worden toegestaan, het kan geen lid zijn van een class
, enzovoort... Het ontwerp van C# vereist unsafe
voor alle pointergebruik en daarom volgt dit ontwerp dat.
Ontwikkelaars kunnen nog steeds een veilige wrapper presenteren bovenop delegate*
waarden op dezelfde manier als voor normale pointertypen. Overwegen:
unsafe struct Action {
delegate*<void> _ptr;
Action(delegate*<void> ptr) => _ptr = ptr;
public void Invoke() => _ptr();
}
Gedelegeerden gebruiken
In plaats van een nieuw syntaxelement te gebruiken, delegate*
, gebruikt u eenvoudigweg bestaande delegate
-typen gevolgd door een *
-type.
Func<object, object, bool>* ptr = &object.ReferenceEquals;
Het afhandelen van de aanroepconventie kan worden uitgevoerd door aantekeningen te maken op de delegate
typen met een kenmerk dat een CallingConvention
waarde aangeeft. Het ontbreken van een attribuut zou wijzen op de conventie voor beheerde aanroepen.
Het coderen hiervan in IL is problematisch. De onderliggende waarde moet worden weergegeven als een aanwijzer, maar moet ook:
- Een uniek type hebben om overbelastingen met verschillende functiepointertypen mogelijk te maken.
- Gelijkwaardig zijn voor OHI-doeleinden over assemblygrenzen heen.
Het laatste punt is bijzonder problematisch. Dit betekent dat elke assembly die gebruikmaakt van Func<int>*
een equivalent type in de metagegevens moet coderen, zelfs als Func<int>*
is gedefinieerd in een assembly die ze niet beheersen.
Daarnaast moet elk ander type dat is gedefinieerd met de naam System.Func<T>
in een assembly die niet mscorlib is, anders zijn dan de versie die is gedefinieerd in mscorlib.
Een optie die is verkend, was het verzenden van zo'n aanwijzer als mod_req(Func<int>) void*
. Dit werkt echter niet omdat een mod_req
geen binding kan maken met een TypeSpec
en daarom geen algemene instantiëringen kan instellen.
Benoemde functie-aanwijzers
De syntaxis van de functiepointer kan omslachtig zijn, met name in complexe gevallen zoals geneste functiepointers. In plaats van dat ontwikkelaars de handtekening elke keer typen wanneer de taal benoemde declaraties van functieaanwijzers mogelijk maakt, zoals met delegate
.
func* void Action();
unsafe class NamedExample {
void M(Action a) {
a();
}
}
Een deel van het probleem hier is dat de onderliggende CLI-primitieve geen namen heeft, dus dit is puur een C#-uitvinding en vereist een beetje metagegevenswerk om in te schakelen. Dat is haalbaar, maar het is een aanzienlijke hoeveelheid werk. C# moet in wezen een aanvulling hebben op de type def-tabel, uitsluitend voor deze namen.
Ook toen de argumenten voor benoemde functie-aanwijzers werden onderzocht, vonden we dat ze even goed konden worden toegepast op een aantal andere scenario's. Het is bijvoorbeeld net zo handig om benoemde tuples te declareren om de noodzaak te verminderen om in alle gevallen de volledige handtekening uit te typen.
(int x, int y) Point;
class NamedTupleExample {
void M(Point p) {
Console.WriteLine(p.x);
}
}
Na een discussie hebben we besloten om de benoemde declaratie van delegate*
-typen niet toe te staan. Als we merken dat er aanzienlijke behoefte is op basis van gebruikersfeedback, zullen we een oplossing voor naamgeving onderzoeken die werkt voor functiepointers, tuples, generics, enzovoort. Dit is waarschijnlijk vergelijkbaar met andere suggesties, zoals volledige ondersteuning van typedef
in de taal.
Toekomstige overwegingen
statische gemachtigden
Dit verwijst naar het voorstel om het declareren van delegate
typen toe te staan die alleen verwijzen naar static
leden. Het voordeel is dat dergelijke delegate
exemplaren zonder toewijzing kunnen zijn en beter presteren in prestatiegevoelige scenario's.
Als de functie aanwijzer is geïmplementeerd, wordt het static delegate
voorstel waarschijnlijk gesloten. Het voorgestelde voordeel van deze functie is de toewijzingsvrije aard. Recente onderzoeken hebben echter vastgesteld dat het niet mogelijk is te realiseren vanwege het ontladen van de montage. Er moet een sterke referentie zijn van de static delegate
naar de methode waar het naar verwijst om ervoor te zorgen dat de assembly niet wordt ontladen.
Als u elke static delegate
instantie wilt onderhouden, moet u een nieuwe ingang toewijzen die tegen de doelstellingen van het voorstel wordt uitgevoerd. nl-NL: Er waren enkele ontwerpen waarbij de toewijzing kon worden omgezet naar één toewijzing per aanroepplaats, maar dat was enigszins ingewikkeld en leek het compromis niet waard.
Dit betekent dat ontwikkelaars in wezen moeten beslissen tussen de volgende afwegingen:
- Veiligheid ten aanzien van het lossen van de montage: hiervoor zijn toewijzingen vereist en daarom is
delegate
al een voldoende optie. - Geen veiligheid ten aanzien van het lossen van de montage: gebruik een
delegate*
. Dit kan worden verpakt in eenstruct
om gebruik buiten eenunsafe
context in de rest van de code toe te staan.
C# feature specifications