2010-03-26 78 views
13

...或者,如何通过它们实现的接口过滤一系列类?F#相当于Enumerable.OfType <'a>

假设我有一个从Foo继承的对象序列,seq<#Foo>。换句话说,我的序列将包含Foo的四个不同的子类中的一个或多个。

每个子类实现一个不同的独立接口,它与其他子类实现的接口不共享任何内容。

现在我需要将这个序列过滤到仅实现特定接口的项目。

C#版本很简单:

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); 
      } 
     } 
    } 

我可以使用LINQ从F#:

let mergeFoosIntoList (foos:seq<#Foo>) (dest:IList<'a>) = 
      System.Linq.Enumerable.OfType<'a>(foos) 
      |> Seq.iter dest.Add 

不过,我觉得应该有一个更惯用的方式来完成它。我认为这会工作...

let mergeFoosIntoList (foos:seq<#Foo>) (dest:IList<'a>) = 
      foos 
      |> Seq.choose (function | :? 'a as x -> Some(x) | _ -> None) 
      |> Seq.iter dest.Add 

然而,编译器抱怨:? 'a - 告诉我:

这个运行时强制或类型的型式检验“B为”一个涉及基于不确定型在这个节目点之前的信息。某些类型不允许运行时类型测试。需要进一步的类型注释。

我找不出什么更多类型的注释添加。接口'a#Foo之间没有任何关系,只是Foo的一个或多个子类实现了该接口。而且,除了它们全部由Foo的子类实现之外,不同接口之间可以以'a传递的不同接口之间没有关系。

只要你们其中一位善良的人指出我失踪的明显的事情,我就热切地期待自己在脑海中闪过。

回答

6

通常只是增加一个“盒子”就足够了(例如,改变functionfun x -> match box x with),但让我尝试一下......

呀;基本上你不能从一个任意泛型类型转换到另一个泛型类型,但是你可以向上转换为System。对象(通过box),然后垂头丧气到你喜欢的东西:

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") 
+0

该诀窍。谢谢! – 2010-03-26 05:51:32

+1

@布莱恩 - 但你会走这条路?在这种情况下,OfType似乎更漂亮... – Benjol 2010-03-26 10:17:51

+0

如果仅仅是语法问题而不是性能问题,那么使用'typeType <'a>'函数来扩展Seq模块就足够简单了。然后,方法体将会是:'pets |> Seq.ofType <'a> |> Seq.iter dest.Add' – 2010-03-26 16:46:25

8

你可以这样做:

let foos = candidates |> Seq.filter (fun x -> x :? Foo) |> Seq.cast<Foo> 
+0

'let ofType <'a>(items:_ seq)= items |> Seq.filter(fun x - > x:?'a)|> Seq.cast <'a>'给我*这种运行时强制或从类型'b到'a的类型测试涉及基于此程序点之前的信息的不确定类型。某些类型不允许运行时类型测试。需要进一步的类型注释*我认为在我目前的情况下需要'box x' – Maslow 2015-05-05 17:20:26

+1

添加另一个强制转换:''let ofType <'a>(items:_ seq)= items |> Seq.cast |> Seq。过滤器(fun x - > x:?'a)|> Seq.cast <'a> \ – 2016-12-27 18:31:46

2

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) 
    } 
1

另一种选择对于那些倾斜:

Module Seq = 
    let ofType<'a> (items: _ seq)= items |> Seq.choose(fun i -> match box i with | :? 'a as a -> Some a |_ -> None) 
0

我在nuget上有一个开源库,FSharp.Interop.Compose

将大多数Linq方法转换为idomatic F# form。包括OfType

测试用例:

[<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]