我正在尝试调用一个接受通用 IEnumerable<T>
的 .NET 方法从 F# 使用 seq<U>
这样 U 是 T 的子类。这不像我预期的那样工作:
使用以下简单的打印机:
let printEm (os: seq<obj>) =
for o in os do
o.ToString() |> printfn "%s"
这些是我得到的结果:
Seq.singleton "Hello World" |> printEm // error FS0001;
//Expected seq<string> -> 'a but given seq<string> -> unit
Seq.singleton "Hello World" :> seq<obj> |> printEm // error FS0193;
//seq<string> incompatible with seq<obj>
Seq.singleton "Hello World" :?> seq<obj> |> printEm // works!
Seq.singleton 42 :> seq<obj> |> printEm // error FS0193
Seq.singleton 42 :?> seq<obj> |> printEm // runtime InvalidCastException!
//Unable to cast object of type 'mkSeq@541[System.Int32]'
// to type 'System.Collections.Generic.IEnumerable`1[System.Object]'.
理想情况下,我希望第一个语法能够工作——或者尽可能接近它的语法,并带有编译时类型检查。我不明白编译器在哪里找到
seq<string> -> unit
该行中的函数,但显然 IEnumerable 的协方差不起作用,并且以某种方式导致该错误消息。使用显式强制转换会导致合理的错误消息 - 但它也不起作用。使用运行时强制转换有效 - 但仅适用于字符串,整数因异常而失败(讨厌)。我正在尝试与其他 .NET 代码互操作;这就是为什么我需要特定的 IEnumerable 类型。
在 F# 中转换协变或逆变接口(interface)(例如 IEnumerable)的最干净且最有效的方法是什么?
最佳答案
使用 Seq.cast
为了这。例如:
Seq.singleton "Hello World" |> Seq.cast |> printEm
诚然,这放弃了类型安全:
type Animal() = class end
type Dog() = inherit Animal()
type Beagle() = inherit Dog()
let printEm (os: seq<Dog>) =
for o in os do
o.ToString() |> printfn "%s"
Seq.singleton (Beagle()) |> Seq.cast |> printEm // ok
Seq.singleton (Animal()) |> Seq.cast |> printEm // kaboom!
但这是权宜之计。
或者,您可以使用 flexible types :
type Animal() = class end
type Dog() = inherit Animal()
type Beagle() = inherit Dog()
let printEm (os: seq<#Dog>) = // note #Dog
for o in os do
o.ToString() |> printfn "%s"
Seq.singleton (Beagle()) |> printEm // ok
Seq.singleton (Animal()) |> printEm // type error
这只是通用“forall types
'a when 'a :> Dog
”的简写。最后,您始终可以映射上播,例如
let printEm (os: seq<obj>) =
for o in os do
o.ToString() |> printfn "%s"
Seq.singleton "Hello" |> Seq.map box |> printEm // ok
在哪里
box
向上转换为 obj
.
关于F# 和接口(interface)协方差 : what to do?(特别是 seq<> aka IEnumerable<>),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4203560/