Byrefs
F# heeft twee belangrijke functiegebieden die te maken hebben met programmeren op laag niveau:
- De
byref
//inref
outref
typen, die beheerde aanwijzers zijn. Ze hebben beperkingen voor het gebruik, zodat u geen programma kunt compileren dat tijdens runtime ongeldig is. - Een
byref
-like struct, een struct met vergelijkbare semantiek en dezelfde compilatietijdbeperkingen alsbyref<'T>
. Een voorbeeld is Span<T>.
Syntaxis
// 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 en outref
Er zijn drie soorten byref
:
inref<'T>
, een beheerde aanwijzer voor het lezen van de onderliggende waarde.outref<'T>
, een beheerde aanwijzer voor het schrijven naar de onderliggende waarde.byref<'T>
, een beheerde aanwijzer voor het lezen en schrijven van de onderliggende waarde.
Een byref<'T>
kan worden doorgegeven waar een inref<'T>
wordt verwacht. Op dezelfde manier kan een byref<'T>
worden doorgegeven waar een outref<'T>
wordt verwacht.
Byrefs gebruiken
Als u een inref<'T>
waarde wilt gebruiken, moet u een aanwijzerwaarde ophalen met &
:
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
Als u naar de aanwijzer wilt schrijven met behulp van een outref<'T>
of byref<'T>
, moet u ook de waarde waarnaar u een aanwijzer mutable
haalt, instellen.
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
Als u alleen de aanwijzer schrijft in plaats van deze te lezen, kunt u overwegen om deze te gebruiken outref<'T>
in plaats van byref<'T>
.
Inref-semantiek
Kijk eens naar de volgende code:
let f (x: inref<SomeStruct>) = x.SomeField
Semantisch betekent dit het volgende:
- De houder van de
x
aanwijzer mag deze alleen gebruiken om de waarde te lezen. - Elke aanwijzer die is verkregen aan
struct
velden die zijn genest binnenSomeStruct
, krijgt het typeinref<_>
.
Het volgende geldt ook:
- Er is geen implicatie dat andere threads of aliassen geen schrijftoegang hebben tot
x
. - Er is geen implicatie die
SomeStruct
onveranderbaar is omdatx
het eeninref
.
Voor F#-waardetypen die onveranderbaar zijn , wordt de this
aanwijzer echter afgeleid als een inref
.
Al deze regels betekenen samen dat de houder van een inref
aanwijzer de onmiddellijke inhoud van het geheugen waarnaar wordt verwezen, niet mag wijzigen.
Outref-semantiek
Het doel is outref<'T>
om aan te geven dat de aanwijzer alleen naar de aanwijzer moet worden geschreven. Onverwacht is outref<'T>
het mogelijk om de onderliggende waarde te lezen ondanks de naam. Dit is voor compatibiliteitsdoeleinden.
Semantisch is outref<'T>
niet anders dan byref<'T>
, behalve voor één verschil: methoden met outref<'T>
parameters worden impliciet samengesteld in een tuple-retourtype, net zoals bij het aanroepen van een methode met een [<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"
Interoperabiliteit met C#
C# ondersteunt naast retourneert ook de in ref
out ref
trefwoorden en trefwoorden ref
. In de volgende tabel ziet u hoe F# interpreteert wat C# verzendt:
C#-constructie | F#-afleiden |
---|---|
ref retourwaarde |
outref<'T> |
ref readonly retourwaarde |
inref<'T> |
in ref Parameter |
inref<'T> |
out ref Parameter |
outref<'T> |
In de volgende tabel ziet u wat F# verzendt:
F#-constructie | Verzonden constructie |
---|---|
Argument voor inref<'T> |
[In] kenmerk op argument |
inref<'T> Terug |
modreq kenmerk op waarde |
inref<'T> in abstracte site of implementatie |
modreq op argument of retour |
Argument voor outref<'T> |
[Out] kenmerk op argument |
Regels voor deductie en overbelasting typen
In inref<'T>
de volgende gevallen wordt een type afgeleid door de F#-compiler:
- Een .NET-parameter of retourtype met een
IsReadOnly
kenmerk. - De
this
aanwijzer op een structtype dat geen onveranderbare velden bevat. - Het adres van een geheugenlocatie die is afgeleid van een andere
inref<_>
aanwijzer.
Wanneer een impliciet adres van een inref
wordt gebruikt, krijgt een overbelasting met een argument van het type SomeType
de voorkeur aan een overbelasting met een argument van het type inref<SomeType>
. Voorbeeld:
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)
In beide gevallen worden de overbelastingen System.DateTime
opgelost in plaats van de overbelastingen die worden gebruikt inref<System.DateTime>
.
Byref-achtige structs
Naast het byref
//inref
outref
trio kunt u uw eigen structs definiëren die aan -achtige semantiek kunnen voldoen.byref
Dit gebeurt met het IsByRefLikeAttribute kenmerk:
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
impliceert Struct
niet . Beide moeten aanwezig zijn op het type.
Een 'byref
-like'-struct in F# is een type stack-gebonden waarde. Het wordt nooit toegewezen aan de beheerde heap. Een byref
-achtige struct is handig voor programmeren met hoge prestaties, omdat deze wordt afgedwongen met een set sterke controles over de levensduur en niet-vastleggen. De regels zijn:
- Ze kunnen worden gebruikt als functieparameters, methodeparameters, lokale variabelen, methode retourneert.
- Ze kunnen geen statische of instantieleden van een klasse of normale struct zijn.
- Ze kunnen niet worden vastgelegd door een sluitingsconstructie (
async
methoden of lambda-expressies). - Ze kunnen niet worden gebruikt als een algemene parameter.
Dit laatste punt is cruciaal voor programmeren in F#-pijplijnstijl, net als |>
een algemene functie waarmee de invoertypen worden geparameteraliseerd. Deze beperking kan in de toekomst worden versoepeld |>
, omdat deze inline is en geen aanroepen doet naar niet-inline algemene functies in de hoofdtekst.
Hoewel deze regels het gebruik sterk beperken, doen ze dit om te voldoen aan de belofte van high-performance computing op een veilige manier.
Byref retourneert
Byref retourneert van F#-functies of leden kunnen worden geproduceerd en verbruikt. Wanneer u een byref
retourmethode gebruikt, wordt de waarde impliciet gededucteerd. Voorbeeld:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
Als u een waarde byref wilt retourneren, moet de variabele die de waarde bevat langer duren dan het huidige bereik.
Als u byref wilt retourneren, gebruikt &value
u (waarbij de waarde een variabele is die langer duurt dan het huidige bereik).
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.
Gebruik (waar x
is de waarde) om de impliciete deductie te voorkomen, zoals het doorgeven van een verwijzing via meerdere gekoppelde aanroepen &x
.
U kunt ook rechtstreeks toewijzen aan een retour byref
. Houd rekening met het volgende (zeer imperatieve) programma:
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
Dit is de uitvoer:
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
Bereik voor byrefs
Een let
-afhankelijke waarde kan de verwijzing niet groter hebben dan het bereik waarin deze is gedefinieerd. Het volgende is bijvoorbeeld niet toegestaan:
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!
()
Dit voorkomt dat u verschillende resultaten krijgt, afhankelijk van of u compileert met optimalisaties of niet.