C# と VB.NET の質問掲示板

わんくま同盟

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

ツリー一括表示

CSVHelperでマッピングがうまくいかない /MGS (18/02/14(Wed) 01:45) #86553
Re[1]: CSVHelperでマッピングがうまくいかない /Hongliang (18/02/14(Wed) 09:25) #86554
│├ Re[2]: CSVHelperでマッピングがうまくいかない /にゃるら (18/02/14(Wed) 10:38) #86555
││└ Re[3]: CSVHelperでマッピングがうまくいかない /MGS (18/02/14(Wed) 22:00) #86560 解決済み
│└ Re[2]: CSVHelperでマッピングがうまくいかない /MGS (18/02/14(Wed) 21:42) #86559 解決済み
Re[1]: CSVHelperでマッピングがうまくいかない /魔界の仮面弁士 (18/02/14(Wed) 11:11) #86556
  └ Re[2]: CSVHelperでマッピングがうまくいかない /MGS (18/02/14(Wed) 21:39) #86558 解決済み


親記事 / ▼[ 86554 ] ▼[ 86556 ]
■86553 / 親階層)  CSVHelperでマッピングがうまくいかない
□投稿者/ MGS (1回)-(2018/02/14(Wed) 01:45:18)

分類:[C#] 

CSVHelperを利用したCSVの読み込みについて質問させてください。

環境:VisualStudio2017
   C#

CsvHelperのClassMapを用いて
読み込んだCSVをDataClass型に格納しようとしております。
以下のようなプログラムを書いていますが、お恥ずかしながら
エラーが出ており、解決法がわかっていない状況です。

    Public DataClass : IData
    {
        public int State;
        public DateTime SignedDate;
        public Double Price;
        public Double Quantity;
    }   

    Public MyClass
    {
        CsvReader csv = new CsvReader();
        IData data= new DataClass();
        IEnumerable<IData> list = csv.Read(data, this.txtCSV.Text);

        foreach (DataClass d in list)
        {
            // 処理
        }
    }

    public class CsvReader
    {
        public IEnumerable<IData> Read(IData data, string filename)
        {
            using (var csv = new CsvHelper.CsvReader(new StreamReader(filename)))
            {
                csv.Configuration.HasHeaderRecord = false; // Headerはなし
                if(data.GetType() == typeof(DataClass))
                {
                    csv.Configuration.RegisterClassMap<MyClassMap>();
                }
                return csv.GetRecords<IData>();
            }
        }
    }

    public MyClassMap()
    {
        Map(m => m.State).Index(0);
        Map(m => m.SignedDate).Index(1).TypeConverter(new DateTimeConverter());
        Map(m => m.Price).Index(2).TypeConverter(new DoubleConverter());
        Map(m => m.Quantity).Index(3).TypeConverter(new DoubleConverter());
    }

[読み込ませたCSV]
0,2017/1/1,1000000,0.5
1,2017/2/1,2000000,0.25

MyClass内foreachでNullReferenceExceptionが発生します。
GetRecords時点で正しくマッピングされていないようですが、原因がつかめていません。
ご教授いただければ幸いです。

[ □ Tree ] 返信 編集キー/

▲[ 86553 ] / ▼[ 86555 ] ▼[ 86559 ]
■86554 / 1階層)  Re[1]: CSVHelperでマッピングがうまくいかない
□投稿者/ Hongliang (614回)-(2018/02/14(Wed) 09:25:56)
マッピングの問題ではないですね。

> using (var csv = new CsvHelper.CsvReader(new StreamReader(filename)))
> {
> csv.Configuration.HasHeaderRecord = false; // Headerはなし
> if(data.GetType() == typeof(DataClass))
> {
> csv.Configuration.RegisterClassMap<MyClassMap>();
> }
> return csv.GetRecords<IData>();
> }

csv.GetRecords<T>は一見すべてのデータをそろえて返してきていますが、
実際にはforeachで次が要求されるたびにStreamReaderからデータを読み込みます。
で、usingがforeachの列挙よりも早くスコープが終了するため
CsvReaderがクローズしてしまい、foreachのタイミングで読み込めなくなっています。

usingを使うのであれば、直接GetRecords<T>()の結果を返すのではなく、
yieldを使って
foreach (IData d in csv.GetRecords<IData>())
{
yield return d;
}
などとすれば、GetRecords<T>のすべての列挙が完了してから
usingのスコープが終了するためうまいこと動くようになります。
[ 親 86553 / □ Tree ] 返信 編集キー/

▲[ 86554 ] / ▼[ 86560 ]
■86555 / 2階層)  Re[2]: CSVHelperでマッピングがうまくいかない
□投稿者/ にゃるら (1回)-(2018/02/14(Wed) 10:38:19)
クエリーが未実行でストリームが閉じてしまうことが原因なのであれば、

return csv.GetRecords<IData>();



return csv.GetRecords<IData>().ToList ();

として、returnされるまでにクエリーを実行させてしまうことで解決できたりしないでしょうか?
[ 親 86553 / □ Tree ] 返信 編集キー/

