我正在使用 F# 开发 PowerShell 工具。我目前遇到了障碍,因为 Async<_>
是不是从非泛型类型派生的泛型类型,因此我无法请求 Async<_>
或Async
作为参数值 - 我必须指定确切的泛型类型参数。
(对于那些不熟悉这两种语言之间交互的人,我可以使用 .NET 语言(例如 F#)编写一个类,从 PowerShell 库中的类派生它,并为其指定特定属性,然后当我运行 PowerShell 时并导入我的库,我的类作为命令公开。命令类型不能是通用的。该类型的属性作为 PowerShell 参数公开。)
据我所知,我无法通过在非泛型类型上拥有泛型成员来避免这种情况,所以理想情况下我应该有一个转换属性(对于非 PS 用户,转换属性有效地执行类型转换在运行时参数绑定(bind)期间)变为 Async<_>
进入Async<obj>
。在大多数情况下,这对我来说非常有用。但是,我无法找到一种方法来检查值是否为 Async<_>
,因为检查 computation :? Async<_>
编译时最终为 computation :? Async<obj>
,不幸的是,这不相同,并且在传递时返回 false Async<int>
.
我在 C# 中遇到了类似的问题,并且能够利用 dynamic
运行反射测试后的关键字,并使参数为派生基类型 System.Threading.Tasks.Task
,例如
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHeirarchy;
var isTaskOf = task.GetType()
.GetProperty("GetAwaiter", flags)
.PropertyType
.GetMethod("GetResult", flags)
.ReturnType != typeof(void);
if (isTaskOf) {
var result = await (dynamic)task;
}
如果可能的话,我愿意在 F# 中做这样的事情,但是:
- 我未能成功获取 dynamic lookup operator
?
编译。具体来说,“'Async<'a>, string' 类型都不支持 '?'运算符(operator)”。不确定我做错了什么,因为解释看起来很简单,而且我找不到此消息的任何其他报告或该运算符(operator)的要求。 - 我不知道这是否可行,或者该运算符是否仅用于动态访问对象的成员。
我尝试过的解决方案是:
/// Transform from Async<_> to Async<obj>
override _.Transform(_, item : obj) : obj =
match item with
// only matches Async<obj>. I get a compiler warning that _ is constrained to obj
| :? Async<_> as computation ->
let boxedComputation : Async<obj> = async { return! computation }
boxedComputation
// if the value is not an async computation, let it pass through. This will allow other transformation or type converters to try to convert the value
| _ -> item
override _.Transform(_, item) =
// no compiler warning about the type being constrained to obj, but the if test does not pass unless item is Async<obj>
if (item :? Async<_>) then async { return! item :?> Async<_> }
else item
我能想到的另一件事是完全使用反射 - 获取异步类型,反射调用所有 AsyncBuilder 方法以创建计算表达式,然后将其转换为异步。由于我对 F# 相当陌生,我不确定我能否很好地拼凑出这样的计算表达式,而且无论哪种方式,它似乎都比应有的复杂得多。我希望有一些更好的方法来识别异步计算的返回类型和/或只是将结果装箱而不关心它实际上是什么类型。
编辑 在尝试使用 AsyncBuilder 类型的反射进行一些极其复杂的事情之后,我意识到我可以更简单地利用它。这是我当前的工作解决方案,但我仍在寻找更好的选择。
static let boxAsyncReturnValue v = async { return v :> obj }
static let bindFunctionReflected = typeof<FSharpAsyncObjTransformationAttribute>.GetMethod(
nameof boxAsyncReturnValue,
BindingFlags.NonPublic ||| BindingFlags.Static
)
override _.Transform(engineIntrinsics, item) =
// I need to identify the current return type of the computation, and quit if "item" is not Async<_>
if item = null then item else
let itemType = item.GetType()
if not itemType.IsGenericType then item else
let genericItemType = itemType.GetGenericTypeDefinition()
if genericItemType <> typedefof<Async<_>> then item else
let returnType = itemType.GetGenericArguments()[0]
if returnType = typeof<obj> then item else
bindFunctionReflected.MakeGenericMethod(itemType).Invoke(null, [|item|])
最佳答案
这就是我要做的:
let convert (a: Async<_>) =
async {
let! x = a
return box x
}
在编译时它的行为正如您所期望的:
let a = async { return "hello" }
let o: Async<obj> = convert a
let res = Async.RunSynchronously o
printfn "%s" res // Error: expected type 'string' but is type 'obj'
printfn "%s" (unbox<string> res) // compiles, prints the string
关于powershell - F# 异步<_> 到异步<obj>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70539260/