none
Linq to datatable で Sumメソッドを使ってdouble型のデータを集計した結果がInteger型となってしまう RRS feed

  • 質問

  • Linqを使ってdatatableに格納されたデータを集計しようとしています。

            Dim query = From p In datatable1.Rows _
            Group p By p.name Into grouping = Group _
            Select name, pointtotal = grouping.Sum(Function(q) q.point)

    上記のようなコードで、
    datatable1 には列name(string型)、列point(double型)に対応する値が各行に格納されています。
    これらの行をname毎にグループ化して、かつグループ毎にpointの合計値pointtotalを求めたいのですが、
    実行結果のpointtotalの値の型が整数値となってしまいます。
    同様な集計に関するEnumerable メソッドのうちAverageを用いても整数値となってしまいます。
    但し、Min,Maxではこのようなことは起きませんでした。
    原因として考えられるのはどのようなことでしょうか?
    また、この問題を解決するにはどのようにしたらよいでしょうか?
    2009年7月27日 5:45

回答

  • StringでなくObject型と認識してましたか。

    datatable1.Rowsから得られるのはのはDataRowで、各列の型情報を持っていません。
    ですからObject型と認識されるのは仕方がないことです。
    Sumにはオーバーロードで引数の型違いで複数ありますがObject型の引数は無いので、このクエリーの場合だとコンパイラがIntegerに変換をする処理の追加を行ってしまいます。(Reflectorで確認)
    #String型のときもコンパイラがIntergerに変換する処理を入れてくれます

    三輪の牛さんが書かれているようにCastするか、From p In datatable1 としてきちんと型を認識させるといいでしょう。
    あるいはNull無しならFunction(q) DirectCast(q.Point, Double)のようにキャストするのもいいでしょう。

    #datatable1.Rows.Cast(Of double)()はDataRowをDoubleにキャストしているので間違っています。
    • 回答としてマーク ハイヂ 2009年7月28日 5:32
    2009年7月28日 3:54
  • Point列が本当にDouble型になっていますか?
    q.pointにマウスを合わせてみたときにDouble型と認識していますか?

    String型になっていていると思われます。
    String型で数字を入れてあると、VBが自動でIntegerに変換してるので結果がIntegerになります。
    • 回答としてマーク ハイヂ 2009年7月28日 5:33
    2009年7月27日 9:49
  • 下記のようにCastをかませてください。
    Dim query = From p In datatable1.Rows.Cast(Of datatable1Row)() _

    Rowsをそのまま使うと遅延バインディング(実行時に型を検査する)になってしまい、
    型推論がうまく機能しなくなり、整数用のSumが呼ばれているようです。

    このようなこともあるので私はコンパイルオプションでOption Strict Onにしています。
    そうするとコンパイラが遅延バインディングを許さず、エラーではじいてくれます。
    http://www.mahoroba.ne.jp/~mw_ken
    • 回答としてマーク ハイヂ 2009年7月28日 5:32
    2009年7月27日 21:05
  • ご返信ありがとうございます。

    q.pointにマウスを合わせた場合にはObject型と認識されます。
    ちなみに、該当コードの直前で、
    datatable1.Columns.Item("point").DataType
    の内容を表示すると

    System.Double
    と出力されるので、Point列はDouble型になっていると思います。

    ちなみに、pointtotalにマウスを合わせると Integer型であると認識しているようです。
    一方、該当コードのメソッドをSumからMinやMaxに変更した場合には、
    pointtotalはObject型と認識するようです。

    • 回答としてマーク ハイヂ 2009年7月28日 5:33
    2009年7月28日 2:04
  • ありがとうございました。

    >あるいはNull無しならFunction(q) DirectCast(q.Point, Double)のようにキャストするのもいいでしょう。

    の方法で解決できました。

    >三輪の牛さんが書かれているようにCastするか、From p In datatable1 としてきちんと型を認識させるといいでしょう。

    この部分については小生の力不足か、理解できませんでした。

    >#datatable1.Rows.Cast(Of double)()はDataRowをDoubleにキャストしているので間違っています。

    ご指摘有り難うございます。
    Dim query = From p In datatable1.Rows.Cast(Of datatable1Row)() _

    とすると型”datatable1Row”が定義されていないと怒られるので、
    Double型と指定していました。

    • 回答としてマーク ハイヂ 2009年7月28日 5:33
    2009年7月28日 5:32

