Udostępnij za pośrednictwem


Byrefs

Język F# ma dwa główne obszary cech, które zajmują się programowaniem niskiego poziomu:

  • Typybyref//inrefoutref, które są zarządzanymi wskaźnikami. Mają one ograniczenia dotyczące użycia, dzięki czemu nie można skompilować programu, który jest nieprawidłowy w czasie wykonywania.
  • Struktura podobna byrefdo -like, która jest strukturą, która ma podobne semantyki i te same ograniczenia czasu kompilacji co byref<'T>. Jednym z przykładów jest Span<T>.

Składnia

// 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 i outref

Istnieją trzy formy:byref

  • inref<'T>, zarządzany wskaźnik do odczytywania wartości bazowej.
  • outref<'T>, zarządzany wskaźnik do zapisywania w wartości bazowej.
  • byref<'T>, zarządzany wskaźnik do odczytywania i zapisywania wartości bazowej.

Element byref<'T> można przekazać w oczekiwanym inref<'T> miejscu. Podobnie można przekazać element byref<'T> , w którym jest oczekiwana outref<'T> wartość .

Używanie obiektów byref

Aby użyć elementu inref<'T>, musisz uzyskać wartość wskaźnika za pomocą &polecenia :

open System

let f (dt: inref<DateTime>) =
    printfn $"Now: %O{dt}"

let usage =
    let dt = DateTime.Now
    f &dt // Pass a pointer to 'dt'

Aby zapisać w wskaźniku przy użyciu elementu outref<'T> lub byref<'T>, musisz również ustawić wartość, którą chwycisz wskaźnik do 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

Jeśli piszesz tylko wskaźnik zamiast go odczytywać, rozważ użycie outref<'T> zamiast byref<'T>.

Semantyka inref

Spójrzmy na poniższy kod:

let f (x: inref<SomeStruct>) = x.SomeField

Semantycznie oznacza to następujące kwestie:

  • Posiadacz x wskaźnika może użyć go tylko do odczytania wartości.
  • Każdy wskaźnik uzyskany do struct pól zagnieżdżonych w ramach programu SomeStruct ma typ inref<_>.

Obowiązuje również następująca wartość:

  • Nie ma wpływu na to, że inne wątki ani aliasy nie mają dostępu do zapisu w programie x.
  • Nie ma implikacji, która SomeStruct jest niezmienna z powodu x bycia inref.

Jednak w przypadku typów wartości języka F#, które niezmienne, this wskaźnik jest wnioskowany jako .inref

Wszystkie te reguły razem oznaczają, że posiadacz inref wskaźnika może nie modyfikować bezpośredniej zawartości pamięci wskazywanej.

Semantyka odzwierciedli

outref<'T> Celem funkcji jest wskazanie, że wskaźnik powinien być zapisywany tylko do. Nieoczekiwanie outref<'T> zezwala na odczytywanie wartości bazowej pomimo jej nazwy. Jest to przeznaczone do celów zgodności.

Semantycznie, outref<'T> nie różni się od byref<'T>, z wyjątkiem jednej różnicy: metody z parametrami outref<'T> są niejawnie konstruowane w typie zwracanym krotki, podobnie jak podczas wywoływania metody z parametrem [<Out>] .

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"

Współdziałanie z językiem C#

Język C# obsługuje in ref słowa kluczowe i out ref oprócz zwracania ref . W poniższej tabeli pokazano, jak język F# interpretuje, co emituje język C#:

Konstrukcja języka C# Wnioskowanie języka F#
ref wartość zwracana outref<'T>
ref readonly wartość zwracana inref<'T>
in ref Parametr inref<'T>
out ref Parametr outref<'T>

W poniższej tabeli pokazano, co emituje język F#:

Konstrukcja języka F# Konstrukcja emitowana
Argument inref<'T> [In] atrybut w argumencie
inref<'T> Zwraca modreq atrybut na wartości
inref<'T> w miejscu abstrakcyjnym lub implementacji modreq przy argumentach lub zwracanych
Argument outref<'T> [Out] atrybut w argumencie

