Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
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 delegatecallvirt 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
unsafecontext. - Methoden die een
delegate*parameter of retourtype bevatten, kunnen alleen worden aangeroepen vanuit eenunsafecontext. - 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 identifiergeldig 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
F0tot een andere funcptr_typeF1, mits aan alle volgende voorwaarden wordt voldaan:-
F0enF1hetzelfde aantal parameters hebben en elke parameterD0ninF0dezelfderef,outofinmodifiers heeft als de bijbehorende parameterD1ninF1. - Voor elke waardeparameter (een parameter zonder
ref,outofinmodifier), bestaat er een identiteitsconversie, impliciete verwijzingsconversie of impliciete aanwijzerconversie van het parametertype inF0naar het bijbehorende parametertype inF1. - Voor elke
ref,outofinparameter is het parametertype inF0hetzelfde als het bijbehorende parametertype inF1. - Als het retourtype op waarde is (geen
refofref readonly), bestaat er een identiteit, impliciete verwijzing of impliciete aanwijzerconversie van het retourtypeF1naar het retourtype vanF0. - Als het retourtype op basis van verwijzing (
refofref readonly) is, zijn het retourtype enrefmodifiers vanF1hetzelfde als het retourtype enrefmodifiers vanF0. - De oproepconventie van
F0is 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:
-
MenFhetzelfde aantal parameters hebben en elke parameter inMdezelfderef,outofinmodifiers heeft als de bijbehorende parameter inF. - Voor elke waardeparameter (een parameter zonder
ref,outofinmodifier), bestaat er een identiteitsconversie, impliciete verwijzingsconversie of impliciete aanwijzerconversie van het parametertype inMnaar het bijbehorende parametertype inF. - Voor elke
ref,outofinparameter is het parametertype inMhetzelfde als het bijbehorende parametertype inF. - Als het retourtype op waarde is (geen
refofref readonly), bestaat er een identiteit, impliciete verwijzing of impliciete aanwijzerconversie van het retourtypeFnaar het retourtype vanM. - Als het retourtype op basis van verwijzing (
refofref readonly) is, zijn het retourtype enrefmodifiers vanFhetzelfde als het retourtype enrefmodifiers vanM. - De oproepconventie van
Mis hetzelfde als de oproepconventie vanF. Dit omvat zowel het bit van de aanroepconventie als eventuele aanroepconventievlaggen die zijn opgegeven in de niet-beheerde identificator. -
Mis 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
Mwordt geselecteerd die overeenkomt met een methode-aanroep van het formulierE(A)met de volgende wijzigingen:- De argumentenlijst
Ais een lijst met uitdrukkingen, die elk zijn geclassificeerd als een variabele, met het type en de modificator (ref,outofin) 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
Mmet hetzelfde aantal parameters alsFen de conversie wordt beschouwd als bestaan. - De geselecteerde methode
Mmoet 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-
staticlokale 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
stackallockan worden gebruikt om geheugen toe te wijzen vanuit de aanroepstack (§23.8).- De
fixedinstructie 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
Eeen adres-of-methodegroep is enTeen functieaanwijzer is, zijn alle parametertypen vanTinvoertypen vanEmet het typeT.
Uitvoertypen
Het volgende wordt toegevoegd:
Als
Eeen adres-of-methodegroep is enTeen functieaanwijzer is, is het retourtypeTeen uitvoertype vanEmet het typeT.
Deducties van het uitvoertype
Het volgende opsommingsteken wordt toegevoegd tussen opsommingstekens 2 en 3:
- Als
Eeen adressmethodegroep is enTeen functieaanwijzer is met parametertypenT1...Tken retourtypeTb, en overbelastingsoplossing vanEmet de typenT1..Tkéén methode oplevert met retourtypeU, wordt er een ondergrensinferentie gemaakt vanUtotTb.
Betere conversie van uitdrukking
Het volgende sub-opsommingsteken wordt toegevoegd als een hoofdletter aan opsommingsteken 2:
Vis een functiepointertypedelegate*<V2..Vk, V1>enUis een functiepointertypedelegate*<U2..Uk, U1>, en de aanroepconventie vanVis identiek aanUen de refness vanViis identiek aanUi.
Ondergrensdeducties
Het volgende geval wordt toegevoegd aan punt 3:
Vis een functie aanwijzertypedelegate*<V2..Vk, V1>en er is een functiepointertypedelegate*<U2..Uk, U1>zodanig datUidentiek is aandelegate*<U2..Uk, U1>en dat de aanroepende conventie vanVidentiek is aanUen de refness vanViidentiek is aanUi.
Het eerste opsommingsteken van afleiding van Ui naar Vi is gewijzigd in:
- Als
Ugeen functieaanwijzertype is enUigeen verwijzingstype is, of alsUeen functieaanwijzer is enUigeen 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
Vdelegate*<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:
Uis een functiepointertypedelegate*<U2..Uk, U1>enVis een functiepointertype dat identiek is aandelegate*<V2..Vk, V1>, en de aanroepconventie vanUis identiek aanV, en de refness vanUiis identiek aanVi.
Het eerste opsommingsteken van afleiding van Ui naar Vi is gewijzigd in:
- Als
Ugeen functieaanwijzertype is enUigeen verwijzingstype is, of alsUeen functieaanwijzer is enUigeen 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
Udelegate*<U2..Uk, U1>is, hangt 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, outen ref readonly parameters en retourtypen
Functieaanwijzerhandtekeningen hebben geen locatie voor parametervlaggen, dus we moeten coderen of parameters en het retourtype in, outof 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
OutAttributetoe te passen als een modreq op een retourtype. - Het is een fout om zowel
InAttributealsOutAttributetoe 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 varargsondersteund.
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 modopts 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 defaultCallKind. 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 CallKindvan 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_conventions. Deze id's zijn respectievelijk Cdecl, Thiscall, Stdcallen Fastcall, die overeenkomen met respectievelijk unmanaged cdecl, unmanaged thiscall, unmanaged stdcallen 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
identifiervoorafgegaan door de tekenreeksCallConv - We kijken alleen naar typen die zijn gedefinieerd in de
System.Runtime.CompilerServicesnaamruimte. - We kijken alleen naar typen die zijn gedefinieerd in de kernbibliotheek van de toepassing. Dit is de bibliotheek die
System.Objectdefinieert en geen afhankelijkheden heeft. - We kijken alleen naar openbare typen.
Als opzoeken slaagt voor alle identifierdie zijn opgegeven in een unmanaged_calling_convention, coderen we de CallKind als unmanaged exten coderen we elk van de opgeloste typen in de set van modoptaan het begin van de functiepointersignatuur. Let op: deze regels betekenen dat gebruikers deze identifierniet kunnen voorafvoegen met CallConv, waardoor ze CallConvCallConvVectorCallkunnen opzoeken.
Bij het interpreteren van metagegevens kijken we eerst naar de CallKind. Als het iets anders is dan unmanaged ext, negeren we alle modoptop het retourtype voor het bepalen van de oproepconventie en gebruiken we alleen de CallKind. Als de CallKindunmanaged extis, 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.Objectdefinieert. - Het type wordt gedefinieerd in de
System.Runtime.CompilerServicesnaamruimte. - 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 identifierin 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_typeis. - 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.CallConvsdie niet voldoen aan de vereisten voor het aanroepen van conventiesmodopts 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 modopts te bepalen die moeten worden gebruikt om de aanroepende conventie te bepalen:
- Als er geen typen zijn opgegeven, wordt de
CallKindbehandeld alsunmanaged ext, zonder aanroepende conventiemodopts aan het begin van het functie aanwijzertype. - Als er één type is opgegeven en dit type
CallConvCdecl,CallConvThiscall,CallConvStdcallofCallConvFastcallis, wordt deCallKindbeschouwd alsunmanaged cdecl,unmanaged thiscall,unmanaged stdcallofunmanaged fastcall, zonder aanroepconventiemodopts 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
CallKindbehandeld alsunmanaged ext, met de samenvoeging van de typen die zijn opgegeven alsmodopts 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 extondersteunen.
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
delegateal een voldoende optie. - Geen veiligheid ten aanzien van het lossen van de montage: gebruik een
delegate*. Dit kan worden verpakt in eenstructom gebruik buiten eenunsafecontext in de rest van de code toe te staan.
C# feature specifications