Reputation: 60564
I wish I could do the following:
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
Then, I'd be able to partially apply with log
, and deep inside the app I could
log.warn typeof<Foo> "a warning message"
which would log a message using the type Foo
to determine the log category.
The troublesome expression is
typeof<ILogger<'a>>.MakeGenericType(t)
where F# seems to infer the type of 'a
in the generic definition to be obj
, and so the call to MakeGenericType
fails with an exception saying that the type definition I'm calling it on is not generic.
The equivalent C# would be
typeof(ILogger<>).MakeGenericType(t)
but typeof<ILogger<>>
doesn't compile in F#.
How do I instantiate a generic type like this, where the type argument is only known at runtime, in F#?
Upvotes: 1
Views: 719
Reputation: 80714
The syntax you're looking for is typedefof<ILogger<_>>
- that will give you the non-instantiated type ILooger<_>
, on which you can then call MakeGenericType
.
However, I would rather recommend you rethink your architecture. Why instantiate types at runtime? That's slower and less safe. In this case, I see no reason to do it.
Better pass your types as generic parameters. Of course you can't have record members be generic functions, so that's a bummer. However, F# does offer another facility for this sort of thing - interfaces. Interface methods can be fully generic:
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"
Granted, the syntax for creating an instance of such type is a bit clunky, but you only have to do it once.
Upvotes: 5
Reputation: 243041
The F# equivalent of typeof(ILogger<>)
in C# is to use the typedefof
function:
typedefof<ILogger<_>>.MakeGenericType(t)
This is still a normal function that takes a fully instantiated type - the _
placeholder will be automatically filled with obj
, but the typedefof
function does not return the type, but its generic type definition. You could also do the same by calling GetGenericTypeDefinition
on the result of typeof
, but typedefof
is a nicer shortcut!
Upvotes: 3