Byref
F# ha due principali aree di funzionalità che si occupano dello spazio di programmazione di basso livello:
- Tipi
byref
//inref
outref
, che sono puntatori gestiti. Hanno restrizioni sull'utilizzo in modo che non sia possibile compilare un programma non valido in fase di esecuzione. byref
Struct simile a , ovvero uno struct con semantica simile e le stesse restrizioni in fase di compilazione dibyref<'T>
. Un esempio è Span<T>.
Sintassi
// 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 e outref
Esistono tre forme di byref
:
inref<'T>
, un puntatore gestito per la lettura del valore sottostante.outref<'T>
, un puntatore gestito per la scrittura nel valore sottostante.byref<'T>
, un puntatore gestito per la lettura e la scrittura del valore sottostante.
È possibile passare un byref<'T>
oggetto in cui è previsto un oggetto inref<'T>
. Analogamente, è possibile passare un oggetto byref<'T>
in cui è previsto un oggetto outref<'T>
.
Uso di byrefs
Per usare un inref<'T>
oggetto , è necessario ottenere un valore del puntatore con &
:
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
Per scrivere nel puntatore usando un outref<'T>
oggetto o byref<'T>
, è necessario impostare anche il valore che si desidera impostare mutable
su .
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
Se si scrive solo il puntatore anziché leggerlo, è consigliabile usare outref<'T>
anziché byref<'T>
.
Semantica inref
Osservare il codice seguente:
let f (x: inref<SomeStruct>) = x.SomeField
Semanticamente, ciò significa quanto segue:
- Il titolare del
x
puntatore può usarlo solo per leggere il valore. - A qualsiasi puntatore acquisito nei
struct
campi annidati all'internoSomeStruct
viene assegnato il tipoinref<_>
.
È vero anche quanto segue:
- Non c'è alcuna implicazione che altri thread o alias non abbiano accesso in scrittura a
x
. - Non c'è alcuna implicazione che
SomeStruct
non è modificabile in virtù dix
essere uninref
.
Tuttavia, per i tipi valore F# non modificabili, il this
puntatore viene dedotto come .inref
Tutte queste regole insieme indicano che il titolare di un inref
puntatore potrebbe non modificare il contenuto immediato della memoria a cui punta.
Semantica outref
Lo scopo di outref<'T>
è indicare che il puntatore deve essere scritto solo in . In modo imprevisto, outref<'T>
consente di leggere il valore sottostante nonostante il nome. Questo è a scopo di compatibilità.
Semanticamente, outref<'T>
non è diverso da , ad eccezione di byref<'T>
una differenza: i metodi con outref<'T>
parametri vengono costruiti in modo implicito in un tipo restituito di tupla, proprio come quando si chiama un metodo con un [<Out>]
parametro .
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"
Interoperabilità con C#
C# supporta le in ref
parole chiave e out ref
, oltre a restituisce ref
. La tabella seguente illustra come F# interpreta gli elementi generati da C#:
Costrutto C# | Inferi F# |
---|---|
ref valore restituito |
outref<'T> |
ref readonly valore restituito |
inref<'T> |
parametro in ref |
inref<'T> |
parametro out ref |
outref<'T> |
La tabella seguente illustra gli elementi generati da F#:
Costrutto F# | Costrutto generato |
---|---|
inref<'T> argomento |
[In] attributo sull'argomento |
inref<'T> Ritorno |
modreq attributo sul valore |
inref<'T> nello slot astratto o nell'implementazione |
modreq argomento o restituzione |
outref<'T> argomento |
[Out] attributo sull'argomento |
Regole di inferenza e overload dei tipi
Un inref<'T>
tipo viene dedotto dal compilatore F# nei casi seguenti:
- Parametro .NET o tipo restituito con un
IsReadOnly
attributo . - Puntatore
this
in un tipo di struct senza campi modificabili. - Indirizzo di una posizione di memoria derivata da un altro
inref<_>
puntatore.
Quando viene acquisito un indirizzo implicito di un oggetto inref
, un overload con un argomento di tipo SomeType
è preferibile a un overload con un argomento di tipo inref<SomeType>
. Ad esempio:
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 entrambi i casi, gli overload che accettano System.DateTime
vengono risolti anziché gli overload che accettano inref<System.DateTime>
.
Struct simili a byref
Oltre al byref
//inref
outref
trio, è possibile definire struct personalizzati che possono essere conformi alla semantica simile.byref
Questa operazione viene eseguita con l'attributo 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
non implica Struct
. Entrambi devono essere presenti nel tipo .
Uno struct "byref
simile" in F# è un tipo di valore associato a stack. Non viene mai allocata nell'heap gestito. Uno byref
struct -like è utile per la programmazione ad alte prestazioni, perché viene applicato con un set di controlli sicuri sulla durata e non acquisizione. Le regole sono:
- Possono essere usati come parametri di funzione, parametri del metodo, variabili locali, metodo restituito.
- Non possono essere membri statici o di istanza di una classe o di uno struct normale.
- Non possono essere acquisiti da alcun costrutto di chiusura (
async
metodi o espressioni lambda). - Non possono essere usati come parametro generico.
Questo ultimo punto è fondamentale per la programmazione in stile pipeline F#, come |>
una funzione generica che parametrizza i relativi tipi di input. Questa restrizione può essere rilassata per |>
in futuro, in quanto è inline e non effettua alcuna chiamata a funzioni generiche non inlined nel suo corpo.
Anche se queste regole limitano fortemente l'utilizzo, lo fanno per soddisfare la promessa di elaborazione ad alte prestazioni in modo sicuro.
Restituisce Byref
La funzione Byref restituisce da funzioni o membri F# può essere prodotta e utilizzata. Quando si utilizza un byref
metodo -returning, il valore viene dereferenziato in modo implicito. Ad esempio:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
Per restituire un byref di valore, la variabile che contiene il valore deve essere più lunga dell'ambito corrente.
Inoltre, per restituire byref, usare &value
(dove value è una variabile che dura più a lungo dell'ambito corrente).
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.
Per evitare la dereferenziazione implicita, ad esempio il passaggio di un riferimento tramite più chiamate concatenati, usare &x
(dove x
è il valore).
È anche possibile assegnare direttamente a un oggetto restituito byref
. Si consideri il programma seguente (estremamente imperativo):
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
L'output è il seguente.
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
Definizione dell'ambito per byrefs
Un let
valore con associazione a -non può avere il riferimento superiore all'ambito in cui è stato definito. Ad esempio, non è consentito quanto segue:
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!
()
Ciò impedisce di ottenere risultati diversi a seconda della compilazione con ottimizzazioni o meno.