Byrefs
F# 有兩個主要功能區域,可處理低階程式設計的空間:
byref
/inref
/outref
型別,也就是受控指標。 它們在使用上有所限制,因此您無法在執行階段編譯不正確的程式。- 類
byref
的結構,是一種具有與byref<'T>
類似語意及相同編譯時間限制的結構。 其中一個範例是 Span<T>。
語法
// 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 和 Outref
byref
有三種形式:
inref<'T>
,用於讀取基礎值的受控指標。outref<'T>
,用於寫入基礎值的受控指標。byref<'T>
,用於讀取和寫入基礎值的受控指標。
byref<'T>
可在預期 inref<'T>
的情況下被傳遞。 同樣地,byref<'T>
可在預期 outref<'T>
的情況下被傳遞。
使用 Byref
若要使用 inref<'T>
,您必須使用 &
來取得指標值:
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
若要使用 outref<'T>
或 byref<'T>
寫入指標,您也必須將擷取到的值設為 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
如果您只寫入指標而不進行讀取,請考慮使用 outref<'T>
而不是 byref<'T>
。
Inref 語意
請考慮下列程式碼:
let f (x: inref<SomeStruct>) = x.SomeField
語意上,這表示下列各項:
x
指標的預留位置只能使用它來讀取值。- 在
SomeStruct
內的struct
巢狀欄位取得的任何指標,都是指定型別inref<_>
。
下列也成立:
- 沒有任何隱含指出,其他執行緒或別名沒有對
x
的寫入權限。 - 沒有任何隱含指出因為
x
是inref
,所以SomeStruct
不可變。
然而,針對不可變 的 F# 實值型別,系統會將 this
指標推斷為 inref
。
所有這些規則都表示 inref
指標的預留位置可能不會修改所指向之記憶體的立即內容。
Outref 語意
outref<'T>
的用途是指出指標應該只能被寫入。 在其名稱的前提下,outref<'T>
意外地允許讀取基礎值。 這是為了達到相容性。
語意上,outref<'T>
與 byref<'T>
除了一項差異之外並無不同:具有 outref<'T>
參數的方法會隱含建構為元組傳回型別,就像使用 [<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"
使用 C# 相互操作
除了 ref
傳回之外,C# 還支援 in ref
和 out ref
關鍵字。 下列表格顯示 F# 如何解譯 C# 發出的內容:
C# 建構 | F# 推斷 |
---|---|
ref 傳回值 |
outref<'T> |
ref readonly 傳回值 |
inref<'T> |
in ref 參數 |
inref<'T> |
out ref 參數 |
outref<'T> |
下列表格顯示 F# 發出的內容:
F# 建構 | 發出的建構 |
---|---|
inref<'T> 引數 |
在引數上的 [In] 屬性 |
inref<'T> 傳回 |
在值上的 modreq 屬性 |
在抽象位置或實作中的 inref<'T> |
在引數或傳回上的 modreq |
outref<'T> 引數 |
在引數上的 [Out] 屬性 |
型別推斷和多載規則
在下列情況下,F# 編譯器會推斷 inref<'T>
型別:
- 具有
IsReadOnly
屬性的 .NET 參數或傳回型別。 - 結構類型上不具可變動欄位的
this
指標。 - 衍生自另一個
inref<_>
指標的記憶體位置之位址。
取得 inref
的隱含位址時,具有 SomeType
型別引數的多載會優先於具有型別 inref<SomeType>
引數的多載。 例如:
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)
在兩種情況下,系統都會解析採用 System.DateTime
的多載,而不是採用 inref<System.DateTime>
的多載。
類 Byref 結構
除了 byref
/inref
/outref
三者之外,您還可以自行定義遵守與 byref
類似的語意之結構。 請使用 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
並不表示 Struct
。 兩者都必須存在於類型上。
F# 中「類似 byref
」的結構是與堆疊繫結的實值型別。 永遠不會配置在受控堆積上。 類似 byref
的結構適用於高效能程式設計,因為它會施行一組關於存留期和非擷取的強式檢查。 規則如下:
- 可作為函式參數、方法參數、區域變數、方法傳回。
- 不能是類別或一般結構的靜態或執行個體成員。
- 無法供任何結束建構 (
async
方法或 Lambda 運算式) 擷取。 - 它們不能作為泛型參數使用。
最後一點對於 F# 管線樣式的程式設計而言非常重要,因為 |>
是一種會對其輸入型別進行參數化的泛型函式。 未來可能會放寬對 |>
的這項限制,因為它內嵌,而且不會在其主體中對非內嵌泛型函式進行任何呼叫。
雖然這些規則會強式限制使用方式,但它們會以安全的方式履行高效能運算的承諾。
Byref 傳回
來自 F# 函式或成員的傳回可供產生和取用。 取用 byref
傳回方法時,該值為隱含取值。 例如:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
若要傳回 Byref 值,包含值的變數的存留期必須超過目前的範圍。
此外,若要傳回 Byref 值,請使用 &value
(值為存留期超過目前範圍的變數)。
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.
為了避免隱含取值,例如透過多個鏈結呼叫傳遞參考,請使用 &x
(其中 x
為值)。
您也可以直接指派給傳回 byref
。 請考量下列 (高度命令式) 程式:
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
此為輸出:
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
Byref 的範圍界定
let
繫結值不能使其參考超出其定義範圍。 例如,下列是不允許的參數:
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!
()
這可防止您根據是否使用最佳化進行編譯而取得不同的結果。