CsvHelperの何カラム目でエラーが出たか知りたくてAutoMappingを諦めた
[追記]
exception.Data["CsvData"]に必要な情報が入っていたので
この記事はほとんどそのまま無駄記事になりました!
CsvHelperの使い方のサンプル程度に残しておきますが、
読んだ人は読み終わったら「exception.Data["CsvData"]でいいじゃん」と唱えましょう。
[追記ここまで]
[元記事ここから]
C#でCSVを読みたかったんですが、CSVって意外と厄介で、標準であるわけでもなく。
そこで、CsvHelperをNugetしてきて使いまして。
レコードをクラスとして定義してCsvClassMap
例えばint型のフィールドに文字列や空文字が入ってると、Exceptionが飛んでくるわけですね。
そうなると使ってる人間としては、「一体どこのカラムに問題があったのか」を知りたいじゃないですか。
100カラムもあるようなcsvから目で探すのは馬鹿らしいじゃないですか。
ところがこのException、何行目・何カラム目が問題だったかの情報が含まれてない。
あまりC#のお作法に慣れてないというかException使うのも初めてなんですが、
そういうもんなんですかね?ここに入れてくれれば万事解決だったのに。
まぁ、そもそも1行ずつ読み込みするので行数は把握できるんですが、カラムはそうもいかない。
…ので、大人しく自動Mappingをやめて、手動にしました(めんどくさい)!
手動ならTryGetField()という便利なものがあったので、
こいつで1カラムずつ調べながら読み込んで、失敗したら自作の例外を投げる。
コードはこんな感じです。
// こんなExceptionを作っておいて… class CsvReadException : Exception { public CsvReadException() { } public CsvReadException(string message) : base(message) { } public CsvReadException(int row, int col) : base("CSV読み込みエラー。行:" + row + ", カラム:" + col) { } public CsvReadException(string message, Exception inner) : base(message) { } } // CsvHelper.CsvReaderのサブクラスを作っちゃう class MyCsvReader : CsvHelper.CsvReader { // コンストラクタは今回のブログに関係ないけど参考までに。 // こうやって継承クラスのコンストラクタでオプション指定したほうが使いまわしやすい public MyCsvReader(string path) : base(new StreamReader(path)) { // 先頭が'/'の行はコメント扱い。デフォルトは'#' this.Configuration.Comment = '/'; // コメントを有効に this.Configuration.AllowComments = true; // ヘッダー行の無いCSVですよ指定 this.Configuration.HasHeaderRecord = false; } // GetField<T>をオーバーライドして、自作例外投げるバージョンにする override public T GetField<T>(int col) { T field; if (!this.TryGetField(col, out field)) { throw new CsvReadException(this.Row, col + 1); } return field; } } class Record { // こんな6カラムがある想定のCSVですよ public int hogeInt1 { get; set; } public int hogeInt2 { get; set; } public int hogeInt3 { get; set; } public string hogeString1 { get; set; } public string hogeString2 { get; set; } public string hogeString3 { get; set; } // 使う側でtry-catchしてね public Record(MyCsvReader csvReader) { int col = 0; this.hogeInt1 = csvReader.GetField<int>(col++); this.hogeInt2 = csvReader.GetField<int>(col++); this.hogeInt3 = csvReader.GetField<int>(col++); this.hogeString1 = csvReader.GetField<string>(col++); this.hogeString2 = csvReader.GetField<string>(col++); this.hogeString3 = csvReader.GetField<string>(col++); } }
記事はここまで。
最初は↓こんなに汚いコードを書いたんだけど、
ブログを更新した直後にうまいやり方が思いつくという黄金パターンでした。
オブジェクト指向もちゃんと使うの初めてに近いからね、仕方ないね。
自らの思考のトレースとして敢えて残しておく。
// Exceptionは上と同じ // CsvReaderのサブクラスを作っていない class Record { // こんな6カラムがある想定のCSVですよ public int hogeInt1 { get; set; } public int hogeInt2 { get; set; } public int hogeInt3 { get; set; } public string hogeString1 { get; set; } public string hogeString2 { get; set; } public string hogeString3 { get; set; } public Record(CsvHelper.CsvReader csvReader) { int col = 0; // out引数にプロパティを渡せなかったので、一度別変数で受け取る(めんどくさい!) int intFiled; string stringFiled; // TryGetFieldで調べつつ、失敗したら例外投げ、うまくいけば対応プロパティに格納 // 他資料から矩形貼り付けとかしたい関係で、無理やり1行にしている if (!csvReader.TryGetField(col++, out intFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeInt1 = intFiled; if (!csvReader.TryGetField(col++, out intFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeInt2 = intFiled; if (!csvReader.TryGetField(col++, out intFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeInt3 = intFiled; if (!csvReader.TryGetField(col++, out stringFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeString1 = stringFiled; if (!csvReader.TryGetField(col++, out stringFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeString2 = stringFiled; if (!csvReader.TryGetField(col++, out stringFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeString3 = stringFiled; } }
BATファイルにドラッグ&ドロップがうまくいかなかったのは実行パスのせいだった
こんな感じのバッチファイルを書いた所…
@echo off call ..\common.bat %1
同じフォルダに置いたファイルをdrag&dropするとうまく動くのに、
別のフォルダにあるファイルをdrag&dropするとうまく動かない。
冷静に良く見たら、バッチの実行パスが変わるんですな。
drag&dropすると、batではなくそのファイルのある場所が実行パスになってしまう。
そのため、callの参照先が違ってたというわけ。
実行パスを必ず置いてあるフォルダにしてくれれば済む話なので、
こうしたら直りました。
@echo off rem カレントディレクトリをこのbatのあるパスにする cd /d %~dp0 call ..\common.bat %1
要件次第ではあるけど、このcdは必ず書く癖をつけてしまったほうが
ほとんどのケースで良いのではないだろうか。少なくとも自分の場合はそう。
adMobのテストモードが効かないのはSDKバージョンのせいだった
開発中のandroidアプリにadMobの広告を仕込んでみたところ、
テストモードにする記述をしているのに、テストモードになってくれない。
具体的な症状としては、完全に本チャンの動きをします。
怪盗ロワイヤルとかの広告が表示されるか、広告を取得できない*1。
本番状態だと、広告が取得できたりできなかったりするので
表示位置の確認などに困ります。登録直後ならたいてい取得できないし。
いくらググっても同じ事象はヒットせず、
そういえばandroid SDKの更新ってしてないなーと思ったら
バージョン8を使ってました(執筆時点の最新は10)。
更新したら無事解決。
なお、adMobの導入の仕方はadMob SDKのバージョンによって少し違います。
適当にググって失敗したと思ったら、その記事の日時に注意。
検索用:テストモードにならない テストモードなのに 設定されない テスト広告が表示されない
*1:ログに「Server replied that no ads are available」などと表示される
android MotionEventがDown→Move→Upの後、へんな座標でMoveが来る?(未解決)
マルチタッチをいじくりまわしていたら、
どうもSurfaceViewのonTouchEventの様子がおかしい。
各イベントでログを出してみたところ…
02-25 12:37:42.772: DEBUG/touchDown(910): X=797.0,Y=3.0
02-25 12:37:43.110: DEBUG/touchMove(910): X=798.0,Y=15.0
02-25 12:37:43.261: DEBUG/touchMove(910): X=796.0,Y=121.0
(中略)
02-25 12:37:45.850: DEBUG/touchMove(910): X=798.0,Y=476.0
02-25 12:37:45.902: DEBUG/touchMove(910): X=799.0,Y=476.0
02-25 12:37:46.121: DEBUG/touchUp(910): X=799.0,Y=476.0
02-25 12:37:46.210: DEBUG/touchMove(910): X=532.6666,Y=317.3333
ACTION_UPの後にACTION_MOVEが1回来る。
このときエミュレータはWVGA、開発中のアプリは横画面限定にしてるので
WVGAの400*800→が320*532になってます。
これはちょうどdip*1に変換された格好になります。
ただの偶然かもしれないけど。
気になるので、あと2ケースも調べてみた。
HVGA(320*480なので、pixel=dipになる)
02-25 12:41:31.761: DEBUG/touchDown(224): X=475.0,Y=293.0
02-25 12:41:31.970: DEBUG/touchMove(224): X=475.0,Y=297.0
(中略)
02-25 12:41:34.080: DEBUG/touchMove(224): X=479.0,Y=317.0
02-25 12:41:34.362: DEBUG/touchUp(224): X=479.0,Y=317.0
02-25 12:41:34.451: DEBUG/touchMove(224): X=532.2222,Y=352.2222
QVGA(240*320)
02-25 12:57:48.169: DEBUG/touchDown(227): X=306.0,Y=181.0
02-25 12:57:48.429: DEBUG/touchMove(227): X=309.0,Y=215.0
(中略)
02-25 12:57:49.448: DEBUG/touchMove(227): X=318.0,Y=237.0
02-25 12:57:49.827: DEBUG/touchUp(227): X=318.0,Y=237.0
02-25 12:57:49.858: DEBUG/touchMove(227): X=530.0,Y=394.99997
???…さっぱり。やっぱdipは偶然だったのかな。
で、これには2つの問題があって、
1.ACTION_UPのあとにACTION_MOVEが来るのはおかしい
2.座標がおかしい
ですね。
大抵のケースでは、タッチに関連する状態をUpのときに完結させるのが普通だと思います。
なので、その後Moveがこようが変な座標だろうが問題にならないと思いますが…
もしこれでハマった人が居たら何かの参考になれば。
なお、気軽に試せる実機が無いので、
エミュレータだけの挙動なのか、実機もそうなのかはわかりません。
また、SurfaceViewでしか確認してないので
他のViewやViewのサブクラスでどうなのかもわかりません。
*1:Device Independent Pixels=端末から独立したPixel。DeviceじゃなくてDensityって人もいるけどどっちだろう。
androidエミュレータの裏プロセスを終了させる方法
android開発をしてると、Eclipse+エミュレータの時点でもう重い重い。
貧弱なPCでは他のことはいっさいできなくなりますね。
android開発をしてる人ならActivityのonCreateやonPauseなどの遷移フローを
最初に勉強すると思うので当たり前なことですが、
androidでは基本的に「プログラムをユーザが明示的に終了する」という概念はなく、
ホームキーや戻るキーでアプリが裏に送られたあと、
メモリが足りなくなったらアプリが終了します。
それでは満足できない人は、TaskManagerやTaskControlなどのアプリを
マーケットから落としてきて使えばいいようですが…。
エミュレータの場合、どうするのか?
私のPCの場合、エミュレータ上でデバッグ中のアプリが走ったままだと、
Eclipse上でコードをちょっといじる度に10秒待たされる始末。
さて、これには「DDMS」を使います。
これもandroid開発してるなら知ってるべきっぽいんだけど…知らなかった。
Eclipseであれば、ウィンドウ→パースペクティブを開く→DDMS
で実行中プロセス(今まさにデバッグ中のものは虫マーク付き)が表示されるので、
クリックしてからSTOPアイコンを押せば終了させられます。
3/2追記:
「戻るキー」の場合はプロセスが終了するほうが普通なのかな?
まぁ開発者が戻るキーフックしてどうこうしてたらそれ次第なので、
ユーザが明示的に終了できない、ってのは嘘じゃないけど。
検索用ワード:バックグラウンド kill プロセスを殺す