如何在F#中运行时实例化泛型类型?

时间:2018-03-14 16:11:51

标签: generics f#

我希望我能做到以下几点:

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#为人所知?

2 个答案:

答案 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"

当然,创建这种类型的实例的语法有点笨拙,但你只需要做一次。