Dela via


ref readonly parametrar

Obs

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 noteras i de relevanta anteckningarna från LDM (Language Design Meeting) .

Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Champion-utgåva: https://github.com/dotnet/csharplang/issues/6010

Sammanfattning

Tillåt parameterdeklarationsplatsmodifikator ref readonly och ändra anropsregler enligt följande:

Anropsplatsanteckning ref parameter ref readonly parameter in parameter out parameter
ref Tillåten Tillåten Varning Fel
in Fel Tillåten Tillåten Fel
out Fel Fel Fel Tillåten
Ingen anteckning Fel Varning Tillåten Fel

(Observera att det finns en ändring av befintliga regler: in parameter med ref anropsobjektanteckning ger en varning i stället för ett fel.)

Ändra argumentvärderegler på följande sätt:

Värdetyp ref parameter ref readonly-parametrar in-parametern out parameter
rvalue Fel Varning Tillåten Fel
Lvalue Tillåten Tillåten Tillåten Tillåten

Där lvalue betyder en variabel (dvs. ett värde med en plats, inte behöver skrivas/tilldelas) och rvalue innebär någon typ av värde.

Motivation

C# 7.2 introducerade in parametrar som ett sätt att skicka skrivskyddade referenser. in parametrar tillåter både lvalues och rvalues och kan användas utan någon anteckning på anropsplatsen. API:er som samlar in eller returnerar referenser från sina parametrar vill dock inte tillåta rvalues och även framtvinga en viss indikation på anropsplatsen om att en referens registreras. ref readonly-parametrar är perfekta i sådana fall eftersom de varnar när de används med r-värden eller utan någon annotering på anropsplatsen.

Dessutom finns det API:er som bara behöver skrivskyddade referenser men som använder

  • ref parametrar eftersom de introducerades innan in blev tillgängliga och att ändra till in skulle vara en käll- och binär icke-bakåtkompatibel ändring, t.ex. QueryInterface, eller
  • in parametrar för att acceptera endast-läs referenser även om det inte är särskilt logiskt att överföra rvalues till dem, t.ex. ReadOnlySpan<T>..ctor(in T value), eller
  • ref parametrar för att inte tillåta r-värden även om de inte muterar den skickade referensen, t.ex. Unsafe.IsNullRef.

Dessa API:er kan migreras till ref readonly parametrar utan att det påverkar användarna. Mer information om binär kompatibilitet finns i den föreslagna metadatakodning. Mer specifikt, ändring

  • refref readonly skulle bara vara en brytande binär förändring för virtuella metoder,
  • refin skulle också vara en binär ändring som bryter bakåtkompatibiliteten för virtuella metoder, men inte en ändring som bryter källkodens bakåtkompatibilitet (eftersom reglerna ändras för att endast varna för ref argument som skickas till in parametrar),
  • inref readonly skulle inte bryta kompatibiliteten (men ingen anropsplatsannotation eller rvalue skulle resultera i en varning),
    • Observera att detta skulle vara en källbrytande ändring för användare som använder äldre kompilatorversioner (eftersom de tolkar ref readonly parametrar som ref parametrar, tillåter inte in eller ingen anteckning på anropsplatsen) och nya kompilatorversioner med LangVersion <= 11 (för konsekvens med äldre kompilatorversioner genereras ett fel som ref readonly parametrar inte stöds om inte motsvarande argument skickas med ref modifieraren).

I motsatt riktning, ändra

  • ref readonlyref skulle potentiellt vara en källkodsbrytande ändring (såvida inte endast ref-anropsobjektanteckningen användes och bara skrivskyddade referenser användes som argument) och en binärbrytande ändring för virtuella metoder.
  • ref readonlyin skulle inte vara en brytande förändring (men ref anropsplatsanteckning skulle resultera i en varning).

Observera att reglerna som beskrivs ovan gäller för metodsignaturer, men inte för att delegera signaturer. Att till exempel ändra ref till in i en ombudssignatur kan vara en källbrytande ändring (om en användare tilldelar en metod med ref parametern till den delegerade typen skulle det bli ett fel efter API-ändringen).

