F # Эквивалент Enumerable.OfType <'a> - PullRequest
14 голосов
/ 26 марта 2010

... или как отфильтровать последовательность классов по интерфейсам, которые они реализуют?

Допустим, у меня есть последовательность объектов, которые наследуются от Foo, a 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 включает неопределенный тип, основанный на информации, предшествующей этой точке программы. Типовые тесты во время выполнения не разрешены для некоторых типов. Необходимы дополнительные аннотации типов.

Я не могу понять, какие еще аннотации типов добавить. Между интерфейсами 'a и #Foo нет никакой связи, за исключением того, что один или несколько подклассов Foo реализуют этот интерфейс. Кроме того, нет никакой связи между различными интерфейсами, которые могут быть переданы как 'a, за исключением того, что они все реализованы подклассами Foo.

Я с нетерпением ожидаю удара головой, как только один из вас, добрых людей, заметит очевидную вещь, по которой я скучаю.

Ответы [ 5 ]

9 голосов
/ 03 апреля 2014

Вы можете сделать это:

let foos = candidates |> Seq.filter (fun x -> x :? Foo) |> Seq.cast<Foo>
6 голосов
/ 26 марта 2010

Обычно достаточно просто добавить 'коробку' (например, изменить function на fun x -> 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")
2 голосов
/ 03 мая 2014

С 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 голос
/ 05 мая 2015

Еще один вариант для склонных:

Module Seq = 
    let ofType<'a> (items: _ seq)= items |> Seq.choose(fun i -> match box i with | :? 'a as a -> Some a |_ -> None)
0 голосов
/ 24 сентября 2015

У меня есть библиотека с открытым исходным кодом, доступная для nuget, FSharp.Interop.Compose

, который преобразует большинство Linq методов в idomatic F # форму . В том числе 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]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...