使用F#生成类型提供程序类型作为通用类型参数

9

背景

我正在学习生成式类型提供程序。

我使用了 Cameron Taggart 的 VectorTP 示例,这里这里。在那段代码中,他构建了一个 C# 代码的向量类,该类具有设计时指定的属性数量,编译它,并返回生成的类型。它工作得很好。

例如,这个客户端代码可以编译并运行:

type Vector2D = Vector<"X", "Y">
let v1 = Vector2D()
v1.X <- 3.14
v1.Y <- 2.91

如果你观察设计时类型提供程序代码内部的情况,你会发现类型提供程序代码是这样被调用的:

ITypeProvider.ApplyStaticArguments(
    VectorTP.Vector,                       // typeWithoutArguments
    [|"ConsoleApplication8"; "Vector2D"|], // typeNameWithArguments
    [|"X"; "Y"; ""; ""; ""; ""; ""|])      // staticArguments

我看起来都不错。

问题

这段客户端代码无法编译:

type Vector2D = Vector<"X", "Y">
let list = System.Collections.Generic.List<Vector2D>()

这次,如果你在设计时查看类型提供程序代码的内部情况,当List<Vector2D>被添加到客户端代码时,你会看到这个额外的调用:

ITypeProvider.ApplyStaticArguments(
    Mindscape.Vectorama.Vector2D,   // typeWithoutArguments
    [|"Vector2D,"|],                // typeNameWithArguments
    [|""; ""; ""; ""; ""; ""; ""|]) // staticArguments

看起来,类型提供程序框架(是否正确的术语?)正在调用`ITypeProvider.ApplyStaticArguments`,要求基于`Vector2D`生成类型而没有任何静态参数。但是,`Vector2D`已经是生成的类型了?!
VectorTP示例没有正确处理这种情况,因此客户端代码将无法编译。
注意:
我尝试将`type Vector2D = Vector<"X", "Y">`声明移动到一个单独的DLL中,然后引用该DLL。当然,这符合预期。此时生成的`Vector2D`类看起来就像任何其他类型一样。
复杂之处似乎在于生成类型并在同一程序集(或脚本,虽然我没有尝试过)中使用它作为泛型参数。
问题:
1. 这是"type provider framework"中的问题吗?还是这是预期行为? 2. 当我使用生成的类型作为泛型类型参数时,为什么会调用`ApplyStaticArguments`? 3. 如果`ITypeProvider`应该处理这种情况,那么正确的响应是什么?

类型提供程序的实现看起来有点可疑...例如,无论分辨率给出什么名称,它总是返回相同的类型,并且同样表明有6个静态参数,但只有在请求Vector类型时才应该这样做。 - kvb
@kvb 你注意到VectorTP的实现总是返回相同的类型和相同的静态参数。同时,你还注意到VectorTP的GetTypes()实现只公布了一个类型--向量类型。作为一种“hello-world-type-provider”,我并不感到惊讶。令我惊讶的是,ApplyStaticArguments被调用时使用了Vector2D类型。我以为我理解了IProvidedNamespace和ITypeProvider接口,直到我观察到这种行为。 - Wallace Kelly
@Wally - 如果请求的类型是意外的,如果您抛出异常,实际会发生什么?您是否认为它在列表中无法工作,还是这确实是您观察到的行为?编译器可能会退出,如果您指示无法处理特定类型,但是当您错误地指示可以处理时,情况可能会变得非常糟糕。 - kvb
@kvb 我接下来会尝试使用基于ProvidedTypes的生成式提供程序。不过,快速浏览表明它的行为类似。不过我会进行确认。 - Wallace Kelly
@kvb 哦,当然!我没有意识到这是直接针对底层接口编写的... - Tomas Petricek
显示剩余4条评论
1个回答

7
在阅读评论并进行更多实验后,我得出了结论。
1. 这是“类型提供程序框架”中的问题吗?还是这是预期行为?
这不是我所期望的行为。
只有当您尝试将生成的类型用作通用类型参数时,才会出现问题。当您将生成的类型用作通用类型参数时,框架会调用该生成类型的ITypeProvider.GetStaticParameters。目前我还不清楚为什么需要这样做。
无论如何,由于出现了意外调用,ITypeProvider.GetStaticParameters()的实现不能像这样简单:
member this.GetStaticParameters(typeWithoutArguments) =
    [1..7] |> List.map (fun i -> stringParameter i "") |> List.toArray

这可能是这样的:

这必须类似于这样:

member this.GetStaticParameters(typeWithoutArguments) =
    if typeWithoutArguments = typeof<Vector> then
        [1..7] |> List.map (fun i -> stringParameter i "") |> List.toArray
    else
        [||] // for the generated types like Vector2D

在进行上述更改后,我能够编译使用 List<Vector2D> 的客户端代码。

2. 在将生成的类型用作泛型类型参数时为什么会调用 ApplyStaticArguments 方法?

请注意,我最初提问的重点是为什么会调用 ITypeProvider.ApplyStaticArguments。调用 ApplyStaticArguments 的原因是因为 GetStaticParameters 表示生成的类型(Vector2D)本身需要静态参数。修复了 GetStaticParameters 后,就不再为生成的类型调用 ApplyStaticArguments。这现在很清楚。

3. 如果 ITypeProvider 应处理此情况,则应采取何种措施?

已在上文中解决。不过,这也引出了一个更大的问题:“有没有关于生成类型提供程序的好例子?”这个问题已经被问过了,但在这里没有得到答案。

最后,关于 ProvidedTypes.fs

在评论中,有人问是否使用 ProvidedTypes.fs 库可以解决这个问题。我使用 ProvidedTypes.fs 重复了这个练习,最初也遇到了同样的问题。也就是说,即使使用了 ProvidedTypes.fs,您必须认识到,ProvidedTypeDefinition.DefineStaticParameters 的处理程序(相当于 ITypeProvider.GetStaticParameters)可能会通过传递生成的类型而调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接