...or, how do I filter a sequence of classes by the interfaces they implement?
Let's say I have a sequence of objects that inherit from Foo, a seq<#开发者_如何学运维Foo>
. In other words, my sequence will contain one or more of four different subclasses of Foo.
Each subclass implements a different independent interface that shares nothing with the interfaces implemented by the other subclasses.
Now I need to filter this sequence down to only the items that implement a particular interface.
The C# version is simple:
void MergeFoosIntoList<T>(IEnumerable<Foo> allFoos, IList<T> dest)
where T : class
{
foreach (var foo in allFoos)
{
var castFoo = foo as T;
if (castFoo != null)
{
dest.Add(castFoo);
}
}
}
I could use LINQ from F#:
let mergeFoosIntoList (foos:seq<#Foo>) (dest:IList<'a>) =
System.Linq.Enumerable.OfType<'a>(foos)
|> Seq.iter dest.Add
However, I feel like there should be a more idiomatic way to accomplish it. I thought this would work...
let mergeFoosIntoList (foos:seq<#Foo>) (dest:IList<'a>) =
foos
|> Seq.choose (function | :? 'a as x -> Some(x) | _ -> None)
|> Seq.iter dest.Add
However, the complier complains about :? 'a
- telling me:
This runtime coercion or type test from type 'b to 'a involves an indeterminate type based on information prior to this program point. Runtime type tests are not allowed on some types. Further type annotations are needed.
I can't figure out what further type annotations to add. There's no relationship between the interface 'a
and #Foo
except that one or more subclasses of Foo implement that interface. Also, there's no relationship between the different interfaces that can be passed in as 'a
except that they are all implemented by subclasses of Foo.
I eagerly anticipate smacking myself in the head as soon as one of you kind people points out the obvious thing I've been missing.
You can do this:
let foos = candidates |> Seq.filter (fun x -> x :? Foo) |> Seq.cast<Foo>
Typically just adding a 'box' is sufficient (e.g. change function
to fun x -> match box x with
), but let me try it out...
Yeah; basically you cannot sideways cast from one arbitrary generic type to another, but you can upcast to System.Object (via box
) and then downcast to anything you like:
type Animal() = class end
type Dog() = inherit Animal()
type Cat() = inherit Animal()
let pets : Animal list =
[Dog(); Cat(); Dog(); Cat(); Dog()]
printfn "%A" pets
open System.Collections.Generic
let mergeIntoList (pets:seq<#Animal>) (dest:IList<'a>) =
pets
|> Seq.choose (fun p -> match box p with
| :? 'a as x -> Some(x) | _ -> None) //'
|> Seq.iter dest.Add
let l = new List<Dog>()
mergeIntoList pets l
l |> Seq.iter (printfn "%A")
From https://gist.github.com/kos59125/3780229
let ofType<'a> (source : System.Collections.IEnumerable) : seq<'a> =
let resultType = typeof<'a>
seq {
for item in source do
match item with
| null -> ()
| _ ->
if resultType.IsAssignableFrom (item.GetType ())
then
yield (downcast item)
}
Another option for those inclined:
Module Seq =
let ofType<'a> (items: _ seq)= items |> Seq.choose(fun i -> match box i with | :? 'a as a -> Some a |_ -> None)
I have an open source library available on nuget, FSharp.Interop.Compose
That Converts most Linq
methods into a idomatic F# form. Including OfType
Test Case:
[<Fact>]
let ofType () =
let list = System.Collections.ArrayList()
list.Add(1) |> ignore
list.Add("2") |> ignore
list.Add(3) |> ignore
list.Add("4") |> ignore
list
|> Enumerable.ofType<int>
|> Seq.toList |> should equal [1;3]
精彩评论