▲[ 86555 ] / 返信無し
■86560 / 3階層)  Re[3]: CSVHelperでマッピングがうまくいかない
□投稿者/ MGS (4回)-(2018/02/14(Wed) 22:00:06)
No86555 (にゃるら さん) に返信
> クエリーが未実行でストリームが閉じてしまうことが原因なのであれば、
>
> return csv.GetRecords<IData>();
>
> を
>
> return csv.GetRecords<IData>().ToList ();
>
> として、returnされるまでにクエリーを実行させてしまうことで解決できたりしないでしょうか?

回答ありがとうございます。

魔界の仮面弁士さんの回答の通り
return csv.GetRecords<Trade>().ToArray();
または
return csv.GetRecords<Trade>().ToList();
のような形でうまくいきました。
解決済み
[ 親 86553 / □ Tree ] 返信 編集キー/

▲[ 86554 ] / 返信無し
■86559 / 2階層)  Re[2]: CSVHelperでマッピングがうまくいかない
□投稿者/ MGS (3回)-(2018/02/14(Wed) 21:42:12)
No86554 (Hongliang さん) に返信
> マッピングの問題ではないですね。
> csv.GetRecords<T>は一見すべてのデータをそろえて返してきていますが、
> 実際にはforeachで次が要求されるたびにStreamReaderからデータを読み込みます。
> で、usingがforeachの列挙よりも早くスコープが終了するため
> CsvReaderがクローズしてしまい、foreachのタイミングで読み込めなくなっています。

なるほど…。
その発想を持っておりませんでした。
戻った結果が入っていないのでうまく値が入っていないと思い込んでしまっていました。
思い込みは駄目ですね。
勉強になりました。ありがとうございます。
解決済み
[ 親 86553 / □ Tree ] 返信 編集キー/

▲[ 86553 ] / ▼[ 86558 ]
■86556 / 1階層)  Re[1]: CSVHelperでマッピングがうまくいかない
□投稿者/ 魔界の仮面弁士 (1564回)-(2018/02/14(Wed) 11:11:35)
No86553 (MGS さん) に返信
> MyClass内foreachでNullReferenceExceptionが発生します。

foreach での列挙が始まる前に、using によって Dispose されているためです。
GetRecords<T> メソッドは、遅延実行されるメソッドですので、
列挙が完了するまでは using してはいけません。

ファイルが小さいものである場合には、後述するように
return 時に .ToArray() メソッドを補って即時実行するのが良いでしょう。

ファイルが大きく、全件一括取り出すのが困難な場合には、プログラムを組み替えて
foreach し終わってから Dispose されるように変更してみてください。


> Public DataClass : IData
これだとコンパイルが通らないような。
「public class DataClass : IData」でしょうか。

IData インターフェイス、あるいは IData クラスは独自定義のものですか?
今回の実装だと、ここの定義も重要になってきます。


> public class CsvReader
この名前は、CsvHelper 名前空間の CsvReader クラス、すなわち
>  using (var csv = new CsvHelper.CsvReader(new StreamReader(filename)))
と被りますよね。

名前空間で区別できるとはいえ、初見では見分けが付きませんでした。
混乱を避けるため、別のクラス名にしておいた方が良いかと思います。掲示板でのやり取りにおいては特に。


> public IEnumerable<IData> Read(IData data, string filename)
data 引数のインスタンスが、型指定のためにしか使われていないのが気にかかりました。

複数の CSV に対応させるなら、
 public IEnumerable<T> Read<T>(string filename) where T : IData
あるいは
 public IEnumerable<IData> Read(Type type, string filename)
で十分な気がするのですが、実際のコードはもっと複雑だということでしょうか。


> return csv.GetRecords<IData>();
列挙したいレコード情報は DataClass 型なのですから、
型パラメーターを IData 型にしてはマズイです。

戻り値を IEnumerable<DataClass> にして、
 return csv.GetRecords<DataClass>().ToArray();
にしてみてください。

戻り値を IEnumerable<IData> のままにしておきたい場合には、
 return csv.GetRecords<DataClass>().OfType<IData>().ToArray();
という感じです。


> public MyClassMap()
これは…メソッドではなさそうですね。もしかして
 public sealed class MyClassMap : CsvHelper.Configuration.ClassMap<DataClass>
あたりのコンストラクタでしょうか。



> Map(m => m.SignedDate).Index(1).TypeConverter(new DateTimeConverter());
ここでいうと DateTimeConverter というのは、独自定義の
「public sealed class DateTimeConverter : CsvHelper.TypeConversion.ITypeConverter」
でしょうか。それとも、
CsvHelper.TypeConversion.DateTimeConverter
をそのまま使っているのでしょうか。

恐らくは後者だと思いますが、CsvReader は独自定義で用意されていたので、念のため確認。


> CsvReader csv = new CsvReader();
> IData data= new DataClass();
> IEnumerable<IData> list = csv.Read(data, this.txtCSV.Text);

