Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Notera
Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.
Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningarna (språkutvecklingsmöte).
Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.
Sammanfattning
Det här förslaget innehåller språkkonstruktioner som exponerar IL-opcodes som för närvarande inte kan nås effektivt, eller alls, i C# idag: ldftn och calli. Dessa IL-opcodes kan vara viktiga i kod med höga prestanda och utvecklare behöver ett effektivt sätt att komma åt dem.
Motivation
Motiveringarna och bakgrunden till den här funktionen beskrivs i följande ärende (liksom en potentiell implementering av funktionen):
Det här är ett alternativt designförslag till kompilatorns inbyggda
Detaljerad design
Funktionspekare
Språket tillåter deklaration av funktionspekare med hjälp av syntaxen delegate*. Den fullständiga syntaxen beskrivs i detalj i nästa avsnitt, men den är avsedd att likna syntaxen som används av Func och Action typdeklarationer.
unsafe class Example
{
    void M(Action<int> a, delegate*<int, void> f)
    {
        a(42);
        f(42);
    }
}
Dessa typer representeras med hjälp av funktionspekartypen enligt beskrivningen i ECMA-335. Det innebär att anrop av en delegate* använder calli där anrop av en delegate använder callvirt på metoden Invoke.
Syntaktiskt är dock anropet identiskt för båda konstruktionerna.
ECMA-335-definitionen av metodpekare innehåller anropskonventionen som en del av typsignaturen (avsnitt 7.1).
Standardanropskonventionen är managed. Ohanterade anropskonventioner kan anges genom att lägga till ett unmanaged-nyckelord efter delegate*-syntax, vilket använder körningsplattformens standardinställning. Specifika ohanterade konventioner kan sedan anges inom hakparenteser till nyckelordet unmanaged genom att ange vilken typ som helst som börjar med CallConv i System.Runtime.CompilerServices namnrymd, vilket lämnar CallConv prefixet. Dessa typer måste komma från programmets kärnbibliotek och uppsättningen giltiga kombinationer är plattformsberoende.
//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>;
Konverteringar mellan delegate* typer görs baserat på deras signatur, inklusive anropskonventionen.
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
    }
}
En delegate* typ är en pekartyp, vilket innebär att den har alla funktioner och begränsningar för en standardpekartyp:
- Endast giltigt i en unsafe-kontext.
- Metoder som innehåller en delegate*parameter eller returtyp kan bara anropas från enunsafekontext.
- Det går inte att konvertera till object.
- Det går inte att använda som ett allmänt argument.
- Kan implicit konvertera delegate*tillvoid*.
- Kan explicit konvertera från void*tilldelegate*.
Inskränkningar:
- Anpassade attribut kan inte tillämpas på en delegate*eller något av dess element.
- Det går inte att markera en delegate*parameter somparams
- En delegate*typ har alla begränsningar för en normal pekartyp.
- Pekarens aritmetik kan inte utföras direkt på funktionspekartyper.
Syntax för funktionspekare
Den fullständiga syntaxen för funktionspekaren representeras av följande grammatik:
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'
    ;
Om ingen calling_convention_specifier anges är standardvärdet managed. Exakt metadatakodning av calling_convention_specifier och vilka identifiersom är giltiga i unmanaged_calling_convention beskrivs i Metadatarepresentation av samtalskonventioner.
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>>;
Konvertering av funktionspekare
I ett osäkert sammanhang utökas uppsättningen med tillgängliga implicita konverteringar (implicita konverteringar) till att omfatta följande implicita pekarkonverteringar:
- Befintliga konverteringar - (§23.5)
- Från funcptr_typeF0till en annan funcptr_typeF1, förutsatt att allt följande är sant:- 
              F0ochF1har samma antal parametrar och varje parameterD0niF0har sammaref,outellerinmodifierare som motsvarande parameterD1niF1.
- För varje värdeparameter (en parameter utan ref,outellerinmodifierare) finns det en identitetskonvertering, implicit referenskonvertering eller implicit pekarkonvertering från parametertypen iF0till motsvarande parametertyp iF1.
- För varje parameter ref,outellerinär parametertypen iF0samma som motsvarande parametertyp iF1.
- Om returtypen är ett värde (ingen refellerref readonly) finns det en identitet, implicit referens eller implicit pekarkonvertering från returtypen avF1till returtypen avF0.
- Om returtypen är per referens (refellerref readonly) är returtypen ochrefmodifierare förF1desamma som returtypen ochrefmodifierare förF0.
- Anropskonventionen för F0är densamma som den anropande konventionen förF1.
 