すべての返信

  • Point列が本当にDouble型になっていますか?
    q.pointにマウスを合わせてみたときにDouble型と認識していますか?

    String型になっていていると思われます。
    String型で数字を入れてあると、VBが自動でIntegerに変換してるので結果がIntegerになります。
    • 回答としてマーク ハイヂ 2009年7月28日 5:33
    2009年7月27日 9:49
  • 下記のようにCastをかませてください。
    Dim query = From p In datatable1.Rows.Cast(Of datatable1Row)() _

    Rowsをそのまま使うと遅延バインディング(実行時に型を検査する)になってしまい、
    型推論がうまく機能しなくなり、整数用のSumが呼ばれているようです。

    このようなこともあるので私はコンパイルオプションでOption Strict Onにしています。
    そうするとコンパイラが遅延バインディングを許さず、エラーではじいてくれます。
    http://www.mahoroba.ne.jp/~mw_ken
    • 回答としてマーク ハイヂ 2009年7月28日 5:32
    2009年7月27日 21:05
  • ご返信ありがとうございます。

    q.pointにマウスを合わせた場合にはObject型と認識されます。
    ちなみに、該当コードの直前で、
    datatable1.Columns.Item("point").DataType
    の内容を表示すると

    System.Double
    と出力されるので、Point列はDouble型になっていると思います。

    ちなみに、pointtotalにマウスを合わせると Integer型であると認識しているようです。
    一方、該当コードのメソッドをSumからMinやMaxに変更した場合には、
    pointtotalはObject型と認識するようです。

    • 回答としてマーク ハイヂ 2009年7月28日 5:33
    2009年7月28日 2:04
  • ご返信ありがとうございます。

    Dim query = From p In datatable1.Rows.Cast(Of double)() _

    としても

    name がDoubleのメンバでない、と怒られます。

    コンパイルオプションについて Option Strict On としても
    特にエラーは出ませんでした。

    ご指摘の通り、
    型推論がうまく機能していないということが問題の様な気がしてきましたが…
    2009年7月28日 2:22
  • StringでなくObject型と認識してましたか。

    datatable1.Rowsから得られるのはのはDataRowで、各列の型情報を持っていません。
    ですからObject型と認識されるのは仕方がないことです。
    Sumにはオーバーロードで引数の型違いで複数ありますがObject型の引数は無いので、このクエリーの場合だとコンパイラがIntegerに変換をする処理の追加を行ってしまいます。(Reflectorで確認)
    #String型のときもコンパイラがIntergerに変換する処理を入れてくれます

    三輪の牛さんが書かれているようにCastするか、From p In datatable1 としてきちんと型を認識させるといいでしょう。
    あるいはNull無しならFunction(q) DirectCast(q.Point, Double)のようにキャストするのもいいでしょう。

    #datatable1.Rows.Cast(Of double)()はDataRowをDoubleにキャストしているので間違っています。
    • 回答としてマーク ハイヂ 2009年7月28日 5:32
    2009年7月28日 3:54
  • 型付きDataSetでテーブルを作れば、datatable1Rowという名前の型が自動的にできてるはずですが
    うまくいかなかったのでDoubleに変えたのですか?
    ちなみに当方では型付きDataSetでテーブルをつくって動作確認済みです。

    http://www.mahoroba.ne.jp/~mw_ken
    2009年7月28日 5:24
  • ありがとうございました。

    >あるいはNull無しならFunction(q) DirectCast(q.Point, Double)のようにキャストするのもいいでしょう。

    の方法で解決できました。

    >三輪の牛さんが書かれているようにCastするか、From p In datatable1 としてきちんと型を認識させるといいでしょう。

    この部分については小生の力不足か、理解できませんでした。

    >#datatable1.Rows.Cast(Of double)()はDataRowをDoubleにキャストしているので間違っています。

    ご指摘有り難うございます。
    Dim query = From p In datatable1.Rows.Cast(Of datatable1Row)() _

    とすると型”datatable1Row”が定義されていないと怒られるので、
    Double型と指定していました。

    • 回答としてマーク ハイヂ 2009年7月28日 5:33
    2009年7月28日 5:32
  • ご指摘ありがとうございます。

    datatable1は型付Datasetから作成していませんでした。
    datatable1のスキーマは他のデータテーブルからCloneで複製したものです。
    複製元のデータテーブルのpoint列の型はDoubleになっています。

    下記のようにdatatable1を複製元のデータテーブル(datatable0)の属するデータセット(datasetA)の型
    付きで作成すると、

    Dim resulttable As New datasetADataSet.datatable0DataTable

    Dim query2 = From p In datatable1.Rows.Cast(Of datasetADataSet.datatable0Row)() _

    でうまく型を認識できました。

    ありがとうございました。
    2009年7月28日 6:17