Reguły wnioskowania i przeciążania typów

Typ inref<'T> jest wnioskowany przez kompilator języka F# w następujących przypadkach:

  1. Parametr platformy .NET lub typ zwracany IsReadOnly , który ma atrybut.
  2. Wskaźnik this typu struktury, który nie ma pól modyfikowalnych.
  3. Adres lokalizacji pamięci pochodzącej z innego inref<_> wskaźnika.

Gdy jest pobierany niejawny adres, inref przeciążenie argumentem typu SomeType jest preferowane do przeciążenia z argumentem typu inref<SomeType>. Na przykład:

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)

W obu przypadkach przeciążenia biorące System.DateTime są rozwiązywane, a nie przeciążenia biorące inref<System.DateTime>wartość .

Struktury podobne do elementu Byref

Oprócz byref//inrefoutref trio można zdefiniować własne struktury, które mogą być zgodne z byrefsemantykami przypominającymi semantykę. Odbywa się to za pomocą atrybutu 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 nie oznacza Struct. Oba muszą być obecne w typie.

Struktura "byref-like" w języku F# jest typem wartości powiązanej ze stosem. Nigdy nie jest przydzielany na zarządzanym stercie. Struktura podobna byrefdo typu jest przydatna w przypadku programowania o wysokiej wydajności, ponieważ jest wymuszana przy użyciu zestawu silnych testów dotyczących okresu istnienia i nieuchwytywania. Reguły to:

  • Można ich używać jako parametrów funkcji, parametrów metody, zmiennych lokalnych, zwracanych metod.
  • Nie mogą być statyczne lub wystąpienia składowe klasy ani struktury normalnej.
  • Nie można ich przechwycić za pomocą żadnej konstrukcji zamknięcia (async metod ani wyrażeń lambda).
  • Nie można ich używać jako parametru ogólnego.

Ten ostatni punkt ma kluczowe znaczenie dla programowania w stylu potoku języka F#, podobnie jak |> funkcja ogólna, która parametrizuje jego typy wejściowe. To ograniczenie może być złagodzone |> w przyszłości, ponieważ jest wbudowane i nie wykonuje żadnych wywołań do nielinowanych funkcji ogólnych w jego ciele.

Chociaż te reguły zdecydowanie ograniczają użycie, robią to, aby spełnić obietnicę obliczeń o wysokiej wydajności w bezpieczny sposób.

Zwraca wartość byref

Funkcja Byref zwracane z funkcji języka F# lub składowych można wygenerować i zużyć. W przypadku korzystania z byrefmetody -returning wartość jest niejawnie wyłuszczana. Na przykład:

let squareAndPrint (data : byref<int>) =
    let squared = data*data    // data is implicitly dereferenced
    printfn $"%d{squared}"

Aby zwrócić wartość byref, zmienna zawierająca wartość musi żyć dłużej niż bieżący zakres. Ponadto, aby zwrócić byref, użyj ( &value gdzie wartość jest zmienną, która żyje dłużej niż bieżący zakres).

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.

Aby uniknąć niejawnego wyłudzenia, takiego jak przekazywanie odwołania przez wiele wywołań łańcuchowych, użyj polecenia &x (gdzie x jest wartością).

Możesz również bezpośrednio przypisać element do zwracanego .byref Rozważmy następujący (wysoce imperatywne) 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

Jest to produkt wyjściowy:

Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence:      1 3 7 30 31 63 127 255 511 1023

Określanie zakresu dla elementu byrefs

Wartość let-bound nie może mieć odwołania przekraczającego zakres, w którym została zdefiniowana. Na przykład następujące informacje są niedozwolone:

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!
    ()

Zapobiega to uzyskiwaniu różnych wyników w zależności od tego, czy kompilujesz z optymalizacjami, czy nie.