- 
              
Tillåt adress-of att rikta in sig på metoder
Metodgrupper kommer nu att tillåtas som argument i ett adressuttryck. Typen av ett sådant uttryck är en delegate* som har motsvarande signatur för målmetoden och en konvention för hanterade anrop:
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;
   }
}
I ett osäkert sammanhang är en metod M kompatibel med en funktionspekartyp F om allt av följande är sant:
- 
              MochFhar samma antal parametrar och varje parameter iMhar sammaref,outellerinmodifierare som motsvarande parameter iF.
- För varje värdeparameter (en parameter utan ref,outellerinmodifierare) finns det en identitetskonvertering, implicit referenskonvertering eller implicit pekarkonvertering från parametertypen iMtill motsvarande parametertyp iF.
- För varje parameter ref,outellerinär parametertypen iMsamma som motsvarande parametertyp iF.
- Om returtypen är ett värde (ingen refellerref readonly) finns det en identitet, implicit referens eller implicit pekarkonvertering från returtypen avFtill returtypen avM.
- Om returtypen är per referens (refellerref readonly) är returtypen ochrefmodifierare förFdesamma som returtypen ochrefmodifierare förM.
- Anropskonventionen för Mär densamma som den anropande konventionen förF. Detta omfattar både anropskonventionsbiten och alla anropskonventionsflaggor som anges i den ohanterade identifieraren.
- 
              Mär en statisk metod.
I ett osäkert sammanhang finns det en implicit konvertering från ett adressuttryck vars mål är en metodgrupp E till en kompatibel funktionspekartyp F om E innehåller minst en metod som är tillämplig i sin normala form på en argumentlista som konstruerats med hjälp av parametertyperna och modifierarna för F, enligt beskrivningen i följande.
- En enda metod Mväljs som motsvarar en metodanrop av formuläretE(A)med följande ändringar:- Argumentlistan Aär en lista med uttryck, var och en klassificerad som en variabel och med typen och modifieraren (ref,outellerin) för motsvarande funcptr_parameter_list avF.
- Kandidatmetoderna är endast de metoder som är tillämpliga i sin normala form, inte de som gäller i utökad form.
- Kandidatmetoderna är bara de metoder som är statiska.
 