Detaljerad design

I allmänhet är reglerna för ref readonly parametrar desamma som de som anges för in parametrar i deras förslag, förutom när det uttryckligen har ändrats i det här förslaget.

Parameterdeklarationer

Det krävs inga ändringar i grammatiken. Modifieraren ref readonly tillåts för parametrar. Förutom normala metoder tillåts ref readonly för indexeringsparametrar (som in men till skillnad från ref), men tillåts inte för operatorparametrar (som ref men till skillnad från in).

Standardparametervärden tillåts för ref readonly parametrar med en varning eftersom de motsvarar att skicka rvalues. Detta gör att API-författare kan ändra in parametrar med standardvärden till ref readonly parametrar utan att införa en kompatibilitetsbrytande ändring.

Kontroller av värdetyp

Observera att även om ref argumentmodifierare tillåts för ref readonly parametrar, ändras inget w.r.t. värdetypkontroller, d.v.s.

  • ref kan endast användas med tilldelningsbara värden.
  • för att skicka skrivskyddade referenser måste man i stället använda argumentmodifieraren in.
  • för att klara rvalues måste man inte använda någon modifierare (vilket resulterar i en varning för ref readonly parametrar enligt beskrivningen i sammanfattningen av detta förslag).

Överbelastningsupplösning

Med överlagringsmatchning kan ref/ref readonly/in/no callsite-anteckningar och parametermodifierare blandas enligt tabellen i sammanfattningen av det här förslaget, d.v.s. alla tillåtna och varning fall kommer att betraktas som möjliga kandidater vid överbelastningsmatchning. Mer specifikt finns det en ändring i befintligt beteende där metoder med in-parametern matchar anrop med motsvarande argument markerat som ref– den här ändringen kommer att styras av LangVersion.

Dock ignoreras varningen för att skicka ett argument utan anropsplatsmodifierare till en ref readonly-parameter om parametern är

  • mottagaren i ett tilläggsmetodanrop,
  • används implicit som del av anpassad insamlingsinitierare eller interpolerad stränghanterare.

Överlagringar efter värde föredras framför ref readonly överlagringar om det inte finns någon argumentmodifierare (in parametrar har samma beteende).

Metodkonverteringar

För anonym funktion [§10.7] och metodgruppen [§10.8] anses dessa modifierare vara kompatibla (men alla tillåtna konverteringar mellan olika modifierare resulterar i en varning):

  • ref readonly parametern för målmetoden tillåts matcha in eller ref parametern för ombudet,
  • in-parametern för målmetoden tillåts matcha ref readonly eller, beroende på LangVersion, ref-parametern för delegeringen.
  • Obs! ref parametern för målmetoden inte tillåts matcha in eller ref readonly parameter för ombudet.

Till exempel:

DIn dIn = (ref int p) => { }; // error: cannot match `ref` to `in`
DRef dRef = (in int p) => { }; // warning: mismatch between `in` and `ref`
DRR dRR = (ref int p) => { }; // error: cannot match `ref` to `ref readonly`
dRR = (in int p) => { }; // warning: mismatch between `in` and `ref readonly`
dIn = (ref readonly int p) => { }; // warning: mismatch between `ref readonly` and `in`
dRef = (ref readonly int p) => { }; // warning: mismatch between `ref readonly` and `ref`
delegate void DIn(in int p);
delegate void DRef(ref int p);
delegate void DRR(ref readonly int p);

Observera att funktionspekarkonverteringarnas beteende inte ändras. Som en påminnelse tillåts inte implicita funktionpekaromvandlingar om det finns en diskrepans mellan referenstypmodifierare, och explicita kastningar är alltid tillåtna utan några varningar.

Signaturmatchning

