Delen via


Byrefs

F# heeft twee belangrijke functiegebieden die te maken hebben met programmeren op laag niveau:

  • De byref//inrefoutref 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 als byref<'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 mutablehaalt, 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 binnen SomeStruct , krijgt het type inref<_>.

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 omdat x het een inref.

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 refout 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:

  1. Een .NET-parameter of retourtype met een IsReadOnly kenmerk.
  2. De this aanwijzer op een structtype dat geen onveranderbare velden bevat.
  3. 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//inrefoutref 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 Structniet . 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 byrefretourmethode 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.