最近我试了一下 F# 中自动向上转换的可能性,我知道 F# 强制是显式的,但是我注意到在某些情况下实际上有点存在向上转换。
下面是一些代码,显示了向上转换工作的情况和不工作的情况:
type NameValue(name: string, value: obj) = class end
type GenNameValue<'T>(name: string, value: 'T) = class end
type MoreNameValue<'Name, 'Value>(name: 'Name, value: 'Value) = class end
let takeObjList (source: obj list) = printfn "%A" source
let takeNameValueList (source: NameValue list) = printfn "%A" source
let takeGenNameValueList (source: GenNameValue<obj> list) = printfn "%A" source
let takeMoreNameValueList (source: MoreNameValue<string, obj> list) = printfn "%A" source
let takeAnonymousList (source: {| Name: string; Value: obj |} list) = printfn "%A" source
let takeStructAnonymousList (source: struct {| Name: string; Value: obj |} list) = printfn "%A" source
let takeTupleList (source: (string * obj) list) = printfn "%A" source
let takeStructTupleList (source: struct (string * obj) list) = printfn "%A" source
let takeValueTuples (source: ValueTuple<string, obj> list) = printfn "%A" source
let takeRefTuples (source: Tuple<string, obj> list) = printfn "%A" source
// Implicit upcasting to obj => works
takeObjList [42; "middle"; 16us]
takeNameValueList [NameValue("a", "c"); NameValue("b", 42)]
takeGenNameValueList [GenNameValue<obj>("a", "c"); GenNameValue<obj>("a", 42)]
takeMoreNameValueList [MoreNameValue<string, obj>("a", "c"); MoreNameValue<string, obj>("a", 42)]
takeAnonymousList [{| Name = "a"; Value = "c" |}; {| Name = "b"; Value = 42 |}]
takeStructAnonymousList [{| Name = "a"; Value = "c" |}; {| Name = "b"; Value = 42 |}]
// Implicit upcasting to obj => doesn't work
// [FS0001] This expression was expected to have type 'obj' but here has type 'string'
// [FS0001] This expression was expected to have type 'obj' but here has type 'int'
takeTupleList [("a", "c"); ("b", 42)]
takeStructTupleList [("a", "c"); ("b", 42)]
takeValueTuples [ValueTuple.Create("a", "c"); ValueTuple.Create("a", 42)]
takeRefTuples [Tuple.Create("a", "c"); Tuple.Create("a", 42)]
此外,我还注意到单独向上转换元组是可行的,但当它是一个集合时会立即失败:
let build1 a b: (string * obj) list = [a; b]
let build2 a: (string * obj) list = a
// Does work
build1 ("a", "c") ("a", 42) |> ignore
// Does not work
build2 [("a", "c"); ("a", 42)] |> ignore
我想知道背后的原理。恕我直言,它看起来有点不一致,在某些情况下可以,在另一些情况下则不能。我可能遗漏了什么,我想知道是什么。
最佳答案
C# 具有隐式协变和逆变。
这使您可以自动转换 IEnumerable<string>
至 IEnumerable<object>
,或使用 Action<object>
用int
.
F# 也没有。 Vote here! .
所以你不能写:
[1; 2; 3] :> obj list //compile error
您看到的其余部分是尝试选择最佳匹配类型的类型推断。
let printlist (list: obj list) =
list |> List.iter(printfn "%A")
printlist [1; 2; 3] //compiles - literals will be inferred using type annotation
let intlist = [1; 2; 3]
printlist intlist //this will not compile
请注意,您所有的失败案例都是针对非同构集合或协变集合。
目前,F# 具有灵活的类型,本质上是用于以下方面的语法糖:
#T = 'a where 'a :> T
它让您的协方差有限。
let printlist (list: #obj list) =
list |> List.iter(printfn "%A")
let intlist = [1; 2; 3]
printlist intlist //compiles
我相信其他人可以提供更多背景信息。
关于.net - F# 和自动向上转换 : sometimes it does, 有时不会,这背后的基本原理是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61723770/