locked
F#からSystem.LinqにあるCastメソッドを利用する方法 RRS feed

  • 質問

  • 前回、String.Concatについて教えて頂きありがとうございました。
    
    System.LinqにあるCastメソッドを利用してみたところ、以下のように「Error: Specified cast is not valid.」となってしまいました。
    

    open System
    open System.Collections.Generic
    open System.Linq
    // 1
    let numList:obj list = [1; 1.1; 2; 2.2; 3; 3.3]
    //評価結果: val numList : obj list = [1; 1.1; 2; 2.2; 3; 3.3]
    
    // 2
    let numList1:obj [] = [|1; 1.1; 2; 2.2; 3; 3.3|]
    //評価結果: val numList1 : obj array = [|1; 1.1; 2; 2.2; 3; 3.3|]
    

    // 3
    // let numList2 = Array.CreateInstance(typeof<obj>,6)
    let numList2 = new System.Collections.Generic.List<obj>()
    numList2.AddRange([1; 1.1f; 2; 2.2f; 3; 3.3f])
    //評価結果: val numList2 : System.Collections.Generic.List<obj>

    printfn"%A"<| numList.Cast<int>()
    printfn"%A"<| numList1.Cast<int>()
    printfn"%A"<| numList2.Cast<Int32>()
    //評価結果: Error: Specified cast is not valid.<br/></span><span>
    //Error: Specified cast is not valid.
    //Error: Specified cast is not valid.
    //Error: Specified cast is not valid.
    //1 <-これはint -> intのキャストは成功したことを示します
    

    おそらく

    > int 1.1;;
    val it : int = 1
    > int (box 1.1);;
     int (box 1.1);;
     -----^^^^^^^
    
    The type 'obj' does not support a conversion to the type 'int'

    と言ったようにF#のキャストはobj型からのキャストに対応していない事が原因かなと考えております。

    F#ではSystem.LinqのCastは利用可能でしょうか?






    2011年8月4日 5:55

