2010-11-17 56 views
11

J'essaie d'appeler une méthode .NET acceptant un IEnumerable<T> générique de F # en utilisant un seq<U> tel que U est une sous-classe de T. Cela ne fonctionne pas comme je l'attendais serait:F # et covariance d'interface: que faire? (spécifiquement seq <> aka IEnumerable <>)

Avec l'imprimante simple:

let printEm (os: seq<obj>) = 
    for o in os do 
     o.ToString() |> printfn "%s" 

Ce sont les résultats que je reçois:

Seq.singleton "Hello World" |> printEm // error FS0001; 
//Expected seq<string> -> 'a but given seq<string> -> unit 

Seq.singleton "Hello World" :> seq<obj> |> printEm // error FS0193; 
//seq<string> incompatible with seq<obj> 

Seq.singleton "Hello World" :?> seq<obj> |> printEm // works! 

Seq.singleton 42 :> seq<obj> |> printEm // error FS0193 
Seq.singleton 42 :?> seq<obj> |> printEm // runtime InvalidCastException! 
//Unable to cast object of type '[email protected][System.Int32]' 
// to type 'System.Collections.Generic.IEnumerable`1[System.Object]'. 

Idéalement, je voudrais la première syntaxe pour travailler - ou quelque chose d'aussi proche de comme po ssible, avec la vérification du type de temps de compilation. Je ne comprends pas où le compilateur trouve une fonction seq<string> -> unit dans cette ligne, mais apparemment covariance pour IEnumerable ne fonctionne pas et que cela entraîne en quelque sorte dans ce message d'erreur. L'utilisation d'une distribution explicite génère un message d'erreur raisonnable, mais cela ne fonctionne pas non plus. Utiliser un cast d'exécution fonctionne - mais seulement pour les chaînes, ints échouent avec une exception (méchant). J'essaie d'interopérer avec un autre code .NET; C'est pourquoi j'ai besoin de types IEnumerable spécifiques. Quel est le moyen le plus propre et de préférence efficace de lancer des interfaces co- ou contravariantes telles que IEnumerable in F #?

+1

Comme le dit Desco, la solution la plus propre est de modifier (ou supprimer) la déclaration de type sur 'os' (si possible). Sur une note non liée, 'o.ToString |> printfn"% s "' peut être écrit de façon plus concise comme 'o |> printfn"% O "'. – kvb

+0

@kvb Je pense que @Eamon n'a pas de problème avec la fonction 'printfn'. –

Répondre

8

Utilisez Seq.cast pour cela. Par exemple:

Seq.singleton "Hello World" |> Seq.cast |> printEm 

Certes, cela donne en sécurité de type:

type Animal() = class end 
type Dog() = inherit Animal() 
type Beagle() = inherit Dog() 

let printEm (os: seq<Dog>) = 
    for o in os do 
     o.ToString() |> printfn "%s" 

Seq.singleton (Beagle()) |> Seq.cast |> printEm // ok 
Seq.singleton (Animal()) |> Seq.cast |> printEm // kaboom! 

mais il est opportun.

Vous pouvez également utiliser flexible types:

type Animal() = class end 
type Dog() = inherit Animal() 
type Beagle() = inherit Dog() 

let printEm (os: seq<#Dog>) = // note #Dog 
    for o in os do 
     o.ToString() |> printfn "%s" 

Seq.singleton (Beagle()) |> printEm // ok 
Seq.singleton (Animal()) |> printEm // type error 

qui est juste un raccourci pour le générique "forall types 'a when 'a :> Dog".

Et enfin, vous pouvez toujours mapper le upcast, par ex.

let printEm (os: seq<obj>) = 
    for o in os do 
     o.ToString() |> printfn "%s" 

Seq.singleton "Hello" |> Seq.map box |> printEm // ok 

box upcasts à obj.

+4

C'est lent, pas statiquement typographié ... pas vraiment ce que j'espérais - mais ça marche et c'est concis! –

9

Malheureusement, F # ne supporte pas la co-contravariance. Voilà pourquoi cette

Seq.singleton "Hello World" :> seq<obj> |> printEm 

ne fonctionne pas

Vous pouvez déclarer paramètre comme suivants < _>, ou ensemble limite de types de paramètres à une famille spécifique en utilisant flexible type s (dièse #) cette corrigera ce scénario:

let printEm (os: seq<_>) = 
for o in os do 
    o.ToString() |> printfn "%s" 

Seq.singleton "Hello World" |> printEm 

Considérant ces lignes:

Seq.singleton 42 :> seq<obj> |> printEm // error FS0193 
Seq.singleton 42 :?> seq<obj> |> printEm 

La variance ne fonctionne que pour les classes, donc le code similaire ne fonctionnera pas en C# aussi.

Vous pouvez jeter des éléments de séquence type requis par explicity Seq.cast

+0

Cependant, en C# l'équivalent de 'Seq.singleton" Hello World "|> printEm' fonctionnera et sera vérifié statiquement - ce qui est le cas le plus intéressant, pour moi. –