Parametry ByRef
Jazyk F# má dvě hlavní oblasti funkcí, které se zabývají prostorem programování nízké úrovně:
- Typy
byref
//inref
outref
, které jsou spravované ukazatele. Mají omezení použití, takže nelze zkompilovat program, který je v době běhu neplatný. - Struktura
byref
-like, což je struktura , která má podobnou sémantiku a stejná omezení času kompilace jakobyref<'T>
. Jedním z příkladů je Span<T>.
Syntaxe
// 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 a outref
Existují tři formy byref
:
inref<'T>
, spravovaný ukazatel pro čtení podkladové hodnoty.outref<'T>
, spravovaný ukazatel pro zápis do podkladové hodnoty.byref<'T>
, spravovaný ukazatel pro čtení a zápis podkladové hodnoty.
Lze byref<'T>
předat, kde je očekáváno inref<'T>
. byref<'T>
Podobně lze předat, kde outref<'T>
je očekáváno.
Použití byrefs
Chcete-li použít inref<'T>
, musíte získat hodnotu ukazatele s &
:
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
Chcete-li zapisovat na ukazatel pomocí nebo outref<'T>
byref<'T>
, musíte také nastavit hodnotu, na mutable
kterou chytnete ukazatel .
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
Pokud píšete pouze ukazatel místo čtení, zvažte použití outref<'T>
namísto byref<'T>
.
Sémantika inref
Uvažujte následující kód:
let f (x: inref<SomeStruct>) = x.SomeField
Séanticky to znamená následující:
- Držitel
x
ukazatele jej může použít pouze ke čtení hodnoty. - Libovolný ukazatel získaný na
struct
pole vnořená doSomeStruct
daného typuinref<_>
.
Platí také toto:
- Neexistuje žádný implikace, že jiné vlákna nebo aliasy nemají přístup k zápisu .
x
- Není žádný implikace, která
SomeStruct
je neměnná na základěx
toho, že je .inref
U typů hodnot jazyka F#, které jsou neměnné, this
je však ukazatel odvozen jako .inref
Všechna tato pravidla společně znamenají, že držitel inref
ukazatele nesmí upravovat okamžitý obsah paměti, na kterou odkazuje.
Sémantika outref
Účelem outref<'T>
je označit, že ukazatel by měl být zapsán pouze do. Neočekávaně outref<'T>
umožňuje čtení podkladové hodnoty bez ohledu na jeho název. To je pro účely kompatibility.
Sémanticky, outref<'T>
není jiné než byref<'T>
, s výjimkou jednoho rozdílu: metody s outref<'T>
parametry jsou implicitně sestaveny do návratového typu řazené kolekce členů, stejně jako při volání metody s parametrem [<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"
Spolupráce s jazykem C#
Jazyk C# kromě vrácení podporuje in ref
out ref
i ref
klíčová slova. Následující tabulka ukazuje, jak jazyk F# interpretuje, co jazyk C# generuje:
Konstruktor jazyka C# | Odvození jazyka F# |
---|---|
ref návratová hodnota |
outref<'T> |
ref readonly návratová hodnota |
inref<'T> |
in ref Parametr |
inref<'T> |
out ref Parametr |
outref<'T> |
Následující tabulka ukazuje, co jazyk F# generuje:
Konstrukce jazyka F# | Generovaný konstruktor |
---|---|
Argument inref<'T> |
[In] atribut v argumentu |
inref<'T> Vrátit |
modreq atribut pro hodnotu |
inref<'T> v abstraktním slotu nebo implementaci |
modreq při argumentu nebo vrácení |
Argument outref<'T> |
[Out] atribut v argumentu |
Pravidla odvozování typů a přetížení
Typ inref<'T>
je odvozen kompilátorem jazyka F# v následujících případech:
- Parametr .NET nebo návratový
IsReadOnly
typ, který má atribut. - Ukazatel
this
na typ struktury, který nemá žádná proměnlivá pole. - Adresa umístění paměti odvozené z jiného
inref<_>
ukazatele.
Pokud je přijata implicitní adresa, inref
přetížení s argumentem typu SomeType
je upřednostňované pro přetížení s argumentem typu inref<SomeType>
. Příklad:
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)
V oboupřípadechch System.DateTime
inref<System.DateTime>
Byref-like struktury
Kromě byref
//inref
outref
trojice můžete definovat vlastní struktury, které mohou dodržovat byref
sémantiku -like. To se provádí pomocí atributu 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
neznamená Struct
. Oba musí být přítomné na typu.
Struktura "byref
-like" v jazyce F# je typ hodnoty vázané na zásobník. Na spravované haldě se nikdy nepřiděluje. Struktura byref
podobná je užitečná pro programování s vysokým výkonem, protože se vynucuje se sadou silných kontrol o životnosti a nezachytávání. Pravidla jsou:
- Mohou být použity jako parametry funkce, parametry metody, místní proměnné, metoda vrací.
- Nemohou být statické nebo instance členy třídy nebo normální struktury.
- Nelze je zachytit žádným konstruktorem uzavření (
async
metodami nebo výrazy lambda). - Nelze je použít jako obecný parametr.
Tento poslední bod je zásadní pro programování ve stylu kanálu F#, stejně jako |>
obecná funkce, která parametrizuje své vstupní typy. Toto omezení může být v budoucnu uvolněné |>
, protože je vložené a neprovádí žádná volání neschycených obecných funkcí v těle.
I když tato pravidla důrazně omezují využití, dělají to tak, aby splňovaly příslib vysoce výkonného výpočetního prostředí bezpečným způsobem.
Byref vrátí
Funkce Byref vrací z funkcí jazyka F# nebo členů lze vytvořit a využívat. Při využívání byref
metody -returning je hodnota implicitně dereferenced. Příklad:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
Pokud chcete vrátit hodnotu byref, proměnná, která obsahuje hodnotu, musí být aktivní déle než aktuální obor.
Pokud chcete vrátit hodnotu byref, použijte &value
hodnotu (kde hodnota je proměnná, která je delší než aktuální obor).
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.
Pokud se chcete vyhnout implicitní dereference, například předání odkazu prostřednictvím více zřetězených volání, použijte &x
(kde x
je hodnota).
Můžete také přímo přiřadit k návratu byref
. Zvažte následující (vysoce imperativní) program:
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
Toto je výstup:
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
Vymezení rozsahu pro byrefs
Hodnota let
vázaná na hodnotu nesmí mít odkaz větší než obor, ve kterém byla definována. Například následující příkaz je zakázán:
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!
()
To vám zabrání v získání různých výsledků v závislosti na tom, jestli je zkompilujete s optimalizacemi nebo ne.