- Argumentlistan 
- Om algoritmen för överbelastningsmatchning genererar ett fel uppstår ett kompileringsfel. Annars genererar algoritmen en enda bästa metod Mhar samma antal parametrar somFoch konverteringen anses finnas.
- Den valda metoden Mmåste vara kompatibel (enligt definitionen ovan) med funktionspekartypenF. Annars uppstår ett kompileringsfel.
- Resultatet av konverteringen är en funktionspekare av typen F.
Det innebär att utvecklare kan vara beroende av regler för överbelastningsmatchning för att fungera tillsammans med adressoperatorn:
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;
    }
}
Adress för operatorn implementeras med hjälp av instruktionen ldftn.
Begränsningar för den här funktionen:
- Gäller endast för metoder som har markerats som static.
- Icke-staticlokala funktioner kan inte användas i&. Implementeringsinformationen för dessa metoder anges avsiktligt inte av språket. Detta inkluderar om de är statiska jämfört med instanser eller exakt vilken signatur de genereras med.
Operatorer för funktionspekartyper
Avsnittet i osäker kod för uttryck ändras så här:
I ett osäkert sammanhang finns flera konstruktioner tillgängliga för att arbeta med alla _pointer_type_s som inte är _funcptr_type_s.
- Den
*operatorn kan användas för indirekt hantering av pekare (§23.6.2).- Operatören
->kan användas för att komma åt en medlem i en struct genom en pekare (§23.6.3).- Den
[]operatorn kan användas för att indexera en pekare (§23.6.4).- Den
&operatören kan användas för att hämta adressen till en variabel (§23.6.5).- Operatorerna
++och--kan användas för att öka och minska pekare (§23.6.6).- Operatorerna
+och-kan användas för att utföra pekararitmetik (§23.6.7).- Operatorerna
==,!=,<,>,<=och=>kan användas för att jämföra pekare (§23.6.8).- Den
stackallocoperatorn kan användas för att allokera minne i anropsstacken (§23.8).- Den
fixedinstruktionen kan användas för att tillfälligt åtgärda en variabel så att dess adress kan erhållas (§23.7).I ett osäkert sammanhang är flera konstruktioner tillgängliga för drift på alla _funcptr_type_s:
- Operatorn
&kan användas för att hämta adressen till statiska metoder (Tillåt adress för till målmetoder)- Operatorerna
==,!=,<,>,<=och=>kan användas för att jämföra pekare (§23.6.8).
Dessutom ändrar vi alla avsnitt i Pointers in expressions för att förbjuda funktionspekartyper, förutom Pointer comparison och The sizeof operator.
Bättre funktionsmedlem
§12.6.4.3 Bättre funktionsmedlem kommer att ändras för att inkludera följande rad:
En
delegate*är mer specifik änvoid*
Det innebär att det är möjligt att överbelasta void* och en delegate* och fortfarande använda adressoperatorn på ett förnuftigt sätt.
Typinferens
I osäker kod görs följande ändringar i typinferensalgoritmerna:
Indatatyper
Följande läggs till:
Om
Eär en metodgruppsadress ochTär en funktionspekartyp är alla parametertyper avTindatatyper avEmed typenT.
Utdatatyper
Följande läggs till:
Om
Eär en metodgruppsadress ochTär en typ av funktionspekare är returtypenTen utdatatyp avEmed typenT.
Slutsatsdragningar för utdatatyp
Följande punkt läggs till mellan punkterna 2 och 3:
- Om
Eär en metodgruppsadress ochTär en funktionspekartyp med parametertyperT1...Tkoch returtypTboch överlagringsmatchning avEmed typernaT1..Tkger en enda metod med returtypU, görs en lägre bindningsslutsats frånUtillTb.
Bättre konvertering från uttryck
Följande underpunkt läggs till som ett ärende i punkt 2:
Vär en funktionspekartypdelegate*<V2..Vk, V1>ochUär en funktionspekartypdelegate*<U2..Uk, U1>, och den anropande konventionen förVär identisk medU, och referensen förViär identisk medUi.
Slutsatser med nedre gräns
Följande fall läggs till i punkt 3:
Vär en funktionspekartypdelegate*<V2..Vk, V1>och det finns en funktionspekartypdelegate*<U2..Uk, U1>så attUär identisk meddelegate*<U2..Uk, U1>, och anropskonventionen förVär identisk medU, och refnessen förViär identisk medUi.
Den första inferensen från Ui till Vi ändras till:
- Om
Uinte är en funktionspekartyp ochUiinte är känd för att vara en referenstyp, eller omUär en funktionspekartyp ochUiinte är känd för att vara en funktionspekartyp eller en referenstyp, görs en exakt slutsatsdragning görs
Sedan läggs till efter den tredje punkten av slutsatsdragning från Ui till Vi:
- Om
Värdelegate*<V2..Vk, V1>beror annars slutsatsdragningen på parametern i-th idelegate*<V2..Vk, V1>:
- Om V1:
- Om returen är efter värde görs en inferens av nedre gräns.
- Om det returneras som en referens görs en exakt slutsats.
- Om V2..Vk:
- Om parametern är efter värde görs en övre gränsinferens.
- Om parametern är som referens görs en exakt slutsatsdragning.
Slutsatsdragningar med övre gräns
Följande fall läggs till i punkt 2:
Uär en funktionspekartypdelegate*<U2..Uk, U1>ochVär en typ av funktionspekare som är identisk meddelegate*<V2..Vk, V1>, och anropskonventionen förUär identisk medV, och referensen förUiär identisk medVi.
Den första inferensen från Ui till Vi ändras till:
- Om
Uinte är en funktionspekartyp ochUiinte är känd för att vara en referenstyp, eller omUär en funktionspekartyp ochUiinte är känd för att vara en funktionspekartyp eller en referenstyp, görs en exakt slutsatsdragning görs
Sedan lagt till efter den tredje punkten för slutsats från Ui till Vi:
- Om
Uärdelegate*<U2..Uk, U1>beror annars slutsatsdragningen på parametern i-th idelegate*<U2..Uk, U1>:
- Om U1:
- Om returen är efter värde görs en övre gränsinferens.
- Om det returneras som en referens görs en exakt slutsats.
- Om U2..Uk:
- Om parametern är per värde skapas en slutsats med lägre gräns.
- Om parametern är som referens görs en exakt slutsatsdragning.
Metadatarepresentation av in, outoch ref readonly parametrar och returtyper
Funktionspekarsignaturer har ingen plats för parameterflaggor, så vi måste koda om parametrar och returtypen är in, outeller ref readonly med hjälp av modreqs.
in
Vi återanvänder System.Runtime.InteropServices.InAttribute, som används som en modreq till referensspecificeraren för en parameter eller returtyp, för att betyda följande:
- Om den tillämpas på en parameter ref-specificerare behandlas den här parametern som in.
- Om den tillämpas på referensspecificeraren för returtyp behandlas returtypen som ref readonly.
out
Vi använder System.Runtime.InteropServices.OutAttribute, som används som en modreq för referensspecificeraren för en parametertyp, vilket innebär att parametern är en out parameter.
Fel
- Det är ett fel att tillämpa OutAttributesom en modreq på en returtyp.
- Det är ett fel att tillämpa både InAttributeochOutAttributesom en modreq på en parametertyp.
- Om någon av dem anges via modopt ignoreras de.
Metadatarepresentation av samtalskonventioner
Anropskonventioner kodas i en metodsignatur i metadata genom en kombination av flaggan CallKind i signaturen och noll eller fler modopti början av signaturen. ECMA-335 deklarerar för närvarande följande element i flaggan CallKind:
CallKind
   : default
   | unmanaged cdecl
   | unmanaged fastcall
   | unmanaged thiscall
   | unmanaged stdcall
   | varargs
   ;
