Byrefs
Język F# ma dwa główne obszary cech, które zajmują się programowaniem niskiego poziomu:
- Typy
byref
//inref
outref
, 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
byref
do -like, która jest strukturą, która ma podobne semantyki i te same ograniczenia czasu kompilacji cobyref<'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 programuSomeStruct
ma typinref<_>
.
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 powodux
byciainref
.
Jednak w przypadku typów wartości języka F#, które są 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:
- Parametr platformy .NET lub typ zwracany
IsReadOnly
, który ma atrybut. - Wskaźnik
this
typu struktury, który nie ma pól modyfikowalnych. - 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
//inref
outref
trio można zdefiniować własne struktury, które mogą być zgodne z byref
semantykami 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 byref
do 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 byref
metody -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.