ここの CsvReader は CsvHelper.CsvReader ではなく、
独自定義の「public class CsvReader」の方ですね。



> IEnumerable<IData> list = csv.Read(data, this.txtCSV.Text);
> foreach (DataClass d in list)

IEnumerable<IData> を「foreach (DataClass d in list)」に渡すのは
違和感があるので、私なら、ここの列挙は
 foreach (var d in list.OfType<DataClass>())
のように書くかと思います。

IEnumerable<IData> で受けて、「foreach (IData d in list)」とか
IEnumerable<DataClass> で受けて、「foreach (DataClass d in list)」とか
IEnumerable<DataClass> で受けて、「foreach (IData d in list)」とするのなら
OfType は使わないですけれども。
[ 親 86553 / □ Tree ] 返信 編集キー/

▲[ 86556 ] / 返信無し
■86558 / 2階層)  Re[2]: CSVHelperでマッピングがうまくいかない
□投稿者/ MGS (2回)-(2018/02/14(Wed) 21:39:51)
No86556 (魔界の仮面弁士 さん) に返信

細かいソースレビューに近い部分までありがとうございます。
独学に近い形でやっていたため非常に勉強になります。
わかりにくい記述が多く申し訳ありません。

> 戻り値を IEnumerable<DataClass> にして、
>  return csv.GetRecords<DataClass>().ToArray();
> にしてみてください。

最終的には記載の方法でうまくいきました!
ご教授いただきありがとうございました。


蛇足かもしれませんが以下、個別の件について回答させていただきます。

>>Public DataClass : IData
> これだとコンパイルが通らないような。
> 「public class DataClass : IData」でしょうか。
>
> IData インターフェイス、あるいは IData クラスは独自定義のものですか?
> 今回の実装だと、ここの定義も重要になってきます。

ご指摘の通りです。
掲示板用に書き換えている際に間違えて消してしまったようです。
IDataインターフェイスは独自定義のものになります。

>>public class CsvReader
> この名前は、CsvHelper 名前空間の CsvReader クラス、すなわち
>> using (var csv = new CsvHelper.CsvReader(new StreamReader(filename)))
> と被りますよね。
>
> 名前空間で区別できるとはいえ、初見では見分けが付きませんでした。
> 混乱を避けるため、別のクラス名にしておいた方が良いかと思います。掲示板でのやり取りにおいては特に。

こちらもご指摘の通りです。
名称は分けるべきでした。申し訳ありません。

>>public IEnumerable<IData> Read(IData data, string filename)
> data 引数のインスタンスが、型指定のためにしか使われていないのが気にかかりました。
>
> 複数の CSV に対応させるなら、
>  public IEnumerable<T> Read<T>(string filename) where T : IData
> あるいは
>  public IEnumerable<IData> Read(Type type, string filename)
> で十分な気がするのですが、実際のコードはもっと複雑だということでしょうか。

実際には他にも処理をしておりますが
ご指摘の形で問題ないので見直したいと思います。

>>public MyClassMap()
> これは…メソッドではなさそうですね。もしかして
>  public sealed class MyClassMap : CsvHelper.Configuration.ClassMap<DataClass>
> あたりのコンストラクタでしょうか。
>
> > Map(m => m.SignedDate).Index(1).TypeConverter(new DateTimeConverter());
> ここでいうと DateTimeConverter というのは、独自定義の
> 「public sealed class DateTimeConverter : CsvHelper.TypeConversion.ITypeConverter」
> でしょうか。それとも、
> CsvHelper.TypeConversion.DateTimeConverter
> をそのまま使っているのでしょうか。
>
> 恐らくは後者だと思いますが、CsvReader は独自定義で用意されていたので、念のため確認。

掲示板用に補正している内に継承が漏れてしまっておりました。
指摘の通り後者になります。

>>CsvReader csv = new CsvReader();
>>IData data= new DataClass();
>>IEnumerable<IData> list = csv.Read(data, this.txtCSV.Text);
>
> ここの CsvReader は CsvHelper.CsvReader ではなく、
> 独自定義の「public class CsvReader」の方ですね。
>
>>IEnumerable<IData> list = csv.Read(data, this.txtCSV.Text);
>>foreach (DataClass d in list)
>
> IEnumerable<IData> を「foreach (DataClass d in list)」に渡すのは
> 違和感があるので、私なら、ここの列挙は
>  foreach (var d in list.OfType<DataClass>())
> のように書くかと思います。
>
> IEnumerable<IData> で受けて、「foreach (IData d in list)」とか
> IEnumerable<DataClass> で受けて、「foreach (DataClass d in list)」とか
> IEnumerable<DataClass> で受けて、「foreach (IData d in list)」とするのなら
> OfType は使わないですけれども。

あまりOfTypeの書き方は書いたことありませんでしたが
無理にCastするより自然ですね。
記載を見直していきたいと思います。
解決済み
[ 親 86553 / □ Tree ] 返信 編集キー/


管理者用

- Child Tree -