Byrefs
F# memiliki dua area fitur utama yang berada di ruang pemrograman tingkat rendah:
- Jenis
byref
/inref
/outref
, yang merupakan pointer terkelola. Jenis-jenis ini memiliki batasan penggunaan sehingga Anda tidak dapat mengompilasi program yang tidak valid pada durasi. - Struktur mirip
byref
, yang merupakan struktur yang memiliki semantik serupa dan pembatasan waktu kompilasi yang sama denganbyref<'T>
. Salah satu contohnya adalah Span<T>.
Sintaks
// 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, dan outref
Ada tiga bentuk dari byref
:
inref<'T>
, pointer terkelola untuk membaca nilai yang mendasar.outref<'T>
, pointer terkelola untuk menulis ke nilai yang mendasar.byref<'T>
, pointer terkelola untuk membaca dan menulis nilai yang mendasar.
byref<'T>
dapat dilewati di mana inref<'T>
diharapkan. Demikian juga, byref<'T>
dapat dilewati di mana outref<'T>
diharapkan.
Menggunakan byrefs
Untuk menggunakan inref<'T>
, Anda perlu mendapatkan nilai pointer dengan &
:
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
Untuk menulis ke pointer dengan menggunakan outref<'T>
atau byref<'T>
, Anda juga harus membuat nilai sebagai pointer ke 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
Jika Anda hanya menulis pointer tanpa membacanya, pertimbangkan untuk menggunakan outref<'T>
daripada byref<'T>
.
Semantik inref
Pertimbangkan gambar berikut:
let f (x: inref<SomeStruct>) = x.SomeField
Secara semantik, ini berarti sebagai berikut:
- Pemegang pointer
x
hanya dapat menggunakannya untuk membaca nilai. - Pointer apa pun yang diperoleh ke bidang
struct
yang disarangkan di dalamSomeStruct
diberi jenisinref<_>
.
Berikut ini juga benar:
- Tidak ada implikasi bahwa alur atau alias lain tidak memiliki akses tulis ke
x
. - Tidak ada implikasi bahwa
SomeStruct
tidak dapat diubah karenax
merupakaninref
.
Namun, untuk jenis nilai F# yang tidak dapat diubah, pointer this
disimpulkan sebagai inref
.
Seluruh aturan ini berarti bahwa pemegang pointer inref
mungkin tidak dapat memodifikasi konten langsung dari memori yang ditunjuk.
Semantik outref
Tujuan outref<'T>
adalah untuk menunjukkan bahwa pointer hanya boleh ditulis. Secara tidak terduga, outref<'T>
mengizinkan pembacaan nilai yang mendasar terlepas dari namanya. Ini untuk tujuan kompatibilitas.
Secara semantik, outref<'T>
tidak berbeda dari byref<'T>
, kecuali pada satu perbedaan: metode dengan parameter outref<'T>
secara implisit dibangun menjadi jenis pengembalian tuple, sama seperti saat memanggil metode dengan parameter [<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"
Interop dengan C #
C# mendukung kata kunci in ref
dan out ref
, selain pengembalian ref
. Tabel berikut ini menunjukkan bagaimana F# menafsirkan apa yang dikeluarkan C#:
Konstruksi C# | F# menyimpulkan |
---|---|
nilai pengembalian ref |
outref<'T> |
nilai pengembalian ref readonly |
inref<'T> |
parameter in ref |
inref<'T> |
parameter out ref |
outref<'T> |
Tabel berikut ini menunjukkan apa yang dikeluarkan F#:
Konstruksi F# | Konstruksi yang dikeluarkan |
---|---|
Argumen inref<'T> |
atribut [In] pada argumen |
pengembalian inref<'T> |
atribut modreq pada nilai |
inref<'T> dalam slot atau implementasi abstrak |
modreq di argumen atau pengembalian |
Argumen outref<'T> |
atribut [Out] pada argumen |
Inferensi jenis dan aturan kelebihan beban
Jenis inref<'T>
disimpulkan oleh kompilator F# dalam kasus berikut:
- Parameter .NET atau jenis pengembalian yang memiliki atribut
IsReadOnly
. - Pointer
this
pada jenis struktur yang tidak memiliki bidang yang dapat diubah. - Alamat lokasi memori yang berasal dari pointer
inref<_>
yang lain.
Ketika alamat implisit dari suatu inref
sedang diambil, kelebihan beban dengan argumen jenis SomeType
lebih dipilih daripada kelebihan beban dengan argumen jenis inref<SomeType>
. Misalnya:
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)
Dalam kedua kasus, pengambilan kelebihan beban System.DateTime
lebih dapat diselesaikan daripada pengambilan kelebihan beban inref<System.DateTime>
.
Struktur mirip-byref
Selain trio byref
/inref
/outref
, Anda dapat menentukan struktur Anda sendiri yang dapat mengikuti semantik mirip-byref
. Hal ini dilakukan dengan atribut 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
tidak menyiratkan Struct
. Keduanya harus ada pada jenisnya.
Struktur "mirip byref
" di F# adalah jenis nilai yang terikat tumpukan. Struktur ini tidak pernah dialokasikan pada tumpukan yang terkelola. Struktur mirip byref
berguna untuk pemrograman performa tinggi, karena diberlakukan dengan serangkaian pemeriksaan yang kuat terkait masa pakai dan non-pengambilan. Aturannya adalah:
- Dapat digunakan sebagai parameter fungsi, parameter metode, variabel lokal, dan pengembalian metode.
- Tidak boleh menjadi anggota statis atau instans dari suatu kelas atau struktur normal.
- Tidak dapat diambil oleh konstruksi penutupan apa pun (metode
async
atau ekspresi lambda). - Tidak dapat digunakan sebagai parameter generik.
Titik terakhir ini sangat penting untuk pemrograman gaya alur F# karena |>
merupakan fungsi generik yang menentukan parameter jenis inputnya. Pembatasan ini dapat dilonggarkan untuk |>
di masa depan karena sebaris dan tidak melakukan panggilan apa pun ke fungsi generik yang tidak sebaris dalam isinya.
Meskipun sangat membatasi penggunaan, aturan ini dilakukan untuk memenuhi janji komputasi berkinerja tinggi dengan cara yang aman.
Pengembalian byref
Byref yang dikembalikan dari fungsi atau anggota F# dapat diproduksi dan digunakan. Saat menggunakan metode pengembalian byref
, nilainya secara implisit didereferensikan. Misalnya:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
Untuk mengembalikan byref nilai, variabel yang berisi nilai harus berlangsung lebih lama dari cakupan saat ini.
Selain itu, untuk mengembalikan byref, gunakan &value
(yang nilainya merupakan variabel yang berlangsung lebih lama dari cakupan saat ini).
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.
Untuk menghindari dereferensi implisit, seperti meneruskan referensi melalui beberapa panggilan berantai, gunakan &x
(x
adalah nilainya).
Anda juga dapat langsung menetapkan ke pengembalian byref
. Pertimbangkan program berikut (sangat penting):
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
Ini adalah outputnya:
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
Cakupan untuk byref
Nilai terikat let
tidak boleh memiliki referensi melebihi cakupan yang ditentukan. Misalnya, hal-hal berikut tidak diperbolehkan:
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!
()
Hal ini mencegah Anda untuk mendapatkan hasil yang berbeda, tergantung pada apakah Anda mengompilasi dengan pengoptimalan atau tidak.