【问题标题】:F# Equivalent to Enumerable.OfType<'a>F# 等价于 Enumerable.OfType<'a>
【发布时间】:2010-03-26 05:11:57
【问题描述】:

...或者,我如何通过它们实现的接口过滤一系列类?

假设我有一个继承自 Foo 的对象序列,即seq&lt;#Foo&gt;。换句话说,我的序列将包含 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);
            }
        }
    }

我可以使用 F# 中的 LINQ:

    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 的运行时强制或类型测试涉及基于此程序点之前的信息的不确定类型。某些类型不允许运行时类型测试。需要进一步的类型注释。

我不知道要添加什么进一步的类型注释。接口'a#Foo 之间没有任何关系,只是Foo 的一个或多个子类实现了该接口。另外,可以作为'a传入的不同接口之间没有任何关系,只是它们都是由Foo的子类实现的。

一旦你们中的一个好心人指出我一直遗漏的显而易见的事情,我热切期待自己的脑袋。

【问题讨论】:

    标签: f#


    【解决方案1】:

    你可以这样做:

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

    【讨论】:

    • let ofType&lt;'a&gt; (items: _ seq) = items |&gt; Seq.filter(fun x -&gt; x :? 'a) |&gt; Seq.cast&lt;'a&gt; 给了我从类型'b 到'a 的运行时强制或类型测试涉及基于此程序点之前的信息的不确定类型。某些类型不允许运行时类型测试。需要进一步的类型注释。我认为在我当前的上下文中需要box x
    • 添加另一个演员可以工作:``let ofType (items: _ seq) = items |> Seq.cast |> Seq.filter(fun x -> x : ? 'a) |> Seq.cast\
    【解决方案2】:

    通常只添加一个“框”就足够了(例如,将 function 更改为 fun x -&gt; match box x with),但让我尝试一下...

    是的;基本上你不能从一种任意泛型类型横向转换为另一种,但你可以向上转换为 System.Object(通过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")
    

    【讨论】:

    • @Brian - 但你会走这条路吗?在这种情况下,OfType 似乎更漂亮了...
    • 如果只是语法问题而不是性能问题,使用ofType&lt;'a&gt; 函数扩展 Seq 模块就很容易了。那么方法体就是:pets |&gt; Seq.ofType&lt;'a&gt; |&gt; Seq.iter dest.Add
    • Enumerable.OfType 更短,对我来说,“最短”通常是“首选/最佳”的代理。我只是在回答问题。 (Enumerable.OfType 还假定依赖于 .NET 3.5 或更高版本。)
    【解决方案3】:

    来自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)
       }
    

    【讨论】:

      【解决方案4】:

      另一种选择:

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

      【讨论】:

        【解决方案5】:

        我在 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]
        

        【讨论】:

          猜你喜欢
          • 2011-02-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-08
          相关资源
          最近更新 更多