2017-11-14 67 views
5

Piszę aplikację Xamarin.Forms, używając XAML dla moich widoków, i próbuję napisać IValueConverter, którego zadanie powinno powrócić false, jeśli dane wejściowe są "puste" dla typów, w których semantyka ma sens (łańcuchy/listy/sekwencje/tablice/IEnumerables). Zacząłem z poniższych, która zwraca wartość false dla pustych strun, ale nie mogę dowiedzieć się, jak rozszerzyć to na listach, sekwencji, tablic i IEnumerables:Określ, czy lista, sekwencja, tablica lub IEnumerable jest pusta.

type FalseIfEmptyConverter() = 
    interface IValueConverter with 
    member __.Convert(value:obj, _, _, _) = 
     match value with 
     | :? string as s -> (s <> "" && not (isNull s)) |> box 
     // TODO: extend to enumerables 
     | x -> invalidOp <| "unsupported type " + x.GetType().FullName 

    member __.ConvertBack(_, _, _, _) = 
     raise <| System.NotImplementedException() 

Czego próbowałem że don” t praca:

  • :? list<_> nie odpowiada (pudełkowej) wykaz (przynajmniej nie z int) i generuje ostrzeżenie This construct causes code to be less generic than indicated by its type annotations. The type variable implied by the use of a '#', '_' or other type annotation at or near [...] has been constrained to be type 'obj'
  • :? list<obj> nie wywołuje ostrzeżenie, ale również nie pasuje do wersji pudełkowej listę ints
  • To Jest taka sama z :? seq<_> i :? seq<obj>
  • To samo z :? System.Collections.Generic.IEnumerable<obj> i IEnumerable<_> (i jeśli umieścić go poniżej podobnym seq meczu, jak podano powyżej, ostrzega, że ​​reguła nie zostanie dopasowana, co ma sens, ponieważ AFAIK seq odpowiada do IEnumerable)
+2

'wartość dopasowania z | :? System.Collections.Wymontowane jako s -> s.GetEnumerator(). MoveNext() |> nie | x -> invalidOp <| "nieobsługiwany typ" + x.GetType(). FullName' –

Odpowiedz

6

Korzystanie pomysł Foggy Finder używać non-generic IEnumerable:

let isEmpty (x:obj) = 
    match x with 
    | null -> true 
    | :? System.Collections.IEnumerable as xs -> xs |> Seq.cast |> Seq.isEmpty 
    | _ -> invalidOp <| "unsupported type " + x.GetType().FullName 

isEmpty "" // true 
isEmpty [] // true 
isEmpty (set []) // true 
isEmpty [||] // true 
isEmpty null // true 

isEmpty "a" // false 
isEmpty [|1|] // false 

isEmpty 1 // exception 

wszystkich typów, które chcesz przetestować są podtypy Seq<'a>, który jest dokładnie taki sam jak IEnumerable<'a> (w tym string, który jest seq<char>). Ale jest to również podtyp typu nietypowego o nazwie IEnumerable (zauważ brak parametru typu). Jest to podobne do IEnumerable<obj>, gdzie każdy element został zapakowany. Dlatego możemy rzucić wszystkie z nich na IEnumerable, a następnie użyć Seq.cast, aby przekonwertować to na IEnumerable<obj>, abyśmy mogli użyć Seq.empty, który działa tylko na typie ogólnym.

+0

Działa wspaniale, dzięki! Ale tak naprawdę nie rozumiem, dlaczego musi używać 'IEnumerable' zamiast' IEnumerable '. Czy mógłbyś trochę rozwinąć? – cmeeren

+2

Podczas wykonywania testu typu, 'string' to' seq ', a' char' jest 'obj', ale' string' nie jest 'seq '. Sprawdzanie typów przodków nie rozciąga się na parametry typu, nawet jeśli mogłoby to teoretycznie. Nie wiem, dlaczego F # zachowuje się w ten sposób, czy też faktycznie z powodu .NET, lub jeśli jest tu podstawowa trudność, a nie brak funkcji. – TheQuickBrownFox

+0

Dzięki, że to oczyszcza i jest mniej więcej tym, co podejrzewałem. – cmeeren