Byrefs
F# har två viktiga funktionsområden som handlar om lågnivåprogrammering:
- Typerna
byref
//inref
outref
, som är hanterade pekare. De har begränsningar för användning så att du inte kan kompilera ett program som är ogiltigt vid körning. - En
byref
-like struct, som är en struct som har liknande semantik och samma kompileringstidsbegränsningar sombyref<'T>
. Ett exempel är Span<T>.
Syntax
// Byref types as parameters
let f (x: byref<'T>) = ()
let g (x: inref<'T>) = ()
let h (x: outref<'T>) = ()
// Calling a function with a byref parameter
let mutable x = 3
f &x
// Declaring a byref-like struct
open System.Runtime.CompilerServices
[<Struct; IsByRefLike>]
type S(count1: int, count2: int) =
member x.Count1 = count1
member x.Count2 = count2
Byref, inref och outref
Det finns tre former av byref
:
inref<'T>
, en hanterad pekare för att läsa det underliggande värdet.outref<'T>
, en hanterad pekare för att skriva till det underliggande värdet.byref<'T>
, en hanterad pekare för att läsa och skriva det underliggande värdet.
En byref<'T>
kan skickas där en inref<'T>
förväntas. På samma sätt kan en byref<'T>
skickas där en outref<'T>
förväntas.
Använda byrefs
Om du vill använda måste inref<'T>
du hämta ett pekarvärde med &
:
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
Om du vill skriva till pekaren med hjälp av en outref<'T>
eller byref<'T>
måste du också göra det värde som du hämtar en pekare till mutable
.
open System
let f (dt: byref<DateTime>) =
printfn $"Now: %O{dt}"
dt <- DateTime.Now
// Make 'dt' mutable
let mutable dt = DateTime.Now
// Now you can pass the pointer to 'dt'
f &dt
Om du bara skriver pekaren i stället för att läsa den bör du överväga att använda outref<'T>
i stället byref<'T>
för .
Inref-semantik
Ta följande kod som exempel:
let f (x: inref<SomeStruct>) = x.SomeField
Semantiskt innebär detta följande:
- Pekarens
x
innehavare får bara använda den för att läsa värdet. - Alla pekare som hämtas till
struct
fält som är kapslade inomSomeStruct
är av typeninref<_>
.
Följande är också sant:
- Det finns ingen antydning om att andra trådar eller alias inte har skrivåtkomst till
x
. - Det finns ingen implikation som
SomeStruct
är oföränderlig på grund avx
att vara eninref
.
För F#-värdetyper som är oföränderliga härleds pekaren this
dock till en inref
.
Alla dessa regler innebär tillsammans att pekarens inref
innehavare inte får ändra det omedelbara innehållet i det minne som pekas på.
Outref-semantik
Syftet med outref<'T>
är att ange att pekaren endast ska skrivas till. Oväntat tillåter outref<'T>
läsning av det underliggande värdet trots dess namn. Detta är i kompatibilitetssyfte.
Semantiskt outref<'T>
sett skiljer sig inte från byref<'T>
, förutom en skillnad: metoder med outref<'T>
parametrar konstrueras implicit till en tuppelns returtyp, precis som när du anropar en metod med en [<Out>]
parameter.
type C =
static member M1(x, y: _ outref) =
y <- x
true
match C.M1 1 with
| true, 1 -> printfn "Expected" // Fine with outref, error with byref
| _ -> printfn "Never matched"
Interop med C#
C# stöder nyckelorden in ref
och out ref
förutom ref
returer. Följande tabell visar hur F# tolkar vad C# genererar:
C#-konstruktion | F#-slutsatsdragningar |
---|---|
ref returvärde |
outref<'T> |
ref readonly returvärde |
inref<'T> |
in ref Parametern |
inref<'T> |
out ref Parametern |
outref<'T> |
Följande tabell visar vad F# genererar:
F#-konstruktion | Genererad konstruktion |
---|---|
inref<'T> -argument |
[In] attributet på argumentet |
inref<'T> Återvända |
modreq attribut för värde |
inref<'T> i abstrakt fack eller implementering |
modreq på argument eller retur |
outref<'T> -argument |
[Out] attributet på argumentet |
Skriv slutsatsdragning och överlagringsregler
En inref<'T>
typ härleds av F#-kompilatorn i följande fall:
- En .NET-parameter eller returtyp som har ett
IsReadOnly
attribut. - Pekaren
this
på en structtyp som inte har några föränderliga fält. - Adressen till en minnesplats som härleds från en annan
inref<_>
pekare.
När en implicit adress för en inref
tas, föredras en överlagring med ett argument av typen SomeType
till en överlagring med ett argument av typen inref<SomeType>
. Till exempel:
type C() =
static member M(x: System.DateTime) = x.AddDays(1.0)
static member M(x: inref<System.DateTime>) = x.AddDays(2.0)
static member M2(x: System.DateTime, y: int) = x.AddDays(1.0)
static member M2(x: inref<System.DateTime>, y: int) = x.AddDays(2.0)
let res = System.DateTime.Now
let v = C.M(res)
let v2 = C.M2(res, 4)
I båda fallen löses överlagringarna System.DateTime
i stället för de överlagringar som tar inref<System.DateTime>
.
Byref-liknande structs
Förutom byref
//inref
outref
trion kan du definiera dina egna structs som kan följa byref
- som semantik. Detta görs med attributet IsByRefLikeAttribute :
open System
open System.Runtime.CompilerServices
[<IsByRefLike; Struct>]
type S(count1: Span<int>, count2: Span<int>) =
member x.Count1 = count1
member x.Count2 = count2
IsByRefLike
innebär Struct
inte . Båda måste finnas på typen.
En "byref
-liknande" struct i F# är en stackbunden värdetyp. Det allokeras aldrig på den hanterade heapen. En byref
-liknande struct är användbar för programmering med höga prestanda, eftersom den tillämpas med en uppsättning starka kontroller om livslängd och icke-avbildning. Reglerna är:
- De kan användas som funktionsparametrar, metodparametrar, lokala variabler, metodreturer.
- De kan inte vara statiska eller instansmedlemmar i en klass eller normal struct.
- De kan inte fångas upp av någon stängningskonstruktion (
async
metoder eller lambda-uttryck). - De kan inte användas som en allmän parameter.
Den här sista punkten är avgörande för programmering i F#-pipelinestil, liksom |>
en allmän funktion som parameteriserar indatatyperna. Denna begränsning kan vara avslappnad för |>
i framtiden, eftersom den är infogad och inte gör några anrop till icke-inlined generiska funktioner i kroppen.
Även om dessa regler starkt begränsar användningen, gör de det för att uppfylla löftet om databehandling med höga prestanda på ett säkert sätt.
Byref returnerar
Byref-returer från F#-funktioner eller medlemmar kan skapas och användas. När du använder en byref
-returning-metod avrefereras värdet implicit. Till exempel:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
Om du vill returnera ett värde byref måste variabeln som innehåller värdet vara längre än det aktuella omfånget.
För att returnera byref använder du &value
(där värdet är en variabel som lever längre än det aktuella omfånget).
let mutable sum = 0
let safeSum (bytes: Span<byte>) =
for i in 0 .. bytes.Length - 1 do
sum <- sum + int bytes[i]
&sum // sum lives longer than the scope of this function.
För att undvika implicit dereference, till exempel att skicka en referens via flera länkade anrop, använder du &x
(där x
är värdet).
Du kan också tilldela direkt till en retur byref
. Överväg följande (mycket imperativa) program:
type C() =
let mutable nums = [| 1; 3; 7; 15; 31; 63; 127; 255; 511; 1023 |]
override _.ToString() = String.Join(' ', nums)
member _.FindLargestSmallerThan(target: int) =
let mutable ctr = nums.Length - 1
while ctr > 0 && nums[ctr] >= target do ctr <- ctr - 1
if ctr > 0 then &nums[ctr] else &nums[0]
[<EntryPoint>]
let main argv =
let c = C()
printfn $"Original sequence: %O{c}"
let v = &c.FindLargestSmallerThan 16
v <- v*2 // Directly assign to the byref return
printfn $"New sequence: %O{c}"
0 // return an integer exit code
Det här är utdata:
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
Omfång för byrefs
Ett let
-bundet värde får inte ha en referens som överskrider det omfång som det definierades i. Följande är till exempel otillåtet:
let test2 () =
let x = 12
&x // Error: 'x' exceeds its defined scope!
let test () =
let x =
let y = 1
&y // Error: `y` exceeds its defined scope!
()
Detta hindrar dig från att få olika resultat beroende på om du kompilerar med optimeringar eller inte.
Feedback
https://aka.ms/ContentUserFeedback.
Kommer snart: Under hela 2024 kommer vi att fasa ut GitHub-problem som feedbackmekanism för innehåll och ersätta det med ett nytt feedbacksystem. Mer information finns i:Skicka och visa feedback för