Medlemmar som deklareras i en enda typ kan inte skilja sig åt i signaturen enbart av ref/out/in/ref readonly. För andra syften med signaturmatchning (t.ex. döljande eller åsidosättande) kan ref readonly ersättas med in modifierare, men det resulterar i en varning på deklarationsplatsen [§7.6]. Detta gäller inte vid matchning av partial-deklaration med dess implementering och vid matchning av interceptor-signatur med avlyssnad signatur. Observera att det inte finns någon ändring i åsidosättning av ref/in och ref readonly/ref modifierarpar. De kan inte bytas ut, eftersom signaturerna inte är binärkompatibla. För att vara konsekvent gäller samma sak för andra signaturmatchningsändamål (t.ex. gömning).

Metadatakodning

Som en påminnelse

  • ref parametrar genereras som vanliga byref-typer (T& i IL),
  • in parametrar är som ref plus att de kommenteras med System.Runtime.CompilerServices.IsReadOnlyAttribute. I C# 7.3 och senare genereras de också med [in] och modreq(System.Runtime.InteropServices.InAttribute)om de är virtuella.

ref readonly parametrar kommer att utges som [in] T&, plus att de noteras med följande attribut:

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public sealed class RequiresLocationAttribute : Attribute
    {
    }
}

Om de är virtuella genereras de dessutom med modreq(System.Runtime.InteropServices.InAttribute) för att säkerställa binär kompatibilitet med in parametrar. Observera att till skillnad från in parametrar genereras inga [IsReadOnly] för ref readonly parametrar för att undvika att öka metadatastorleken och även för att göra så att äldre kompilatorversioner tolkar ref readonly parametrar som ref parametrar (och därför kommer refref readonly inte att vara en källbrytande ändring även mellan olika kompilatorversioner).

RequiresLocationAttribute matchas med namnområdeskvalificerat namn och syntetiseras av kompilatorn om den inte redan ingår i kompilering.

Att ange attributet i källan är ett fel om det tillämpas på en parameter, på samma sätt som ParamArrayAttribute.

Funktionspekare

I funktionspekare genereras in parametrar med modreq(System.Runtime.InteropServices.InAttribute) (se förslag på funktionspekare). ref readonly parametrar kommer att utsändas utan modreq, men istället med modopt(System.Runtime.CompilerServices.RequiresLocationAttribute). Äldre kompilatorversioner ignorerar modopt och tolkar därför ref readonly parametrar som ref parametrar (överensstämmer med äldre kompilatorbeteende för normala metoder med ref readonly parametrar enligt beskrivningen ovan) och nya kompilatorversioner som är medvetna om modopt använder den för att identifiera ref readonly parametrar för att generera varningar under konverteringar och anrop. För konsekvens med äldre kompilatorversioner rapporterar nya kompilatorversioner med LangVersion <= 11 fel för att ref readonly-parametrar inte stöds om inte motsvarande argument skickas med ref-modifieraren.

Observera att det är en binär paus att ändra modifierare i funktionspekarsignaturer om de är en del av offentliga API:er, vilket innebär att det blir en binär paus när du ändrar ref eller in till ref readonly. En källbrytning sker dock bara för anropare med LangVersion <= 11 vid ändring av in till ref readonly (om pekaren anropas med in anropsmodifierare), i enlighet med normala metoder.

Icke-bakåtkompatibla ändringar

Den ref/in matchningsfelsavslappningen i överbelastningsmatchningen introducerar en beteendebrytande ändring som visas i följande exempel:

class C
{
    string M(in int i) => "C";
    static void Main()
    {
        int i = 5;
        System.Console.Write(new C().M(ref i));
    }
}
static class E
{
    public static string M(this C c, ref int i) => "E";
}

I C# 11 binder anropet till E.M, och därför skrivs "E" ut. I C# 12 tillåts C.M att binda (med en varning) och inga tilläggsomfång genomsöks eftersom vi har en tillämplig kandidat, och därför skrivs "C" ut.

Det finns också en källbrytande ändring på grund av samma orsak. Exemplet nedan skriver ut "1" i C# 11, men kan inte kompileras med ett tvetydighetsfel i C# 12:

