我希望我能做到以下几点:
type Logger = {
warn : System.Type -> string -> unit
// also methods for error, info etc
}
// during application startup
let warn (serviceProvider : IServiceProvider) (t : Type) s =
let loggerType = typeof<ILogger<'a>>.MakeGenericType(t)
let logger = serviceProvider.GetService(loggerType) :?> ILogger
logger.LogWarning(s)
let log = { warn = warn; ... } // with similar setups for info, error etc
然后,我可以部分申请log
,并且我可以在应用程序的深处
log.warn typeof<Foo> "a warning message"
会使用类型Foo
记录消息以确定log category。
麻烦的表达是
typeof<ILogger<'a>>.MakeGenericType(t)
其中F#似乎将泛型定义中的'a
的类型推断为obj
,因此对MakeGenericType
的调用失败,并且出现了一个异常,即类型定义我是调用它不是通用的。
等效的C#将是
typeof(ILogger<>).MakeGenericType(t)
但typeof<ILogger<>>
无法在F#中编译。
如何实例化这样的泛型类型,其中type参数仅在运行时以F#为人所知?
答案 0 :(得分:3)
C#中F#的等效typeof(ILogger<>)
是使用typedefof
函数:
typedefof<ILogger<_>>.MakeGenericType(t)
这仍然是一个正常的函数,它采用完全实例化的类型 - _
占位符将自动填充obj
,但typedefof
函数不会返回类型,但是泛型定义。您也可以在GetGenericTypeDefinition
的结果上调用typeof
,但typedefof
是一个更好的捷径!
答案 1 :(得分:3)
您正在寻找的语法是typedefof<ILogger<_>>
- 这将为您提供非实例化类型ILooger<_>
,然后您可以在其上调用MakeGenericType
。
但是,我建议您重新考虑您的架构。为什么要在运行时实例化类型?这比较慢,不太安全。在这种情况下,我认为没有理由这样做。
最好将您的类型作为通用参数传递。当然,您不能将记录成员作为通用功能,因此这是一个无赖。但是,F#确实为这类东西提供了另一种设施 - 接口。接口方法可以完全通用:
type Logger =
abstract member warn<'logger> : string -> unit
let mkLog (serviceProvider : IServiceProvider) =
{ new Logger with
member warn<'logger> s =
let logger = serviceProvider.GetService(typeof<ILogger<'logger>>) :?> ILogger
logger.LogWarning(s)
}
let log = mkLog serviceProvider
// usage:
log.warn<Foo> "a warning message"
当然,创建这种类型的实例的语法有点笨拙,但你只需要做一次。