F#泛型约束使一个泛型类型继承另一个泛型类型

3

在C#中,如果一个泛型参数继承自另一个泛型参数,则定义一个泛型类是很直观的,例如:

public class MyClass<TClass, TInterface> where TClass : class, TInterface
{
}
    

这是用于“强制”类TClass实现接口TInterface。我希望在F#中做同样的事情,但令人惊讶的是似乎不起作用。例如,以下代码:

type Startup<'S, 'I when 'I : not struct and 'S : not struct and 'S :> 'I>() =
    member _.x = 0

FS0663的结果——该类型参数已被使用,以使其始终为“ I when 'I:not struct”时约束。我想知道是否可以在F#中实现类似于C#的泛型结构。

此时,你可能会想为什么需要它?原因是我想要一个与CoreWCF类似的通用的F#交互:

open CoreWCF
open CoreWCF.Configuration
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.DependencyInjection

module Startup =

    type Startup<'S, 'I when 'I : not struct and 'S : not struct>() =
        let createServiceModel (builder : IServiceBuilder) = 
            builder
                .AddService<'S>()
                .AddServiceEndpoint<'S, 'I>(new BasicHttpBinding(), "/basichttp")
                .AddServiceEndpoint<'S, 'I>(new NetTcpBinding(), "/nettcp")
            |> ignore

        member _.ConfigureServices(services : IServiceCollection) =
            do services.AddServiceModelServices() |> ignore

        member _.Configure(app : IApplicationBuilder, env : IHostingEnvironment) =
            do app.UseServiceModel(fun builder -> createServiceModel builder) |> ignore

然后可以按以下方式使用:

open System.Net
open CoreWCF.Configuration
open Microsoft.AspNetCore
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Server.Kestrel.Core

module Builder =
    let CreateWebHostBuilder() : IWebHostBuilder =
        let applyOptions (options : KestrelServerOptions) =
            let address : IPAddress = IPAddress.Parse("192.168.1.89")
            let port = 8080
            let endPoint : IPEndPoint = new IPEndPoint(address, port)
            options.Listen(endPoint)

        WebHost
            .CreateDefaultBuilder()
            .UseKestrel(fun options -> applyOptions options)
            .UseNetTcp(8808)
            .UseStartup<Startup<EchoWcfService, IEchoWcfService>>()

EchoWcfService 当然会实现接口 IEchoWcfService。没有通用约束的 Startup 会有问题,因为它不限制像这样写的内容:.UseStartup<Startup<EchoWcfService, string>>(),当然,这会在运行时出错。

更新

考虑到评论和参考资料,我尝试用混合 F# + C# 的方法来解决这个问题。因此,我们可以很容易地在 C# 中创建一个通用类:

public class WcfStartup<TService, TInterface>
    where TService : class
    //where TService : class, TInterface
{
    private void CreateServiceModel(IServiceBuilder builder)
    {
        builder
            .AddService<TService>()
            .AddServiceEndpoint<TService, TInterface>(new BasicHttpBinding(), "/basichttp")
            .AddServiceEndpoint<TService, TInterface>(new NetTcpBinding(), "/nettcp");
    }

    public void ConfigureServices(IServiceCollection services) =>
        services.AddServiceModelServices();

    public void Configure(IApplicationBuilder app, IHostingEnvironment env) =>
        app.UseServiceModel(CreateServiceModel);
}

在引用了包含该类的C#项目后,将.UseStartup<Startup<EchoWcfService, IEchoWcfService>>()更改为.UseStartup<WcfStartup<EchoWcfService, IEchoWcfService>>()

现在,如果我注释掉where TService : class并取消注释where TService : class, TInterface,那么F#项目就不再编译,并显示This expression was expected to have type 'EchoWcfService' but here has type 'IEchoWcfService',这与原始的F#编译错误基本相同,只是呈现了不同的风格。

我认为这是F#编译器的一个bug...


一个最小化的复现是 type T<'A, 'B when 'A :> 'B>() = class end,值得在 dotnet/fsharp 存储库上发布。 - Charles Roddie
1个回答

1

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