Aracılığıyla paylaş


Byrefs

F# alt düzey programlama alanıyla ilgilenen iki önemli özellik alanına sahiptir:

  • yönetilen işaretçiler olan byref/inref/outref türleri. Çalışma zamanında geçersiz olan bir programı derleyemezsiniz diye kullanım kısıtlamaları vardır.
  • benzer semantiklere ve byref<'T>ile aynı derleme zamanı kısıtlamalarına sahip bir yapı olan byrefbenzeri bir yapı. Örnek olarak Span<T>.

Sözdizimi

// 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 ve outref

üç byrefbiçimi vardır:

  • inref<'T>, temel değeri okumak için yönetilen bir işaretçidir.
  • outref<'T>, temel alınan değere yazmak için bir yönetilen işaretçidir.
  • byref<'T>, altında yatan değeri okumak ve yazmak için yönetilen bir işaretçidir.

byref<'T>'nın yerine inref<'T>'in beklendiği yerde geçirilebilir. Benzer şekilde, outref<'T> beklendiği yerde bir byref<'T> geçirilebilir.

Byrefs'i kullanma

inref<'T>kullanmak için, &ile bir işaretçi değeri almanız gerekir:

open System

let f (dt: inref<DateTime>) =
    printfn $"Now: %O{dt}"

let usage =
    let dt = DateTime.Now
    f &dt // Pass a pointer to 'dt'

Bir outref<'T> veya byref<'T>kullanarak işaretçiye yazmak için, aynı zamanda elde ettiğiniz değeri mutableiçin bir işaretçi haline getirmeniz gerekmektedir.

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

İşaretçiyi okumak yerine yalnızca yazıyorsanız, byref<'T>yerine outref<'T> kullanmayı düşünün.

Inref semantiği

Aşağıdaki kodu göz önünde bulundurun:

let f (x: inref<SomeStruct>) = x.SomeField

Bu, anlam olarak şu anlama gelir:

  • x işaretçisinin tutucusu yalnızca değeri okumak için kullanabilir.
  • SomeStruct iç içe yerleştirilmiş struct alanlara alınan tüm işaretçilere inref<_>türü verilir.

Aşağıdakiler de geçerlidir:

  • Diğer iş parçacıklarının veya diğer adların xyazma erişimine sahip olmadığını gösteren bir sonuç yoktur.
  • SomeStruct'ın değiştirilmez olduğu söylenemez çünkü x bir inref'dir.

Ancak, sabit olan F# değer türleri için, this işaretçisi inrefolarak çıkarılır.

Bu kuralların tümü birlikte, bir inref işaretçisinin sahibinin işaret edilen belleğin hemen içeriğini değiştiremeyeceği anlamına gelir.

Outref semantiği

outref<'T>'ın amacı, işaretçinin yalnızca üzerine yazılabileceğini belirtmektir. Beklenmedik şekilde, outref<'T> adına rağmen temel alınan değerin okunmasına izin verir. Bu, uyumluluk amaçlıdır.

Semantik olarak, outref<'T>'un byref<'T>'den tek farkı şudur: outref<'T> parametreleri içeren yöntemler, tıpkı [<Out>] parametresine sahip bir yöntemi çağırırken olduğu gibi, örtük olarak tanımlama grubu dönüş türüne dönüştürülür.

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# ile birlikte çalışma

C# in ref ve out ref anahtar sözcüklerinin yanı sıra ref dönüşlerini de destekler. Aşağıdaki tabloda, C# dilinin ürettiklerini F# dilinin nasıl yorumladığı gösterilmektedir.

C# dil yapısı F# çıkarım yapar
ref dönüş değeri outref<'T>
ref readonly dönüş değeri inref<'T>
in ref parametresi inref<'T>
out ref parametresi outref<'T>

Aşağıdaki tablo, F#'ın ürettiklerini gösteriyor.

F# yapısı Yayılan yapılandırma
inref<'T> bağımsız değişkeni Argümandaki [In] özniteliği
inref<'T> iade Değer üzerinde modreq özniteliği
Soyut yuva veya uygulamada inref<'T> modreq bağımsız değişken veya dönüş üzerinde
outref<'T> bağımsız değişkeni Bağımsız değişkende [Out] özniteliği

Tür çıkarımı ve aşırı yükleme kuralları

