问题
This is a follow-up on this previous question, but with a different twist.
I would like to write a function which, given an object oMap, returns its count if oMap happens to be of type Map<'k,'v>, and -1 otherwise. My constraint : oMap type can only be 'discovered' at runtime.
As apparently "there is no built-in way to pattern match on a generic Map." (see link to previous question), I am using reflection for this.
namespace genericDco
module Test1 =
let gencount (oMap : obj) : int =
let otype = oMap.GetType()
let otypenm = otype.Name
if otypenm = "FSharpMap`2" then
// should work, as oMap of type Map<'a,'b>, but does not. *How to fix this?*
Map.count (unbox<Map<_,_>> oMap)
else
// fails, as oMap is not of any type Map<'a,'b>.
-1
let testfailObj : int = gencount ("foo")
// FAILS
let testsuccessObj : int =
let oMap = [| ("k1", "v1"); ("k1", "v1") |] |> Map.ofArray
gencount (box oMap)
The error being :
System.InvalidCastException: Unable to cast object of type 'Microsoft.FSharp.Collections.FSharpMap`2[System.String,System.String]' to type 'Microsoft.FSharp.Collections.FSharpMap`2[System.IComparable,System.Object]'. at Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicFunctions.UnboxGeneric[T](Object source)
My question: How should I rewrite the above to get this to work?
PS : I am not looking for solutions where we know at compile time that oMap is of type Map<'k,'v>, e.g. :
module Test2 =
let gencount2<'k,'v when 'k : comparison> (gMap : Map<'k,'v>) : int =
Map.count gMap
let testsuccessStr : int =
let gMap = [| ("k1", "v1"); ("k2", "v2") |] |> Map.ofArray
gencount2<string,string> gMap
let testsuccessDbl : int =
let gMap = [| ("k1", 1.0); ("k2", 2.0); ("k3", 3.0) |] |> Map.ofArray
gencount2<string,double> gMap
== EDIT ==
Thanks to Asti's suggestion, that's the solution that worked for me :
let gencount (oMap : obj) : int =
let otype = oMap.GetType()
let propt = otype.GetProperty("Count")
try
propt.GetValue(oMap) :?> int
with
| _ -> -1
回答1:
Since Map.count is just defined as let count m = m.Count, we can just go for the Count property.
let gencount<'k,'v when 'k : comparison> map =
let mtype = typeof<Map<'k, 'v>>
let propt = mtype.GetProperty("Count")
if map.GetType() = mtype then
propt.GetValue(map) :?> int
else
-1
Test:
[<EntryPoint>]
let main argv =
let m = Map.ofSeq [ ("a", 1); ("b", 2)]
printfn "%d" (gencount<string, int> m)
printfn "%d" (gencount<string, string> m)
Console.ReadKey() |> ignore
0 // return exit code 0
Using _ in place of a type will simply end up as object if no additional constraint information is available. You use unbox when you strongly know what type your value is, except that the value is boxed in.
来源:https://stackoverflow.com/questions/59242217/f-generic-map-count