В настоящее время нет способа сделать это без отражения. Это решение, которое я придумал, и оно работает довольно хорошо. До сих пор у него не было проблем с этим, но имейте в виду, что это не пуленепробиваемое и, возможно, есть какой-то край, о котором я не думал.
Как это работает?
Рассмотрим следующую функцию 'a', которая фиксирует состояние.
let o = "o" // non static!
// function captures outer variable 'o'
let a = (fun () ->
printf "%s" o
)
Функция, которую мы связываем с 'a', превращается в класс, сгенерированный компилятором, который содержит фактический код в методе с именем Invoke. ,Если функция не захватывает состояние, то у нее есть конструктор без параметров. Ему не нужно ничего, что не передано в invoke. НО, если он захватывает состояние, класс будет иметь конструктор, принимающий <1 параметров. Это то, что мы будем использовать для определения, фиксирует ли функция состояние. </p>
Кроме того, поскольку производительность является проблемой, мы кешируем результат анализа.
Вот как выглядит код:
module FunctionAnalysis =
open System
open System.Reflection
open System.Collections.Concurrent
let internal cache = ConcurrentDictionary<Type, bool>()
let private flags =
BindingFlags.Instance |||
BindingFlags.NonPublic |||
BindingFlags.Public
let capturesState (func : 'a) : bool =
let type' = func.GetType()
let hasValue, value = cache.TryGetValue type'
match hasValue with
| true -> value
| false ->
let capturesState =
type'.GetConstructors(flags)
|> Array.map (fun info -> info.GetParameters().Length)
|> Array.exists (fun parameterLength -> parameterLength > 0)
cache.AddOrUpdate(type', capturesState, (fun identifier lastValue -> capturesState))