var i = 5;
System.Console.Write(C.M(null, ref i));

interface I1 { }
interface I2 { }
static class C
{
    public static string M(I1 o, ref int x) => "1";
    public static string M(I2 o, in int x) => "2";
}

Exemplen ovan visar avbrott för metodanrop, men eftersom de orsakas av ändringar i överbelastningsmatchningen kan de utlösas på samma sätt för metodkonverteringar.

Alternativ

parameterdeklarationer

API-författare kan kommentera in parametrar som är utformade för att endast acceptera lvalues med ett anpassat attribut och tillhandahålla en analysator för att flagga felaktiga användningar. Detta skulle inte tillåta API-författare att ändra signaturer för befintliga API:er som valde att använda ref parametrar för att neka rvalues. Anropare av sådana API:er skulle fortfarande behöva utföra extra arbete för att få en ref om de bara har åtkomst till en ref readonly variabel. Att ändra dessa API:er från ref till [RequiresLocation] in skulle vara en källbrytande ändring (och i fallet med virtuella metoder, också en binär brytande ändring).

I stället för att tillåta modifieraren ref readonlykan kompilatorn identifiera när ett särskilt attribut (som [RequiresLocation]) tillämpas på en parameter. Detta diskuterades i LDM 2022-04-25, och beslutade att detta är en språkfunktion, inte en analysator, så det bör se ut som en.

Kontroll av värdeslag

Att skicka lvalues utan några modifierare till ref readonly parametrar kan tillåtas utan några varningar, på samma sätt som C++:s implicita byref-parametrar. Detta diskuterades i LDM 2022-05-11, och noterade att den primära motivationen för ref readonly parametrar är API:er som samlar in eller returnerar referenser från dessa parametrar, så markör av något slag är bra.

Att skicka rvalue till en ref readonly kan vara ett fel, inte en varning. Det accepterades ursprungligen i LDM 2022-04-25, men senare e-postdiskussioner lättade på detta eftersom vi skulle förlora möjligheten att ändra befintliga API:er utan att bryta användarna.

in kan vara en "naturlig" anropsmodifierare för ref readonly parametrar och användning av ref kan resultera i varningar. Detta skulle säkerställa ett konsekvent kodformat och göra det uppenbart på anropsplatsen att referensen är skrivskyddad (till skillnad från ref). Det accepterades ursprungligen i LDM 2022-04-25. Varningar kan dock vara en friktionspunkt för API-författare att gå från ref till ref readonly. Dessutom har in omdefinierats som ref readonly + bekvämlighetsfunktioner, därför avvisades detta i LDM 2022-05-11.

Väntande LDM-granskning

Inget av följande alternativ implementerades i C# 12. De är fortfarande potentiella förslag.

parameterdeklarationer

Omvänd ordning av modifierare (readonly ref i stället för ref readonly) kan tillåtas. Detta skulle vara oförenligt med hur readonly ref returnerar och fält beter sig (omvänd ordning tillåts inte eller innebär något annat) och kan kollidera med skrivskyddade parametrar om det implementeras i framtiden.

Standardparametervärden kan vara ett fel för ref readonly parametrar.

Typ av värde kontroller

Fel kan utlösas istället för varningar när "rvalues" skickas till ref readonly-parametrar eller vid inkorrekt matchning av anropsanteckningar och parametermodifierare. På samma sätt kan särskilda modreq användas i stället för ett attribut för att säkerställa att ref readonly parametrar skiljer sig från in parametrar på binär nivå. Detta skulle ge starkare garantier, så det skulle vara bra för nya API:er, men förhindra implementering i befintliga körnings-API:er som inte kan införa icke-bakåtkompatibla ändringar.

