F# 有兩個主要功能區域,可處理低階程序設計的空間:
- 這些
byrefinref//outref類型是Managed指標。 它們有使用限制,因此您無法在運行時間編譯無效的程式。 - 類似 結構,這是具有類似語意和與 相同的編譯時間限制
byref<'T>的結構。byref其中一個範例是 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>,用於讀取基礎值的Managed指標。 -
outref<'T>,用於寫入基礎值的Managed指標。 -
byref<'T>,用於讀取和寫入基礎值的Managed指標。
byref<'T>可以傳遞 ,其中inref<'T>必須是 。 同樣地, byref<'T> 可以傳遞預期 中的 outref<'T> 。
使用 byrefs
若要使用 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許可權。 - 沒有隱含
SomeStruct不可變的x,因為是inref。
不過, 對於不可變 的 F# 實值型別, this 指標會推斷為 inref。
所有這些規則一起表示指標的 inref 持有者可能不會修改所指向之內存的直接內容。
Outref 語意
outref<'T>的目的是要指出指標應該只寫入 。 非預期地, outref<'T> 允許讀取基礎值,儘管其名稱。 這是為了相容性目的。
語意上與 outref<'T> 不同 byref<'T>,除了一個差異:具有 outref<'T> 參數的方法會隱含地建構成 Tuple 傳回型別,就像使用 [<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 的 Interop#
除了傳回之外,refC# 還支援 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 on 自變數或傳回 |
outref<'T> 論點 |
[Out] 自變數上的屬性 |
類型推斷和多載規則
inref<'T>在下列情況下,F# 編譯程式會推斷類型:
- 具有屬性的
IsReadOnly.NET 參數或傳回型別。 -
this結構類型上沒有可變動欄位的指標。 - 衍生自另一個
inref<_>指標的記憶體位置位址。
當採用的隱含位址時,具有 型SomeType別自變數的多inref載慣用至具有 型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-like」 結構是堆疊系結實值類型。 它絕不會在Managed堆積上配置。
byref類似 結構適用於高效能程式設計,因為它會強制執行一組關於存留期和非擷取的強式檢查。 規則如下:
- 它們可作為函式參數、方法參數、局部變數、方法傳回。
- 它們不可以是類別或一般結構的靜態或實例成員。
- 任何關閉建構 (
async方法或 Lambda 運算式) 都無法擷取它們。 - 它們不能當做泛型參數使用。
- 從 F# 9 開始,如果使用 允許 ref 結構反條件約束,在 C# 中定義泛型參數,這項限制就會放寬。 F# 可以在類似 byref 的類型和方法中具現化這類泛型。 作為一些範例,這會影響 BCL 委派類型(、)、介面(
Action<>IEnumerable<>、IComparable<>) 和泛型自變數與使用者提供的累加器函式 (String.string Create<TState>(int length, TState state, SpanAction<char, TState> action))。Func<> - 無法在 F# 中撰寫支援 byref 類似型別的泛型程式代碼。
- 從 F# 9 開始,如果使用 允許 ref 結構反條件約束,在 C# 中定義泛型參數,這項限制就會放寬。 F# 可以在類似 byref 的類型和方法中具現化這類泛型。 作為一些範例,這會影響 BCL 委派類型(、)、介面(
最後一點對於 F# 管線樣式程式設計至關重要,如同 |> 參數化其輸入類型的泛型函式。 未來可能會放寬 |> 這項限制,因為它內嵌,而且不會在其主體中對非內嵌泛型函式進行任何呼叫。
雖然這些規則會強烈限制使用方式,但它們會以安全的方式履行高效能運算的承諾。
Byref 傳回
您可以從 F# 函式或成員傳回 Byref,並加以取用。 取 byref用 -returning 方法時,會隱含取值。 例如:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
若要傳回 byref 值,包含值的變數必須比目前範圍還長。
此外,若要傳回 byref,請使用 &value (其中 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
byrefs 的範圍界定
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!
()
這可防止您根據是否使用優化進行編譯而取得不同的結果。