回答

  • 質問の回答になっていないかもしれませんが、容赦ください。

    複数の型が含まれているリストや配列を特定の型のリストや配列にするには、一律のキャストではなく、どこかで場合わけして変換することが必要だと思います。

     

    open System
    
    let numList:obj list = [1; 1.1; 2; 2.2; 3; 3.3]
    
    numList 
    |> List.map (function 
     | :? int as x -> x 
     | :? float as x -> int x 
     | _ -> invalidOp "can't convert") 
    |> printfn "%A"
    
    numList 
    |> List.map Convert.ToInt32
    |> printfn "%A"
    

     

     

    自分が最初に思いついたのは、パターンマッチングで型を見て変換する方法です。

    でも、ぜくるさんがすでに示されていますが、System.Convertクラスを使うのがシンプルですね(この場合は、System.Convertクラスが内部で場合わけして変換してくれる)。

     

    あくまで個人的な好みですが、F#を使う場合は、LINQを使うよりも、Seq、List、Arrayといったモジュールの関数のほうが使いやすい気がしています。

    LINQのEnumerable.Cast<TResult>に対応するものとしては、Seqモジュールにcast関数がありますね。

    2011年8月4日 15:49
  • F#でもSystem.LinqのCastは利用可能です。
    しかし、Enumerable.Cast<TResult>メソッドによる変換では、「InvalidCastException」の例外がスローされてしまうようなキャストは失敗してしまいます。
    F#のキャストがobj型からunboxなキャストに対応していないからというわけではありません。

    > int 1.1;;
    val it : int
    
    が成功するカラクリは、Operators.int<^T> 関数の中で、静的メソッドのIntPtr.op_Explicit が呼ばれているからですね。


    do 
     let a = box 3 :?> int
     a |> printfn "%d"
     int 3.3 |> printfn "%d" // 内部でIntPtr.op_Explicit が使われているので変換できる
     let b = box 3.3 :?> int // InvalidCastException
     b |> printfn "%d"
    


    Operators.int<^T> 関数のシグネチャは以下のようになっています。
    // Signature:
    int : ^T -> int (requires ^T with static member op_Explicit)
    


    要件によっては、キャス可能な要素のみを取得するOfTypeを利用したり、ConvertAllですべてを変換したりすると良いかもです。

    open System
    open System.Collections.Generic
    open System.Linq
    // 1
    let numList:obj list = [1; 1.1; 2; 2.2; 3; 3.3]
    
    // 2
    let numList1:obj [] = [|1; 1.1; 2; 2.2; 3; 3.3|]
    
    // 3
    let numList2 = new List<obj>()
    numList2.AddRange([1; 1.1f; 2; 2.2f; 3; 3.3f])
    
    /// seqの中身が評価され、キャストに失敗すると、はじめてInvalidCastExceptionが発生する
    numList.Cast<int>() |> Seq.iter (printf "%A,") |> Console.WriteLine // InvalidCastException
    numList1.Cast<int>() |> Seq.iter (printf "%A,") |> Console.WriteLine // InvalidCastException
    numList2.Cast<Int32>() |> Seq.iter (printf "%A,") |> Console.WriteLine // InvalidCastException
    
    /// 4
    let numList3:obj list = [1;2;3;4;5]
    /// obj型からのキャストに対応していないわけではない。F#でもSystem.LinqのCastは利用可能。
    printfn"%A"<| numList3.Cast<int>()
    printfn ""
    
    /// 方法1:OfTypeを使う。キャストできる要素のみを取得したい場合
    numList.OfType<int>() |> Seq.iter (printf "%A,") |> Console.WriteLine 
    numList1.OfType<int>() |> Seq.iter (printf "%A,") |> Console.WriteLine 
    numList2.OfType<Int32>() |> Seq.iter (printf "%A,") |> Console.WriteLine 
    printfn ""
    
    /// 方法2:ConvertAllを使う(LINQりたいなら)。
    numList.ToList().ConvertAll(fun x -> Convert.ToInt32(x)) |> Seq.iter (printf "%A,") |> Console.WriteLine 
    numList1.ToList().ConvertAll(fun x -> Convert.ToInt32(x)) |> Seq.iter (printf "%A,") |> Console.WriteLine 
    numList2.ConvertAll(fun x -> Convert.ToInt32(x)) |> Seq.iter (printf "%A,") |> Console.WriteLine 
    
    Console.ReadLine () |> ignore
    


    参考URL:
    キャストと変換 (F#)
    http://msdn.microsoft.com/ja-jp/library/dd233220.aspx
    Enumerable.Cast<TResult> メソッド
    http://msdn.microsoft.com/ja-jp/library/bb341406.aspx
    Operators.int<^T> 関数 (F#)
    http://msdn.microsoft.com/ja-jp/library/ee370576.aspx
    IntPtr.op_Explicit メソッド (Int32)
    http://msdn.microsoft.com/ja-jp/library/ee370576.aspx



    • 編集済み ぜくる 2011年8月4日 13:32 レイアウト修正
    • 回答としてマーク ながとタン 2011年8月4日 16:13
    2011年8月4日 13:28

すべての返信

  • F#でもSystem.LinqのCastは利用可能です。
    しかし、Enumerable.Cast<TResult>メソッドによる変換では、「InvalidCastException」の例外がスローされてしまうようなキャストは失敗してしまいます。
    F#のキャストがobj型からunboxなキャストに対応していないからというわけではありません。

    > int 1.1;;
    val it : int
    
    が成功するカラクリは、Operators.int<^T> 関数の中で、静的メソッドのIntPtr.op_Explicit が呼ばれているからですね。


    do 
     let a = box 3 :?> int
     a |> printfn "%d"
     int 3.3 |> printfn "%d" // 内部でIntPtr.op_Explicit が使われているので変換できる
     let b = box 3.3 :?> int // InvalidCastException
     b |> printfn "%d"
    


    Operators.int<^T> 関数のシグネチャは以下のようになっています。
    // Signature:
    int : ^T -> int (requires ^T with static member op_Explicit)
    


    要件によっては、キャス可能な要素のみを取得するOfTypeを利用したり、ConvertAllですべてを変換したりすると良いかもです。

    open System
    open System.Collections.Generic
    open System.Linq
    // 1
    let numList:obj list = [1; 1.1; 2; 2.2; 3; 3.3]
    
    // 2
    let numList1:obj [] = [|1; 1.1; 2; 2.2; 3; 3.3|]
    
    // 3
    let numList2 = new List<obj>()
    numList2.AddRange([1; 1.1f; 2; 2.2f; 3; 3.3f])
    
    /// seqの中身が評価され、キャストに失敗すると、はじめてInvalidCastExceptionが発生する
    numList.Cast<int>() |> Seq.iter (printf "%A,") |> Console.WriteLine // InvalidCastException
    numList1.Cast<int>() |> Seq.iter (printf "%A,") |> Console.WriteLine // InvalidCastException
    numList2.Cast<Int32>() |> Seq.iter (printf "%A,") |> Console.WriteLine // InvalidCastException
    
    /// 4
    let numList3:obj list = [1;2;3;4;5]
    /// obj型からのキャストに対応していないわけではない。F#でもSystem.LinqのCastは利用可能。
    printfn"%A"<| numList3.Cast<int>()
    printfn ""
    
    /// 方法1:OfTypeを使う。キャストできる要素のみを取得したい場合
    numList.OfType<int>() |> Seq.iter (printf "%A,") |> Console.WriteLine 
    numList1.OfType<int>() |> Seq.iter (printf "%A,") |> Console.WriteLine 
    numList2.OfType<Int32>() |> Seq.iter (printf "%A,") |> Console.WriteLine 
    printfn ""
    
    /// 方法2:ConvertAllを使う(LINQりたいなら)。
    numList.ToList().ConvertAll(fun x -> Convert.ToInt32(x)) |> Seq.iter (printf "%A,") |> Console.WriteLine 
    numList1.ToList().ConvertAll(fun x -> Convert.ToInt32(x)) |> Seq.iter (printf "%A,") |> Console.WriteLine 
    numList2.ConvertAll(fun x -> Convert.ToInt32(x)) |> Seq.iter (printf "%A,") |> Console.WriteLine 
    
    Console.ReadLine () |> ignore
    


    参考URL:
    キャストと変換 (F#)
    http://msdn.microsoft.com/ja-jp/library/dd233220.aspx
    Enumerable.Cast<TResult> メソッド
    http://msdn.microsoft.com/ja-jp/library/bb341406.aspx
    Operators.int<^T> 関数 (F#)
    http://msdn.microsoft.com/ja-jp/library/ee370576.aspx
    IntPtr.op_Explicit メソッド (Int32)
    http://msdn.microsoft.com/ja-jp/library/ee370576.aspx



    • 編集済み ぜくる 2011年8月4日 13:32 レイアウト修正
    • 回答としてマーク ながとタン 2011年8月4日 16:13
    2011年8月4日 13:28
  • 質問の回答になっていないかもしれませんが、容赦ください。

    複数の型が含まれているリストや配列を特定の型のリストや配列にするには、一律のキャストではなく、どこかで場合わけして変換することが必要だと思います。

     

    open System
    
    let numList:obj list = [1; 1.1; 2; 2.2; 3; 3.3]
    
    numList 
    |> List.map (function 
     | :? int as x -> x 
     | :? float as x -> int x 
     | _ -> invalidOp "can't convert") 
    |> printfn "%A"
    
    numList 
    |> List.map Convert.ToInt32
    |> printfn "%A"
    

     

     

    自分が最初に思いついたのは、パターンマッチングで型を見て変換する方法です。

    でも、ぜくるさんがすでに示されていますが、System.Convertクラスを使うのがシンプルですね(この場合は、System.Convertクラスが内部で場合わけして変換してくれる)。

     

    あくまで個人的な好みですが、F#を使う場合は、LINQを使うよりも、Seq、List、Arrayといったモジュールの関数のほうが使いやすい気がしています。

    LINQのEnumerable.Cast<TResult>に対応するものとしては、Seqモジュールにcast関数がありますね。

    2011年8月4日 15:49