在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