Kontroller av värdeslag kan bli mindre strikta för att tillåta att skrivskyddade referenser skickas via ref till in/ref readonly parametrar. Det skulle likna hur referenstilldelningar och referensreturer fungerar i dag – de tillåter även att referenser skickas skrivskyddat via ref-modifieraren i källuttrycket. Men ref där finns vanligtvis nära platsen där målet deklareras som ref readonly, så det är tydligt att vi skickar en referens som readonly, till skillnad från anrop där argument- och parametermodifierare vanligtvis befinner sig långt ifrån varandra. Dessutom tillåter de endastref modifierare till skillnad från argument som också tillåter in, vilket innebär att in och ref skulle bli utbytbara för argument, eller in skulle bli praktiskt taget föråldrad om användarna ville göra sin kod konsekvent (de skulle förmodligen använda ref överallt eftersom det är den enda modifieraren som tillåts för referenstilldelningar och ref-returer).

överbelastningsupplösning

Överbelastningslösning, åsidosättning och konvertering kan förhindra utbytbarhet av ref readonly och in modifikatorer.

Ändringen av överbelastningslösningen för befintliga in-parametrar kan göras utan villkor (utan hänsyn till LangVersion), men det skulle innebära en brytande förändring.

Om du anropar en tilläggsmetod med ref readonly mottagare kan det leda till varning om att "Argument 1 ska skickas med ref eller in nyckelord" som skulle inträffa för icke-tilläggsanrop utan anropsobjektmodifierare (användaren kan åtgärda en sådan varning genom att omvandla tilläggsmetodens anrop till anrop av statiska metoder). Samma varning kan rapporteras när du använder anpassad insamlingsinitierare eller interpolerad stränghanterare med ref readonly parameter, även om användaren inte kunde kringgå den.

ref readonly överlagringar kan föredras framför överlagringar efter värde när det inte finns någon anropswebbplatsmodifierare eller om det kan uppstå ett tvetydighetsfel.

Metodkonverteringar

Vi kan tillåta att ref parametern för målmetoden matchar in och ref readonly parametern för ombudet. Detta skulle göra det möjligt för API-författare att ändra till exempel ref till in i delegatsignaturer utan att bryta sina användare (konsekvent med vad som är tillåtet för normala metodsignaturer). Men det skulle också leda till följande överträdelse av readonly-garantier, endast med en varning:

class Program
{
    static readonly int f = 123;
    static void Main()
    {
        var d = (in int x) => { };
        d = (ref int x) => { x = 42; }; // warning: mismatch between `ref` and `in`
        d(f); // changes value of `f` even though it is `readonly`!
        System.Console.WriteLine(f); // prints 42
    }
}

Funktionspekarkonverteringar kan varna för ref readonly/ref/in skiljaktigheter, men om vi vill begränsa det i LangVersion, skulle en betydande investering i implementering krävas eftersom typkonverteringar idag inte behöver tillgång till kompilering. Även om matchningsfel för närvarande är ett fel är det dessutom enkelt för användarna att lägga till en cast för att tillåta matchningsfelet om de vill.

metadatakodning

Det kan vara tillåtet att ange RequiresLocationAttribute i källan, på samma sätt som In och Out attribut. Alternativt kan det vara ett fel när det används i andra kontexter än bara parametrar, på samma sätt som IsReadOnly attribut. för att bevara ytterligare designutrymme.

Funktionspekarens ref readonly parametrar kan genereras med olika modopt/modreq kombinationer (observera att "source break" i den här tabellen avser anropare med LangVersion <= 11):

Modifierare Kan identifieras i olika kompileringar Gamla kompilatorer ser dem som refref readonly inref readonly
modreq(In) modopt(RequiresLocation) Ja in binär, källbrytning binärt avbrott
modreq(In) Nej in binär, källbrytning Okej
modreq(RequiresLocation) Ja Ej stöds binär, källbrytning binär, källbrytning
modopt(RequiresLocation) Ja ref binärt avbrott binär, källbrytning

Vi kan generera både [RequiresLocation]- och [IsReadOnly]-attribut för ref readonly parametrar. Då skulle inref readonly inte vara en bakåtkompatibilitetsbrytande ändring även för äldre kompilatorversioner, men refref readonly skulle bli en källkodsinkompatibel ändring för äldre kompilatorversioner (eftersom de skulle tolka ref readonly som inoch inte tillåta ref-modifierare) samt för nya kompilatorversioner med LangVersion <= 11 (för att säkerställa konsekvens).