Aşağıdaki durumlarda F# derleyicisi tarafından bir inref<'T> türü çıkarılır:

  1. IsReadOnly özniteliğine sahip bir .NET parametresi veya dönüş türü.
  2. Değiştirilebilir alanları olmayan bir yapı türü üzerindeki this işaretçisi.
  3. Başka bir inref<_> işaretçisinden türetilen bellek konumunun adresi.

Bir inref adresi örtük olarak alındığında, SomeType türünde bir bağımsız değişkene sahip aşırı yükleme, inref<SomeType>türünde bir bağımsız değişkene sahip aşırı yüklemeye tercih edilir. Örneğin:

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)

Her iki durumda da System.DateTime alan aşırı yüklemeler, inref<System.DateTime>alan aşırı yüklemeler yerine çözülür.

Byref benzeri yapılar

byref / inref / outref trio'ya ek olarak, byrefbenzeri semantiklere bağlı kalabilecek kendi yapılarınızı tanımlayabilirsiniz. Bu işlem IsByRefLikeAttribute özniteliğiyle yapılır:

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 Structanlamına gelmez. Her ikisi de tipinde bulunmalıdır.

F# dilinde "byref-like" yapısı, yığına bağlı bir değer türüdür. Yönetilen yığında hiçbir zaman tahsis edilmez. byrefgibi bir yapı, ömrü ve yakalanmama konusunda güçlü denetimlerle desteklendiğinden yüksek performanslı programlamada faydalıdır. Kurallar şunlardır:

  • İşlev parametreleri, yöntem parametreleri, yerel değişkenler, yöntem dönüşleri olarak kullanılabilirler.
  • Statik veya bir sınıfın veya normal yapının örnek üyeleri olamazlar.
  • Bunlar herhangi bir kapatma yapısı (async yöntemleri veya lambda ifadeleri) tarafından yakalanamaz.
  • Genel parametre olarak kullanılamazlar.
    • F# 9'dan itibaren, genel parametre C# dilinde ref struct sınırlaması kaldırarak tanımlanırsa bu kısıtlama gevşetilir. F#, byref benzeri tiplerle tip ve metotlarda bu tür genel tiplerin örneğini oluşturabilir. Birkaç örnek olarak, bu BCL temsilci türlerini (Action<>, Func<>), arabirimleri (IEnumerable<>, IComparable<>) ve kullanıcı tarafından sağlanan bir biriktirici işleviyle (String.string Create<TState>(int length, TState state, SpanAction<char, TState> action)) genel bağımsız değişkenleri etkiler.
    • F# dilinde byref benzeri türleri destekleyen genel kod yazmak mümkün değildir.

|> giriş türlerini parametreleştiren genel bir işlev olduğundan, bu son nokta F# işlem hattı stili programlama için çok önemlidir. Bu kısıtlama, satır içi olduğu ve gövdesinde satır içi olmayan genel fonksiyonlara çağrıda bulunmadığı için gelecekte |> için gevşetilebilir.

Bu kurallar kullanımı güçlü bir şekilde kısıtlasa da, yüksek performanslı bilgi işlem sözünü güvenli bir şekilde yerine getirmek için bunu yapar.

Byref döndürür

F# işlevlerinden veya üyelerinden Byref dönüşleri üretilebilir ve kullanılabilir. byref-dönüş yöntemi kullanıldığında, değer örtük olarak işlenir. Mesela:

let squareAndPrint (data : byref<int>) =
    let squared = data*data    // data is implicitly dereferenced
    printfn $"%d{squared}"

Bir değer byref döndürmek için, değeri içeren değişkenin geçerli kapsamdan daha uzun yaşaması gerekir. Ayrıca, byref döndürmek için &value kullanın (burada değer, geçerli kapsamdan daha uzun süre yaşayan bir değişkendir).

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.

Birden çok zincirli çağrıdan referans geçirme gibi örtük dereferansı önlemek için &x kullanın (burada x değerdir).

Ayrıca doğrudan bir dönüş etiketi byrefatayabilirsiniz. Aşağıdaki (son derece zorunlu) programı göz önünde bulundurun:

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

Çıkış şu şekildedir:

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 için kapsam belirleme

letbağlı bir değerin, referansının tanımlandığı kapsamı aşması mümkün değildir. Örneğin, aşağıdakilere izin verilmiyor:

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

Bu, iyileştirmelerle derleyip derlemediğinize bağlı olarak farklı sonuçlar almanızı önler.