我正在编写一个适配器类,将IEnumerable<'T>映射到IDataReader,完整的源代码在https://gist.github.com/jsnape/56f1fb4876974de94238供参考,但我想询问编写它的一部分的最佳方法。即两个函数:
member this.GetValue(ordinal) =
let value = match enumerator with
| Some e -> getters.[ordinal](e.Current)
| None -> raise (new ObjectDisposedException("EnumerableReader"))
match value with
| :? Option<string> as x -> if x.IsNone then DBNull.Value :> obj else x.Value :> obj
| :? Option<int> as x -> if x.IsNone then DBNull.Value :> obj else x.Value :> obj
| :? Option<decimal> as x -> if x.IsNone then DBNull.Value :> obj else x.Value :> obj
| :? Option<obj> as x -> if x.IsNone then DBNull.Value :> obj else x.Value
| _ -> value
这个函数必须返回一个对象,但是由于传递的值可以是任何f#选项类型,这些类型不能被下游函数(如SqlBulkCopy)理解,我需要解包该选项并将其转换为null/DBNull。
上面的代码可以工作,但我觉得它有点笨拙,因为我必须为不同的类型(float等)添加新的专门化。我试过使用通配符|:?Option<_>作为匹配中的x ->,但编译器给了我一个"不太通用的警告",代码只匹配Option怎么才能写得更地道呢?我怀疑活动模式可能起作用,但我从未使用过它们。
对于另一个函数也类似:
member this.IsDBNull(ordinal) =
match (this :> IDataReader).GetValue(ordinal) with
| null -> true
| :? DBNull -> true
| :? Option<string> as x -> x.IsNone
| :? Option<int> as x -> x.IsNone
| :? Option<decimal> as x -> x.IsNone
| :? Option<obj> as x -> x.IsNone
| _ -> false
我不关心它是什么类型的选项我只想检查是否为IsNone
我认为你应该使用一些像这样的反射技巧:
open System
let f (x:obj) =
let tOption = typeof<option<obj>>.GetGenericTypeDefinition()
match x with
| null -> printfn "null"; true
| :? DBNull -> printfn "dbnull"; true
| _ when x.GetType().IsGenericType && x.GetType().GetGenericTypeDefinition() = tOption ->
match x.GetType().GenericTypeArguments with
| [|t|] when t = typeof<int> -> printfn "option int"; true
| [|t|] when t = typeof<obj> -> printfn "option obj"; true
| _ -> printfn "option 't" ; true
| _ -> printfn "default"; false
let x = 4 :> obj
let x' = f x //default
let y = Some 4 :> obj
let y' = f y // option int
let z = Some 0.3 :> obj
let z' = f z // option 't
事实上,如果你只是有兴趣检查所有选项类型的IsNone情况,并且不想使用反射,你不需要其他情况,它们将落在null情况下,因为None被编译为null。例如,用前面的函数试试:
let y1 = (None: int option) :> obj
let y1' = f y1 // null
let z1 = (None: float option) :> obj
let z1' = f z1 // null
正在处理第一种情况(null情况)
对于GetValue成员,我看了一下你的要点,因为你已经在包含该成员的类型中定义了泛型'T ',你可以只写:
match value with
| :? Option<'T> as x -> if x.IsNone then DBNull.Value :> obj else x.Value :> obj
正如Gustavo的回答所建议的那样,您应该为此使用反射。如果参数'a
在编译时未知,则没有其他方法可以从obj
类型强制转换为option<'a>
类型。相反,您必须检查作为System.Type
对象的参数,然后决定下一步要做什么。
一般的做法是设置一个函数,它可以接受任何选项类型作为参数,并返回与该选项类型的参数无关的值。在确定参数类型之后,可以通过反射调用该函数。
要定义可以接受任何选项类型作为参数的函数,helper接口很有用,因为我们可以在该接口中定义泛型方法:
type IGenericOptionHandler<'result> =
abstract Handle<'a> : 'a option -> 'result
注意,接口作为一个整体是泛型的返回类型'result
的Handle
方法,但内部的'a
参数只在方法本身的定义中提到。
let handleGeneric
(handle : IGenericOptionHandler<'result>)
(x : obj) // something that might be an option type
(defaultValue : 'result) // used if x is not an option type
: 'result =
let t = x.GetType()
if t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<_ option>
then
match t.GetGenericArguments() with
| [|tArg|] ->
handle
.GetType()
.GetMethod("Handle")
.MakeGenericMethod([|tArg|])
.Invoke(handle, [|x|])
:?> 'result
| args -> failwith "Unexpected type arguments to option: %A" args
else defaultValue
最后,我们可以方便地使用对象表达式调用它,例如,下面将充当类似于上面的IsDBNull
的通用选项类型检测器-您需要在defaultValue
参数中添加DBNull
的特殊情况以完全复制它。
Option.handleGeneric
{ new IGenericOptionHandler<bool> with member this.Handle _ = true }
(Some 5)
false