Av dessa stöder funktionspekare i C# alla utom varargs.
Dessutom uppdateras körmiljön (och slutligen 335) för att lägga till en ny CallKind på nya plattformar. Detta har för närvarande inget formellt namn, men det här dokumentet använder unmanaged ext som platshållare för att stå för det nya utökningsbara anropskonventionformatet. Utan modopts är unmanaged ext standardanropskonventionen för plattformen, unmanaged utan hakparenteser.
Mappa calling_convention_specifier till en CallKind
En calling_convention_specifier som utelämnas, eller anges som managed, mappas till defaultCallKind. Detta är standard CallKind för alla metoder som inte tillskrivs UnmanagedCallersOnly.
C# identifierar 4 särskilda identifierare som mappar till specifika befintliga ohanterade CallKindfrån ECMA 335. För att den här mappningen ska ske måste dessa identifierare anges på egen hand, utan några andra identifierare, och det här kravet kodas i specifikationen för unmanaged_calling_conventions. Dessa identifierare är Cdecl, Thiscall, Stdcalloch Fastcall, som motsvarar unmanaged cdecl, unmanaged thiscall, unmanaged stdcallrespektive unmanaged fastcall. Om fler än en identifer har angetts, eller om den enda identifier inte är av de särskilt identifierade identifierarna, utför vi ett särskilt namnsökning på identifieraren med följande regler:
- Vi förbereder identifiermed strängenCallConv
- Vi tittar bara på typer som definierats i System.Runtime.CompilerServicesnamnrymd.
- Vi tittar bara på typer som definierats i programmets kärnbibliotek, vilket är biblioteket som definierar System.Objectoch inte har några beroenden.
- Vi tittar bara på offentliga typer.
Om sökningen lyckas på alla identifiersom anges i en unmanaged_calling_conventionkodar vi CallKind som unmanaged extoch kodar var och en av de lösta typerna i uppsättningen med modopts i början av funktionspekarsignaturen. Observera att dessa regler innebär att användarna inte kan prefix dessa identifiers med CallConv, eftersom det resulterar i att leta upp CallConvCallConvVectorCall.
När vi tolkar metadata tittar vi först på CallKind. Om det är något annat än unmanaged extignorerar vi alla modoptpå returtypen för att fastställa anropskonventionen och använder endast CallKind. Om CallKind är unmanaged exttittar vi på modopterna i början av funktionspekartypen och bildar en union av alla typer som uppfyller följande krav:
- Definieras i kärnbiblioteket, vilket är biblioteket som inte refererar till några andra bibliotek och definierar System.Object.
- Typen definieras i namnområdet System.Runtime.CompilerServices.
- Typen börjar med prefixet CallConv.
- Typen är offentlig.
Dessa representerar de typer som måste hittas när du utför sökning på identifiers i en unmanaged_calling_convention när du definierar en funktionspekartyp i källan.
Det är ett fel att försöka använda en funktionspekare med en CallKind av unmanaged ext om målmiljön inte stöder egenskapen. Detta bestäms genom att söka efter förekomsten av System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind konstant. Om den här konstanten finns anses körmiljön stödja funktionen.
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
              System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute är ett attribut som används av CLR för att ange att en metod ska anropas med en specifik anropskonvention. Därför introducerar vi följande stöd för att arbeta med attributet:
