F# 泛型约束使一个泛型类型从另一个继承
F# Generic constraint to have one generic type inherit from another
在 C# 中,当一个泛型参数继承自另一个时,定义泛型 class 很简单,例如:
public class MyClass<TClass, TInterface> where TClass : class, TInterface
{
}
这用于“强制”class TClass
实现接口 TInterface
。我想在 F# 中做同样的事情,但令人惊讶的是它似乎不起作用。例如下面的代码:
type Startup<'S, 'I when 'I : not struct and 'S : not struct and 'S :> 'I>() =
member _.x = 0
结果为 FS0663
- This type parameter has been used in a way that constrains it to always be ''I when 'I : not struct'
。不知道上面类似C#的泛型结构是否可以在F#中实现
此时有人可能想知道我为什么需要它?这就是为什么。我想要一个通用的 F# 互操作 CoreWCF
like:
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
没有 'S
实现 'I
的通用约束的问题是,没有什么可以阻止编写类似 .UseStartup<Startup<EchoWcfService, string>>()
的东西,当然,这会在运行时爆炸。
更新
鉴于评论和参考资料,我尝试以 F# + C# 的混合方式解决问题。因此,我们可以轻松地在 C# 中创建泛型 class:
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);
}
然后在用 class.
引用 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# 编译器中的错误...
来自 F# 规范:
形式 type :> 'b 的新约束再次解决为 type = 'b.
有一些流行的 F# 语言建议旨在解决此问题,请参阅:
在 C# 中,当一个泛型参数继承自另一个时,定义泛型 class 很简单,例如:
public class MyClass<TClass, TInterface> where TClass : class, TInterface
{
}
这用于“强制”class TClass
实现接口 TInterface
。我想在 F# 中做同样的事情,但令人惊讶的是它似乎不起作用。例如下面的代码:
type Startup<'S, 'I when 'I : not struct and 'S : not struct and 'S :> 'I>() =
member _.x = 0
结果为 FS0663
- This type parameter has been used in a way that constrains it to always be ''I when 'I : not struct'
。不知道上面类似C#的泛型结构是否可以在F#中实现
此时有人可能想知道我为什么需要它?这就是为什么。我想要一个通用的 F# 互操作 CoreWCF
like:
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
没有 'S
实现 'I
的通用约束的问题是,没有什么可以阻止编写类似 .UseStartup<Startup<EchoWcfService, string>>()
的东西,当然,这会在运行时爆炸。
更新
鉴于评论和参考资料,我尝试以 F# + C# 的混合方式解决问题。因此,我们可以轻松地在 C# 中创建泛型 class:
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);
}
然后在用 class.
引用 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# 编译器中的错误...
来自 F# 规范:
形式 type :> 'b 的新约束再次解决为 type = 'b.
有一些流行的 F# 语言建议旨在解决此问题,请参阅: