共用方式為


Byrefs

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 refout 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# 編譯程式會推斷類型:

  1. 具有屬性的 IsReadOnly .NET 參數或傳回型別。
  2. this結構類型上沒有可變動欄位的指標。
  3. 衍生自另一個 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# 管線樣式程式設計至關重要,如同 |> 參數化其輸入類型的泛型函式。 未來可能會放寬 |> 這項限制,因為它內嵌,而且不會在其主體中對非內嵌泛型函式進行任何呼叫。

雖然這些規則會強烈限制使用方式,但它們會以安全的方式履行高效能運算的承諾。

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!
    ()

這可防止您根據是否使用優化進行編譯而取得不同的結果。