- Det är ett fel att direkt anropa en metod som kommenterats med det här attributet från C#. Användarna måste hämta en funktionspekare till metoden och sedan anropa den pekaren.
- Det är ett fel att tillämpa attributet på något annat än en vanlig statisk metod eller vanlig statisk lokal funktion. C#-kompilatorn markerar alla icke-statiska eller statiska icke-vanliga metoder som importerats från metadata med det här attributet som inte stöds av språket.
- Det är ett fel om en metod är markerad med attributet och har en parameter eller returtyp som inte är en unmanaged_type.
- Det är ett fel för en metod som har markerats med attributet för att ha typparametrar, även om dessa typparametrar är begränsade till unmanaged.
- Det är ett fel för en metod i en generisk typ att markeras med attributet.
- Det är ett fel att konvertera en metod som markerats med attributet till en delegerad typ.
- Det är ett fel att ange några typer för UnmanagedCallersOnly.CallConvssom inte uppfyller kraven för att anropa konventionenmodopts i metadata.
När du fastställer anropskonventionen för en metod som har markerats med ett giltigt UnmanagedCallersOnly attribut, utför kompilatorn följande kontroller av de typer som anges i egenskapen CallConvs för att fastställa den effektiva CallKind och modoptsom ska användas för att fastställa anropskonventionen:
- Om inga typer anges behandlas CallKindsomunmanaged ext, utan anropskonventionmodopts i början av funktionspekartypen.
- Om det finns en angiven typ och den typen heter CallConvCdecl,CallConvThiscall,CallConvStdcallellerCallConvFastcallbehandlasCallKindsomunmanaged cdecl,unmanaged thiscall,unmanaged stdcallrespektiveunmanaged fastcall, utan anropskonventionmodopts i början av funktionspekartypen.
- Om flera typer anges eller om den enskilda typen inte heter någon av de särskilt kallade typerna ovan behandlas CallKindsomunmanaged ext, med union av de angivna typerna som behandlas sommodopts i början av funktionspekartypen.
Kompilatorn tittar sedan på den här effektiva CallKind och modopt samling och använder normala metadataregler för att fastställa den sista anropande konventionen för funktionspekartypen.
Öppna frågor
Identifiera körningsstöd för unmanaged ext
              https://github.com/dotnet/runtime/issues/38135 spårar hur du lägger till den här flaggan. Beroende på feedbacken från granskningen använder vi antingen egenskapen som anges i problemet, eller använder vi närvaron av UnmanagedCallersOnlyAttribute som den flagga som avgör om körtiderna stöder unmanaged ext.
