我正在尝试模式匹配我关心SQL生成的几种类型。理想情况下我想这样做:
let rec getSafeValue record (prop: PropertyInfo) =
match prop.GetValue(record, null) with
| :? string as str -> "'" + str + "'"
| :? Option<_> as opt ->
match opt with
| Some v -> getSafeValue v prop
| None -> "null"
| _ as v -> v.ToString()
问题在于,Option<_>
的类型参数会获得与record
匹配的约束,最终只有obj
。
我知道我可以做一些基于反射的检查(检查它是一个通用类型,并且它是基于名称的选项类型),但我宁愿避免这种情况,如果可能的话
答案 0 :(得分:9)
不,使用F#的内置结构没有好办法。但是,您可以为此类事物构建自己的可重用活动模式:
open Microsoft.FSharp.Reflection
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.DerivedPatterns
open Microsoft.FSharp.Quotations.Patterns
let (|UC|_|) e o =
match e with
| Lambdas(_,NewUnionCase(uc,_)) | NewUnionCase(uc,[]) ->
if (box o = null) then
// Need special case logic in case null is a valid value (e.g. Option.None)
let attrs = uc.DeclaringType.GetCustomAttributes(typeof<CompilationRepresentationAttribute>, false)
if attrs.Length = 1
&& (attrs.[0] :?> CompilationRepresentationAttribute).Flags &&& CompilationRepresentationFlags.UseNullAsTrueValue <> enum 0
&& uc.GetFields().Length = 0
then Some []
else None
else
let t = o.GetType()
if FSharpType.IsUnion t then
let uc2, fields = FSharpValue.GetUnionFields(o,t)
let getGenType (t:System.Type) = if t.IsGenericType then t.GetGenericTypeDefinition() else t
if uc2.Tag = uc.Tag && getGenType (uc2.DeclaringType) = getGenType (uc.DeclaringType) then
Some(fields |> List.ofArray)
else None
else None
| _ -> failwith "The UC pattern can only be used against simple union cases"
现在你的功能看起来像这样:
let rec getSafeValue (item:obj) =
match item with
| :? string as str -> "'" + str + "'"
| UC <@ Some @> [v] -> getSafeValue v
| UC <@ None @> [] -> "null"
| _ as v -> v.ToString()
答案 1 :(得分:0)
如果没有协方差,这在F#中无效。假设您很高兴v
属于obj
类型,您希望能够将Option<anything>
视为Option<obj>
。如果没有协方差,Option<anything>
和Option<obj>
是独立的类型。
答案 2 :(得分:0)
当我把你的代码放在F#Interactive中时,它似乎使'record'成为一个通用的参数。也许它在普通编译器中的工作方式不同。无论如何,由于obj
的第一个参数是类型GetValue
,它可能会提取obj
类型。
对不起,我现在无法测试,但请试一试。 box
函数使用通用参数,因此可以做到这一点。
let rec getSafeValue record (prop: PropertyInfo) =
match prop.GetValue(box record, null) with
| :? string as str -> "'" + str + "'"
| :? Option<_> as opt ->
match opt with
| Some v -> getSafeValue v prop
| None -> "null"
| _ as v -> v.ToString()
答案 3 :(得分:0)
有些v - &gt; getSafeValue v prop仅在v与记录类型相同时才有效。 (或从该类型派生)否则第一行将失败。你不能说prop.GetValue(record,null),除非prop指向的属性在第一个参数的上下文中有意义(也就是类型的一部分)。
如果它是同一类型,你可以这样做:
let rec getSafeValue (record:'a) (prop: PropertyInfo) =
match prop.GetValue(box record, null) with
| :? string as str -> "'" + str + "'"
| :? Option<'a> as opt ->
match opt with
| Some v -> getSafeValue v prop
| None -> "null"
| _ as v -> v.ToString()
但如果v的类型来自'a它将匹配最后一个案例,那么为了使上述工作,他们需要完全相同的类型