【发布时间】:2021-12-31 05:23:55
【问题描述】:
我正在使用 F# 开发 PowerShell 工具。我目前遇到了一个障碍,因为Async<_> 是一个泛型类型,它不是从非泛型类型派生的,所以我不能请求Async<_> 或Async 作为参数值 - 我必须指定确切的泛型类型参数。
(对于那些不熟悉这两种语言之间交互的人,我可以用 .NET 语言(例如 F#)编写一个类,从 PowerShell 库中的一个类派生它,并给它一个特定的属性,当我运行 PowerShell并导入我的库,我的类作为命令公开。命令类型不能是通用的。类型的属性作为 PowerShell 参数公开。)
据我所知,我无法通过在非泛型类型上使用泛型成员来避免这种情况,所以理想情况下我会有一个转换属性(对于非 PS 用户,转换属性有效地执行类型转换在运行时参数绑定期间)将Async<_> 转换为Async<obj>。在大多数情况下,这对我很有用。但是,我无法找到检查值是否为 Async<_> 的方法,因为在编译时检查 computation :? Async<_> 最终结果为 computation :? Async<obj>,这 不是,不幸的是,同样,通过Async<int>时返回false。
我在 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, string' 类型都不支持 '?'操作员”。不知道我做错了什么,因为解释看起来很简单,而且我找不到关于此消息的任何其他报告或该操作员的要求。 - 我不知道这是否可行,或者该运算符是否仅用于动态访问对象的成员。
我尝试过的解决方案是:
/// 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|])
【问题讨论】:
标签: powershell generics f#