Överväganden
Tillåt instansmetoder
Förslaget kan utökas för att stödja instansmetoder genom att dra nytta av EXPLICITTHIS CLI-anropskonvention (med namnet instance i C#-kod). Den här formen av CLI-funktionspekare placerar parametern this som en explicit första parameter i syntaxen för funktionspekaren.
unsafe class Instance {
    void Use() {
        delegate* instance<Instance, string> f = &ToString;
        f(this);
    }
}
Detta är bra men ger en viss komplikation till förslaget. Särskilt eftersom funktionspekare som skiljer sig från anropskonventionen instance och managed skulle vara inkompatibla även om båda fallen används för att anropa hanterade metoder med samma C#-signatur. I alla fall där detta skulle vara värdefullt att ha, fanns det en enkel lösning: använd en lokal funktion med static.
unsafe class Instance {
    void Use() {
        static string toString(Instance i) => i.ToString();
        delegate*<Instance, string> f = &toString;
        f(this);
    }
}
Kräv inte osäker vid deklaration
I stället för att kräva unsafe vid varje användning av en delegate*behöver du bara den vid den tidpunkt då en metodgrupp konverteras till en delegate*. Det är här de viktigaste säkerhetsfrågorna spelar in (med vetskapen om att den innehållande modulen inte kan avlägsnas medan värdet bibehålls). Att kräva unsafe på de andra platserna kan ses som överdrivet.
Så här var designen ursprungligen avsedd. Men de resulterande språkreglerna kändes mycket besvärliga. Det är omöjligt att dölja det faktum att detta är ett pekarvärde och det fortsatte att synas även utan nyckelordet unsafe. Konverteringen till object kan till exempel inte tillåtas, den kan inte vara medlem i en classosv. ... C#-designen är att kräva unsafe för alla pekare och därför följer den här designen det.
Utvecklare kommer fortfarande att kunna presentera en säker wrapper ovanpå delegate* värden på samma sätt som de gör för vanliga pekartyper idag. Överväga:
unsafe struct Action {
    delegate*<void> _ptr;
    Action(delegate*<void> ptr) => _ptr = ptr;
    public void Invoke() => _ptr();
}
Använda ombud
I stället för att använda ett nytt syntaxelement delegate*använder du bara befintliga delegate typer med en * som följer typen:
Func<object, object, bool>* ptr = &object.ReferenceEquals;
Hantering av anropskonvention kan göras genom att kommentera de delegate typerna med ett attribut som anger ett CallingConvention värde. Avsaknaden av ett attribut skulle innebära en hanterad anropskonvention.
Det är problematiskt att koda detta i IL. Det underliggande värdet måste representeras som en pekare, men det måste också:
- Ha en unik typ för att tillåta överbelastningar med olika funktionspekartyper.
- Vara ekvivalent för OHI-ändamål över sammansättningsgränser.
Den sista punkten är särskilt problematisk. Det innebär att varje assembly som använder Func<int>* måste koda en motsvarande typ i metadata, även om Func<int>* definieras i en assembly som de inte kontrollerar.
Dessutom måste alla andra typer som definieras med namnet System.Func<T> i en sammansättning som inte är mscorlib skilja sig från den version som definierats i mscorlib.
Ett alternativ som utforskades var att sända ut en sådan pekare som mod_req(Func<int>) void*. Detta fungerar dock inte som en mod_req inte kan binda till en TypeSpec och kan därför inte rikta in sig på generiska instansieringar.
Namngivna funktionspekare
Syntaxen för funktionspekaren kan vara besvärlig, särskilt i komplexa fall som kapslade funktionspekare. I stället för att låta utvecklare skriva ut signaturen varje gång skulle språket kunna tillåta namngivna deklarationer av funktionspekare som görs med delegate.
func* void Action();
unsafe class NamedExample {
    void M(Action a) {
        a();
    }
}
En del av problemet här är att den underliggande CLI-primitiven inte har namn, därför skulle detta vara en ren C#-uppfinning och kräva lite metadataarbete för att aktivera. Det är genomförbart men handlar mycket om arbete. Det kräver i princip att C# har en följeslagare till tabellen type def enbart för dessa namn.
När argumenten för namngivna funktionspekare undersöktes upptäckte vi också att de kunde tillämpas lika bra på ett antal andra scenarier. Det skulle till exempel vara lika praktiskt att deklarera namngivna tupplar för att minska behovet av att skriva ut den fullständiga signaturen i alla fall.
(int x, int y) Point;
class NamedTupleExample {
    void M(Point p) {
        Console.WriteLine(p.x);
    }
}
Efter diskussionen beslutade vi att inte tillåta namngiven deklaration av delegate* typer. Om vi upptäcker att det finns ett betydande behov av detta baserat på feedback från kundanvändning kommer vi att undersöka en namngivningslösning som fungerar för funktionspekare, tupplar, generiska objekt etcetera. Detta kommer sannolikt att likna andra förslag i form, såsom fullständigt stöd för typedef i språket.
Framtida överväganden
statiska delegater
Detta avser förslaget att tillåta deklaration av delegate typer som endast får referera till static medlemmar. Fördelen är att sådana delegate instanser kan vara allokeringsfria och bättre i prestandakänsliga scenarier.
Om funktionen för funktionspekare implementeras kommer static delegate-förslaget sannolikt att avslutas. Den föreslagna fördelen med den funktionen är dess allokeringsfria natur. Nyligen genomförda undersökningar har dock visat att det inte är möjligt att uppnå på grund av monterings lossning. Det måste finnas ett starkt handtag från static delegate till den metod som den refererar till för att hålla sammansättningen från att lossas under den.
För att bevara varje static delegate-instans skulle det krävas att allokera ett nytt handtagsvärde, vilket strider mot de mål som föreslås i förslaget. Det fanns vissa designer där allokeringen kunde fördelas ut till en enda allokering per anropsställe, men det var lite komplext och verkade inte värt kompromissen.
Det innebär att utvecklare i princip måste välja mellan följande kompromisser:
- Säkerhet vid monteringsavlastning: detta kräver resurstilldelningar och därför är delegateredan ett tillräckligt bra alternativ.
- Ingen säkerhet vid monterings lossning: använd en delegate*. Detta kan omslutas i enstructför att tillåta användning utanför enunsafekontext i resten av koden.
C# feature specifications