Vi kan göra beteendet för LangVersion <= 11 annorlunda än beteendet för äldre kompilatorversioner. Det kan till exempel vara ett fel när en ref readonly parameter anropas (även när du använder ref-modifieraren på anropsplatsen), eller så kan den alltid tillåtas utan fel.

Oförenliga ändringar

Det här förslaget föreslår att acceptera en ändring som bryter mot befintligt beteende eftersom det bör vara sällsynt att stöta på, är gated av LangVersion och användare kan kringgå det genom att anropa tilläggsmetoden direkt. I stället kan vi mildra det genom att

  • att inte tillåta ref/in matchningsfel (det skulle bara förhindra migrering till in för gamla API:er som använde ref eftersom in inte var tillgängligt ännu),
  • Ändra reglerna för överbelastningsupplösning för att fortsätta leta efter en bättre matchning (bestäms av bättrehetsregler som anges nedan) när det finns en ref-typmissanpassning som införts i det här förslaget.
    • eller så kan du alternativt fortsätta endast för ref mot in mismatch, inte de andra mismatcharna (ref readonly mot ref/in/by-value).
Förbättring styr

Följande exempel resulterar för närvarande i tre tvetydighetsfel för de tre anropen av M. Vi skulle kunna lägga till nya bättre regler för att lösa tvetydigheterna. Detta skulle också lösa den källbrytande ändring som beskrevs tidigare. Ett sätt är att göra ett exempelutskrift 221 (där ref readonly-parametern matchas med ett in-argument eftersom det skulle vara en varning att anropa det utan modifierare, å andra sidan är det tillåtet för in-parametern).

interface I1 { }
interface I2 { }
class C
{
    static string M(I1 o, in int i) => "1";
    static string M(I2 o, ref readonly int i) => "2";
    static void Main()
    {
        int i = 5;
        System.Console.Write(M(null, ref i));
        System.Console.Write(M(null, in i));
        System.Console.Write(M(null, i));
    }
}

Nya bättre regler kan markera som värre parametern vars argument kunde ha skickats med en annan argumentmodifierare för att göra det bättre. Med andra ord bör användaren alltid kunna omvandla en sämre parameter till en bättre parameter genom att ändra motsvarande argumentmodifierare. När ett argument till exempel skickas av inprioriteras en ref readonly parameter framför en in parameter eftersom användaren kan skicka argumentets värde för att välja parametern in. Den här regeln är bara en utvidgning av by-value/in preferensregeln som gäller idag (det är den sista regeln i överbelastningsupplösningen och hela överbelastningen är bättre om någon av dess parametrar är bättre och ingen är sämre än motsvarande parameter i en annan överbelastning).

argument bättre parameter sämre parameter
ref/in ref readonly in
ref ref ref readonly/in
efter värde efter värde/in ref readonly
in in ref

Vi bör hantera metodkonverteringar på liknande sätt. Följande exempel resulterar för närvarande i två tvetydighetsfel för de två delegerade tilldelningarna. Nya bättre regler skulle kunna föredra en metodparameter vars refnessmodifierare matchar motsvarande referensmodifierare för måldelegatparametern framför en som har ett matchningsfel. Därför skulle följande exempel skriva ut 12.

class C
{
    void M(I1 o, ref readonly int x) => System.Console.Write("1");
    void M(I2 o, ref int x) => System.Console.Write("2");
    void Run()
    {
        D1 m1 = this.M;
        D2 m2 = this.M; // currently ambiguous

        var i = 5;
        m1(null, in i);
        m2(null, ref i);
    }
    static void Main() => new C().Run();
}
interface I1 { }
interface I2 { }
class X : I1, I2 { }
delegate void D1(X s, ref readonly int x);
delegate void D2(X s, ref int x);

Designa möten