2023年10月23日月曜日

SQLite の妙と Dapper のシンプルさ

.NETでSQLite

System.Data.SQLite と Microsoft.Data.SQLite

 
.NET で SQLite データベースを扱う場合、通常は ADO.NET プロバイダーの  System.Data.SQLite を使うと思う。
だが、NuGet パッケージマネージャーで「Sqlite」を検索すると、もう一つ Microsoft.Data.SQLite というのが出てくる。
これは、Entity Framework のチームが .NET Core に合わせて作った、新しい SQLite の ADO.NET プロバイダーだそうだ。
この両者の違いは、以下のページに詳しい。


そしてご存じの通り、SQLite はデータ型の扱いが他の DBMS のような厳格なものではなく、かなりユルユルなのだが、.NET 側に変換されるのは、long (Int64)、double、string くらいだ。あっ DateTime があった。SQL 文で指定するときは文字列としてだが、.NET 側には  DateTime に変換される。
上で long と書いたが、そう、CREATE TABLE でデータ型を INTEGER として定義しても、.NET 側には long に変換される。
ところが!
上記で紹介したページにも書いてあるが、System.Data.SQLite の方は「追加のセマンティクスが列の型に適用され、」らしい。

これはどういうことかと言うと、INTEGER で定義した列のデータは long に変換されるが、INT で定義した列のデータは int に変換される。
論より証拠、サンプルプログラムを作成してみた。

テスト データベースの作成


まず適当なフォルダにテスト用データベース「test.db」を作成し、続けて以下の SQL 文を発行して「test1」テーブルを作成する。


3行目の列名「id_int」の型を INTEGER ではなく、INT にする。

※5行目の列名「name_nvarchar」の型 nvarchar は、.NET での照合順が正常に行われると、どこかで見た。。。けど忘れた。誰か知ってたら教えて。
ちなみに、SQLite は内部では UNICODE なので、nvarchar の「n」はあってもなくても同じこと。
※7行目の「value_float」の型 floatは、.NET に変換されるのは double で float に変換してくれないというテスト用。
※8行目の「value_double」の型 double は double を指定できるよ、というテスト用。

続けて、作成したテーブルにテスト データを追加しておく。


テスト プロジェクトの作成


C# コンソール プロジェクトを作成し、NuGet から System.Data.SQLite をインストールする。
テーブル「test1」に対応したクラス「Test1.cs」を作成する。


続けて「Program.cs」を以下のように書き換える。


これで実行すると、エラーなく結果が出力される。


このように、テーブル カラムを INTEGER として定義すると long に、INT として定義すると int に変換してくれる。そしてもちろんだが、列「id」の受け側のクラス メンバーを int にすると、エラーになる。
また、FLOAT として定義しても float にはしてくれず、double になってしまう。しかしこれは、REAL が SINGLE だということを考えれば、通常の動作ということになる。
ちなみに、float へ変換する際、上記のソースでは、(float)(double)dr["value_float"] としているが、エラーになる可能性がある。その場合は、以下のようにするとよろしい。


でも、この記事の主旨は、SQLite テーブル定義で、INT として列を定義すると、受け側の型を int にしておいても大丈夫、というものだが、果たしてどれだけの需要があるのか。。。
実際の業務では、このような曖昧なことをしてはいけない。後から(改修など)このテーブルを再定義する必要がある場合、「あっ!『INT』にしてるよ、前の担当者。直しとこ。」って INTEGER に直されたら大変なことになる。

実は、Dapper はこの辺りも吸収してくれて、尚且つシンプルになるので、かなりいい感じなんだなぁ。

ADO.NET から Dapper へ


上記では、Main 関数内で、foreach で行ごとにクラスへ変換しているが、Dapper を使うとシンプルになる。
また、DTO としてのデータ クラスのデータ型もある程度吸収してくれる。

テスト プロジェクトに NuGet から Dapper をインストールする。

Program.cs を以下のように書き換える。


実行時引数で引数なし、または 0 のときは、ADO.NET を、1 のときは、Dapper を使った処理になるようにした。
テーブルの行数を 10 万行、100 万行単位にして処理速度の比較テストを行う場合は、出力(コンソールやテキスト ファイルなど)をせず、最終形態であるデータ クラスのリストになるところまでで終了として、System.Diagnostics.Stopwatch で計測する。

見ての通り、Dapper は 47 行目の1行だけである。これで、列「id_int」も「value_float」もちゃんと変換してくれる。
あっぱれ!

2017年8月18日金曜日

Visual Studio での開発バージョン

Visual Studio での開発バージョン


長年 Visual Studio で開発をしていると、バージョンが上がっても特に意識することなく開発を続行できる。
もちろん、ここで言う「意識」とは、バージョンを上げるという「意識」は存在するし、参照しているDLLの関係上バージョンを上げられないという「意識」も存在する。それらすべての条件をクリアした状態でのバージョンアップの作業という意味での「意識」だ。

この辺りの使い勝手の良さは、Visual Studio が他を圧倒している。

だが、バージョン物である以上、バージョン間の互換性の問題がつきまとう。
冒頭で述べたバージョンアップに関しては、諸条件をクリアしていれば特に意識することはないが、一旦上げたバージョンを以前のバージョンの Visual Studio で開こうとすると叱られる。叱られるどころか、そもそも開けない状況になることがある。

そんな時はソリューションファイル .sln をテキストエディタで開いて開発バージョンを確認するが、分かりづらいため覚えとして載せておく。

自分の開発環境でこんなソリューションファイルを見つけた。

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2010




このソリューションファイルを最後に開いた Visual Studio バージョンは 2012 だ。
つまり、Format Version 12.00 の部分が最後に開いたバージョンで、# から始まる行はコメントで、このソリューションファイルを作成したバージョンということだろう。

分かりづらい。

Format Version に記述されているバージョン番号の対応を記すと、

Format Version 10.00 → Visual Studio 2008
Format Version 11.00 → Visual Studio 2010
Format Version 12.00 → Visual Studio 2012

となり、2012 で作成したソリューションファイルの中身は、

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012

となる。
ここまでは何とか分かるが、2013 から複雑になる。
Visual Studio 2013 で作成したソリューションファイルの中身が以下。

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013

そう、Format Version は 12.00 のままなのだ。
ただし以下の2つの項目が増えている。

VisualStudioVersion = 12.0.21005.1
MinimumVisualStudioVersion = 10.0.40219.1

ビルド番号以下は Update の適用によって変わるだろうが、そもそも MinimumVisualStudioVersion とは?
ちなみにこのソリューションファイルは 2010 で開けない。
結局、このソリューションファイルを理解できる最小のバージョンということだろう。
つまり手を加えれば開けますよ、ということか。

分かりづらい。

そして、Visual Studio 2015 はもっと凄いことになる。

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1

Format Version は 12.00 のままだし、2015 って、Visual Studio 14 ていうのね。

分かりづらい。

分かりづらいながらも結論としては、

1.基本的に、Visual Studio のバージョンは下げない。

どうしても下げる必要があるときは、下位のバージョンでソリューションを作成し、既存のプロジェクトにリンクするか、ソースファイルなどの構成ファイルを新しいソリューションフォルダまたはプロジェクトフォルダにごっそりコピーして、各プロジェクトに追加していく。その際、元のプロジェクトファイルをテキストエディタで開いて、構成ファイルや参照を確認しながら追加する。(この作業をやったことがある人は分かると思うが、本当にめんどくさい!)

2.開発バージョンを確認するときは、ソリューションファイルをテキストエディタで開き、以下の規則にしたがって判別する。

・VisualStudioVersion = xx.x.xxxxx.x の記述がない場合は上で書いたバージョン番号対応で確認する。
・VisualStudioVersion = xx.x.xxxxx.x の記述がある場合は、
 VisualStudioVersion = 12.0.xxxxx.x は Visual Studio 2013、
 VisualStudioVersion = 14.0.xxxxx.x は Visual Studio 2015
 とする。

Visual Studio 2017 はどうなってるんだろう。

※間違いがあれば指摘してください。

2012年12月7日金曜日


Windows の unicode と MAX_PATH

3年前だったか、仕事で付き合いのある人で変わった癖(?)のある人に出会った。
その人、自分でフォルダ名(ファイル名も)を付ける際に異常なほど長い名前を付けていた。
理由は、

「だってほら、一目で何のフォルダ(ファイル)か分かるでしょ?」

一目でっつっても、ファイル名が日本語の summary になっていた。
しかもご丁寧にどのファイルにも作成日が先頭に付いている。
もっと厄介なのは、どのファイル(フォルダも)も、何処に移動しても重複しないように意図されていたこと。
例えば年度フォルダなど、「2012」や「H24」といった簡素なものはまず無く、その親フォルダ名が頭に付いていたりする。
そら長くなるわ。
て言うか、階層の意味ねーじゃん、て言いたくなった。

だが人それぞれ。
毛の抜けたSEもいれば、毛の抜けたアンガス・ヤングもいる。

先日その人から連絡があった。
「毛の抜けたSEさんに影響されて、ちょっと前にプログラミングの勉強を始めた。。。」
ふむふむ。で?、まさか教えろとか言わねーよな。

「MAX_PATH って260バイトですよね?
 おかしいんですよ、400バイト超えてるんですけど平気なんです。」

別に私は貴方のファイルパスが260バイトを超えようが、3バイトに満たなくても平気ですけど。
っつーか、MAX_PATH ってまさか「C」やってるって言わねーよな!

「ご多分に漏れず C# です」

ブライアン・ジョンソンのように高音のしわがれ声で
「#?@! You!!!」
電話を切りたくなったが、ちょっと待てよ。

400バイトってどういう勘定してるんだ?

「だってほら、私が付けるファイル名は殆ど日本語の全角文字だから、
 単純に×2すると400バイトをゆうに超えるんですよ。」

ここにも居たか、shift-jis にしがみつく過去の遺物が。
かくいう私も、しがみつかざるを得ない状況なのだが。。。


今の Windows は内部では UNICODE (UTF-16) を使用している。

これは、全ての文字を2バイトで表現しようとするものだが、
世界中の文字が、たった 65,536 通りで表現できるはずもない。
よって、通常これだけあれば大丈夫だろうと思われる文字群を、
基本多言語面(BMP)という領域に収め、それ以外をサロゲートペアという
2つの2バイト域(計4バイト)で表現する。
つまり、通常2バイトだが、文字によっては4バイト使用する文字もあるということ。

もちろんこの人は、UNICODE の話をしているのではない。
shift-jis の話をしているのだ。だから「全角」という言葉が出てくる。

だが、ちょっと怖くなったので、今回改めてネットで検索してみたら、

やっぱり!

昔の shift-jis の頃に作成されたであろうページがわんさかヒットする。
それらのページの多くには、

「Windows の最大ファイルパス長は 260バイト

だが、新設なページも多数あった。

「Windows の最大ファイルパス長は 260バイト(UNICODE は260文字)」

そう、UNICODE では、206文字なのだ。
だから、

「あいうえお」と「aiueo」は同じ文字数の5文字だ。

shift-jis の世界では「あいうえお」は確かに10バイトだが、
UNICODE でも「あいうえお」はおそらく10バイトだろう。
ただし、「aiueo」は shift-jis では5バイトだが、
UNICODE では10バイトだ。

UNICODE は先にも言ったように、通常2バイトだから、変な漢字(失礼)や特殊な文字を使用していなければ、バイト数計算は楽だ。
だが、最大パス長を示す _MAX_PATH 定数が持つ「260」という数字は、

shift-jis の世界ならバイト数
UNICODE の世界なら文字数

という切り分けをしないといけない。
が、現在ではあまり意識しなくても良い環境になっている。

というのも、Java も C# も VB.NET も、て言うか Visual Studio も、
C++Builder も Delphi もすべて内部では UNICODE になっている。
Windows も UNICODE なんだから何も考えずに「文字数」でいけばいい。

ただし、ファイルを開いたり、保存したりするときは、文字コードに気をつけるのは言わずもがな、である。

あっ!あともう一つ。
USBメモリは良く使うが、NTFS でフォーマットできなかったりする。
確か、Windows日本語版は FAT には shift-jis に変換して送信するはず。
これはファイルの中身の話ではない。ファイル名の話だ。
だから上記の400バイト(実際は200文字以下だろう)を超えるパス長の状態そのままで、USBメモリにコピーするとエラーになる可能性がある。


しかし今や「全角」「半角」という言葉自体が消滅しつつあるのを、先に勉強した方が良い。
それを踏まえて「全角」「半角」という言葉を使って欲しい。

それと、「260」と言ったが、正確には「259」文字だ。
文字列の最後に null が付くので。

また、パス文字列の前に接頭辞として "\\?\" を付けると、約32,000文字までいけるらしいが、
そもそも最大パス長付近をウロウロしているようでは、まだまだフサフサだな。

2012年12月6日木曜日

C# タイプ別コントロール配列の取得(と、ジェネリックスを使った構造体の比較)

C# タイプ別コントロール配列の取得(と、ジェネリックス)


例えば、ある親コントロール内の子コントロール配列は、以下のようにすれば簡単に取得できる。


Control[] childControl = parentControl.Controls;

通常は以下のような使い方になるか。(フォームのコントロール一覧)

foreach (Control child in this.Controls)
{
  listBox1.Items.Add(child.Name);
}


この Control.Controls は Control.ControlCollection を返す。
普通 VisualStudio などで Windows フォームアプリケーションを開発していると、フォームのデザインはフォームデザイナで行うと思う。
その場合、あまり気づきにくいのだが、Form1.Designer.cs 内に、デザイナで行ったコントロールの追加や、位置およびプロパティの変更値などが書かれている。
例えば、フォームへのコントロールの追加は、この Controls プロパティの Add メソッドを使用している。

this.button1 = new System.Windows.Forms.Button();
this.Controls.Add(this.button1);

また、ある親コントロール内で、指定された名前のコントロールを探すには、上記の Name プロパティで探しても良いが、Controls.Find メソッドを使うと簡単だ。

Control[] controls = parentControl.Find("button1", true);

これは、例えば大量のボタンやラベル、テキストボックスを扱う時に、コントロールの名前を例えば、

button_01, button_02, button_03, ...

などのようにしておけば、プログラム内でシーケンシャルにアクセスできるだろう。

また、当然だが、コントロール名ではなくコントロールそのものが親コントロール内に存在するかどうかの確認も行える。

if (this.Controls.Contains(button1))
{
  MessageBox.Show("いやいや、当然でしょ");
}

しかしこれは、コントロールを動的に作成するような場合でなければ、あまり使うことはないだろう。

そして本題なのだが、コントロール名やコントロールそのもので検索するのではなく、タイプ別にコントロール一覧を取得したい場合がある。(えっ?俺だけ?)

namespace NantokaKantoka
{
  public class Honyalala
  {
    /// <summary>
    /// タイプ別にコントロールの一覧を取得
    /// </summary>
    /// <typeparam name="T">
    /// コントロールのタイプ(型)
    /// </typeparam>
    /// <param name="parent">
    /// 親コントロール
    /// </param>
    /// <param name="searchAllChildren">
    /// parent以下の全てのコントロールを検索するかどうか
    /// </param>
    /// <returns>
    /// 指定された型のコントロール一覧
    /// </returns>
    /// <exception cref="ArgumentNullException">
    /// parentがnullの場合
    /// </exception>
    /// <exception cref="ArgumentException">
    /// parentがControlではない場合
    /// </exception>
    /// <remarks>
    /// あまり使うことはないか...
    /// しかし例えば、大量のチェックボックスがあって、
    /// その全てのCheckedプロパティをfalseにするとか。
    /// いちいちコントロール名を書いていると疲れません?
    /// シーケンシャルな名前にして検索するのも一手だが、
    /// コントロール名には分かりやすい名前を付けたいもの。
    /// </remarks>
    /// <example>
    /// フォーム内の全てのCheckBoxのチェックをオフにする
    /// <code>
    /// foreach (CheckBox cb in
    ///   GetControlsByType&lt;CheckBox&gt;(this, true))
    /// {
    ///   cb.Checked = false;
    /// }
    /// </code>
    /// </example>
    public static T[] GetControlsByType<T>(
      Control parent, bool searchAllChildren)
    {
      if (parent == null)
        throw new ArgumentNullException();
      if (!(parent is Control))
        throw new ArgumentException();
      
      ArrayList list = new ArrayList();
      
      foreach (Control child in parent.Controls)
      {
        if (child is T) list.Add(child);
        if (searchAllChildren)
          list.AddRange(
            GetTypeControls<T>(
              parent, searchAllChildren));
      }
      
      return (list.Count == 0) ? null : (T[])list.ToArray(typeof(T));
    }
  }
}

使い方は example の通り、コントロールの型を指定する。

foreach (CheckBox cb in GetControlsByType<CheckBox>(this, true))
{
  cb.Checked = false;
}

C++ にもテンプレートがあって結構重宝していたが、C# のジェネリックスには敵わない。
C++ の場合はやっぱり後付け感満載で、インライン展開されるヘッダ内の巨大なマクロに変身するが、C# の場合まるで手足のように当然感満載なわけだ。
もちろん C# のジェネリックスには、C++ テンプレートに比べ不便なところもあるが、「型推論」を根底の考えに持つジェネリックスでは当然のことと言えよう。
ただ、上記のメソッドは普通のジェネリックメソッドと違い、いわゆる型引数がない(ControlCollection の中から目的の Type に変換可能なものだけを Type[] として返すため、引数で Type を指定するのではない。)
なので、

GetControlsByType<CheckBox>

の <CheckBox> を取ると、型推論できないと叱られる。
しかしこれは明示的に指定するのが正当なやり方のような気がする。
逆に、取り出す先を var にするのはもちろん可能だ。

foreach (var cb in GetControlsByType<CheckBox>(this, true))
{
  cb.Checked = false;
}

これは以前に紹介した var についてでも触れたが、ここでの var は CheckBox という型が決定している。

C# ジェネリックスの良いところは、コンパイル時に型推論が行われ、型を厳密にチェックしてくれる(リフレクションで型の判別ができない Java とは違う)のは言うに及ばず、VisualStudio では、コーディング時に知らせてくれるので非常に助かる。

ついでにジェネリックスを利用したメソッドを2つほど紹介する。
どちらも比較メソッドで、List (ジェネリックリスト)と配列の比較。

using System.Collections;

public static bool ListEquals<T>(List<T> list1, List<T> list2)
{
  // 型は<T>で統一しているので、
  // 型の比較は不要(コンパイルエラーとなる)
  
  // null のチェックとリストのアイテム数のチェック
  // null の引数を以下に通すとエラーになるため
  
  // どちらも null 参照の場合は同じものとする
  if (list1 == null && list2 == null) return true;
  // 片方のみ null 参照は違うものとする
  if (list1 == null || list2 == null) return false;
  // 両方とも空のリストは同じものとする
  if (list1.Count == 0 && list2.Count == 0) return true;
  
  IStructuralEquatable st1 = list1.ToArray();
  IStructuralEquatable st2 = list2.ToArray();
  
  return st1.Equals(st2, StructuralComprisons.StructuralEqualityComparer);
}

public static bool ArrayEquals<T>(T[] array1, T[] array2)
{
  // 型は<T>で統一しているので、
  // 型の比較は不要(コンパイルエラーとなる)
  
  // null のチェックとリストのアイテム数のチェック
  // null の引数を以下に通すとエラーになるため
  
  // どちらも null 参照の場合は同じものとする
  if (array1 == null && array2 == null) return true;
  // 片方のみ null 参照は違うものとする
  if (array1 == null || array2 == null) return false;
  // 両方とも空のリストは同じものとする
  if (array1.Length == 0 && array2.Length == 0) return true;
  
  IStructuralEquatable st1 = array1;
  IStructuralEquatable st2 = array2;
  
  return st1.Equals(st2, StructuralComprisons.StructuralEqualityComparer);
}

使い方はこんな感じ。

List<string> list1 = new List<string>();
List<string> list2 = new List<string>();

bool ret = ListEquals(list1, list2);

string[] str1 = new string[] { "abc", "def", "ghi" };
string[] str2 = new string[] { "abc", "def", "gigii" };

ret = ArrayEquals(str1, str2);

2つともやっていることは殆ど同じで、比較メソッドをサポートする IStructuralEquatable 型へ代入している。
List の方は、ToArray() メソッドを使用して配列に変換している。
「うん?」と思った方も多いだろう。
そう、List の比較でも以下のようにすれば ArrayEquals を使用できる。

bool ret = ArrayEquals(list1.ToArray(), list2.ToArray());

しかし、メソッド呼び出し時点で、list1 が null だったら、この呼び出し時点でエラーとなる。
なぜなら、null 参照の List で ToArray() メソッドは使えないからだ。

そうすると、以下のように事前にチェックが必要となる。

if (list1 != null && list2 != null)
  ret = ArrayEquals(list1.ToArray(), list2.ToArray());

実際のコードではこんなに単純ではないだろう。こういう煩わしいチェックを極力なくすのがオブジェクト指向の一端とも言える。
だからメソッド内でチェックできる環境を作る。
もちろん、これ以前に List 型変数の null 参照がコード内に存在しても良いかどうかは別問題となるが。

また、よく見かけるジェネリックメソッドの典型として、以下のように型特有のメンバを使用するための、型の型決めみたいなもの(条件とも言える)を指定する where なるものがある。

public static bool Comp<T>(T t1, T t2)
  where T : IComparable
{
  return (t1.CompareTo(t2) == 0) ? true : false;
}

これを制約条件というらしい。
こうしておけば、IComparable を実装している型しか与えることはできない。
逆に言うと where 以下を取り去ると、「CompareTo って何?」と言われる。
またこの制約条件にはインターフェイスなどの他に、「値型ですよ」という struct や、「参照型ですよ」という class などもある。
また、変わったところでは「引数無しのコンストラクタを有する」という new() なんかもあったりする。(複数指定する場合は最後に指定する必要がある)

何故上記の2つを紹介したかというと、元々構造体のメンバの等価比較が簡単にできないかとあれこれ考えていて、ジェネリックスを使うことにしたんだが、例えば以下のようなジェネリックメソッドを考える。

public static bool CompareStruct<T>(T st1, T st2)
  where T : struct
{
  return st1.Equals(st2);
}

上記で説明したように、制約条件には struct を指定する。
現在の C# の構造体は IEquatable を実装しているので、Equals メソッドが使える。
これで構造体の等価比較ができる。

ところが!

構造体のメンバが値型だけなら(String 型も OK)これで良いのだが、配列やクラスなどのメンバは対応できないんだなぁこれが!

論より証拠、以下のような構造体を作る。

struct TypeA
{
  public int a;
  public double b;
  public string c;
  public DateTime d;
}

これを作成したジェネリックメソッドでチェックする

TypeA typeA1 = new TypeA();
TypeA typeA2 = new TypeA();

typeA1.a = 1;
typeA1.b = 1.0;
typeA1.c = "one";
typeA1.d = new DateTime(1968, 10, 16);

typeA2.a = 1;
typeA2.b = 1.0;
typeA2.c = "one";
typeA2.d = new DateTime(1968, 10, 16);

bool ret = CompareStruct(typeA1, typeA2);

みごとに true が返る。
ところが、この TypeA にメンバを追加して以下のようにする。

struct TypeA
{
  public int a;
  public double b;
  public string c;
  public DateTime d;
  public int[] e;
  public double[] f;
  public string[] g;
  public List<string> l;
}

この状態で CompareStruct でチェックするとバッチリ false が返る!
これら値型以外のチェックは独自に行う必要がある。(めんどくさっ)

実はこれ以前に、私は C# で構造体を定義する時は、Equals と GetHashCode メソッドをオーバーライドしていた。
この時に使用していたのが上記2つの等価比較メソッドである。

struct TypeA
{
  public int a;
  public double b;
  public string c;
  public DateTime d;
  public int[] e;
  public double[] f;
  public string[] g;
  public List<string> l;
  
  public bool Equals(TypeA other)
  {
    return (this.a == other.a) &&
      (this.b == other.b) &&
      (this.c == other.c) &&
      (ArrayEquals(this.e, other.e)) &&
      (ArrayEquals(this.f, other.f)) &&
      (ArrayEquals(this.g, other.g)) &&
      (ListEquals(this.l, other.l));
  }
  public override bool Equals(object obj)
  {
    if (obj == null ||
      this.GetType() != obj.GetType())
      return false;
    
    return base.Equals((TypeA)obj);
  }
  public override int GetHashCode()
  {
    int retX = 0;
    
    retX ^= a;
    retX ^= b.GetHashCode();
    retX ^= c.GetHashCode();
    retX ^= d.GetHashCode();

    foreach (int val in this.e)
      retX ^= val;
    foreach (double val in this.f)
      retX ^= val.GetHashCode();
    foreach (string val in this.g)
      retX ^= val.GetHashCode();
    foreach (string val in this.l)
      retX ^= val.GetHashCode();
    
    return retX;
  }
}

実はこれ、IEquatable を実装しているのと同じで、定義する際に、

struct TypeA : IEquatable<TypeA>
{
  ...
}

と IEquatable<TypeA> と明示する。
そして、CompareStruct の制約条件に struct と IEquatable を付ける。

public static bool CompareStruct<T>(T st1, T st2)
  where T : struct, IEquatable<T>
{
  return st1.Equals(st2);
}

この状態で、ジェネリックメソッド CompareStruct でチェックすると、

bool ret = CompareStruct(st1, st2);

見事に true が返る!メデタシ、メデタシだが、元々構造体のみで等価比較ができるように Equals と GetHashCode をオーバーライドしたので、ジェネリックメソッドを使わなくても以下のように簡単に比較できる。

bool ret = typeA1.Equals(typeA2);

こちらも一行で済んでしまうので、今更ジェネリックもなぁ。
しかし、ジェネリックスを勉強してて思ったんだが、ますます持って Java を使うのが怖くなったなぁ。
C# は 2.0 からガラッと変わったが、Java も後方互換性ばかり意識せずに、Python のように「えいっ!」と変わったら?

2012年2月16日木曜日

C 系のビット操作と C# の enum

最近のプログラマはビット操作をやらないそうだ。
というより、毛の抜けた SE も今は滅多にやらなくなった。
そういう時代なのだろう。

だが、ビットをフラグとして扱うビット判定は今でもちょくちょく使う。
代表的な使用例が enum だ。以下 C# の例。

[FlagsAttribute]
enum AruFlags
{
    None   = 0x00, // 0000 0000
    One     = 0x01, // 0000 0001
    Two    = 0x02, // 0000 0010
    Three = 0x04, // 0000 0100
    Four  = 0x08, // 0000 1000
    All     = 0x0F // 0000 1111
};

enum は便利なので良く使う。
この enum にフラグ属性を持たせる場合は、上記のように宣言の前部に [FlagsAttribute] を加える。
それだけではダメで、中身の各 ID に対してビットフラグを定義する。
「うん?」
と思った人はプログラマだろう。
そう、何も [FlagsAttribute] を付けなくても、enum は中身は int なのだから、各 ID に対して 2 の y 乗を定義すれば良いんじゃない?
と思うだろう。実際、毛の抜けた SE は以前はそうしていた。
論より証拠、上記のコードから [FlagsAttribute] を取り去って、以下のようにビット判定しても何も問題は起きない。

AruFlags sonota = AruFlgas.One | AruFlags.Three;

if ((sonota & AruFlags.One) != 0) Console.WriteLine("One が入ってます");
if ((sonota & AruFlags.Two) != 0) Console.WriteLine("Two が入ってます");
if ((sonota & AruFlags.Three) != 0) Console.WriteLine("Three が入ってます");
if ((sonota & AruFlags.Four) != 0) Console.WriteLine("Four が入ってます");

/* 結果はもちろん以下のようになる
One が入ってます
Three が入ってます
*/

では何故、[FlagsAttribute] を加えるかというと、
MSDN リファレンスでは、
「列挙体をビット フィールド、つまりフラグのセットとして扱えることを示します。」
と要約していて、下部の解説で長々と説明しているが、あたかも、これを付けないとフラグ判定できませんよ、とも読める。
が、要するに、作り手(プログラマ)の礼儀作法を教育しているように思われる。
これは何も [FlagsAttribute] に限ったことではなく、最近の(以前から?) Microsoft の方針のようだ。
リファレンスの下部の解説部分でもそういう「ビットフィールドの作成方法」的な部分が大半を占める。

ということは、

「ビットフィールドとは何か」、
「ビットフィールドはどういう状況で使うのか」、
「ビットフィールドを作成・使用する際の注意点は何か」

という、昔は先輩のプログラマに教わったような初歩的な事柄が、

System.FlagsAttribute

という、enum でしか使わない特異で何だか怪しいものの解説ページに一緒に載っているのである。
もとい、大半を占めているのである。
異様な感じに包まれたのは、毛の抜けた SE だけだろうか。。。

しかし、違いはもちろんだが、ある。
FlagsAttribute のページではなく、列挙型のリファレンスにもあるように、
結果を文字列として表示するような場合、
例えば、

MessageBox.Show(sonota.ToString());

この結果が違ってくる。
[FlagsAttribute] 属性を付けると、「One, Three」と表示されるのに対して、
[FlagsAttribute] 属性を付けないと、「5」と表示される。


また、この [FlagsAttribute] 属性について、こんなコードを見つけた。(まんまではない)

[FlagsAttribute]
enum FileFlags
{
    None = 0,
    Read = 1,
    Write = 1 << 1,
    Create = 1 << 2,
    Delete = 1 << 3,
    All = 1 | 1 << 1 | 1 << 2 | 1 << 3
};

ビットフィールドを定義する際に、直に値を定義するのではなく、左シフト演算子を使って桁を上げていって、2 の y 乗を実現している。
懐かしくて、何だか心地よい気分になったもんだが、毛がフサフサなヒヨコにはどうか。
むしろ初心者は、2 のべき乗の値そのものを覚えておくべきだ。(せめて 1024 までは) というのが毛の抜けた SE の教育方針だ。



この enum 便利でよく使うが、あくまでも静的定義なので、VCL の 集合体 Set<> なんかみたいに、enum を組み込んで、Contains みたいなメソッドが使えないかなと思ってあれやこれや調べてたら、無い(笑)。
結局、Enum のクラスメソッドを使って実現するしか手はなさそう。
そして、作った関数がこれ。ID を文字列として検索して値を出力するのと、値を受け取って ID を文字列として返すもの。

public bool Search_Enum<TEnum>(string str, out int num)
{
    num = 0;
    try
    {
      var etype = typeof(TEnum);
      if (!etype.IsEnum) return false;
      if (Enum.GetUnderlyingType(etype) != typeof(int)) return false;
      foreach (int val in Enum.GetValues(etype))
      {
        if (str == Enum.GetName(etype, val))
        {
          num = val;
          return true;
        }
      }
      return false;
    }
    catch (Exception)
    {
      return false;
    }
}

public bool Get_Enum_Value<TEnum>(int num, out string str)
{
    str = "";
    try
    {
      var etype = typeof(TEnum);
      if (!etype.IsEnum) return false;
      if (Enum.GetUnderlyingType(etype) != typeof(int)) return false;
      if (!Enum.IsDefined(etype, num)) return false;
      str = Enum.GetName(etype, num);
      return true;
    }
    catch (Exception)
    {
     return false;
    }
}

う~ん、もっとスマートな方法は無いのかぇ?
しかし、これを作ったのにはもうひとつ理由があって、特に2つ目の関数がその目的なのだが、
enum の .ToString() って遅くない?
この関数つかっている部分と、.ToString() 使ってる部分とで、明らかに速度が違う。。。
何だかなぁ。。。

2012年2月15日水曜日

C# の var

C#3.0から使用できるようになった var ついて



MSDN リファレンス使用例あるのでまんまのコード使わせてもらう


例1

string[] words = { "apple", "strawberry", "grape", "peach", "banana" };
var wordQuery = from word in words
          where word[0] == 'g'
          select word;

foreach (string s in wordQuery)
{
    Console.WriteLine(s);
}



例2:
var custQuery = from cust in customers
          where cust.City == "Phoenix"
          select new { cust.Name, cust.Phone };

foreach (var item in custQuery)
{
    Console.WriteLine("Name={0}, Phone={1}", item.Name, item.Phone);
}





リファレンスにもあるように、例1は string 型しかあり得ないので、var を使う必要は無いが、もちろん使ってもよい。
ところが、例2は型が分からない、というより曖昧にしておいて、コンパイラに判断してもらおうというもの。
当然 foreach で要素を取得する場合の item 変数も明示的に var 宣言しないとダメ。


var は、JavaScript ではご存じローカル変数の一般的な宣言方法なので、C# の var とは全くの別物と理解できるが、言うほど別物でも無かったりするから厄介だ。
※注 Java は厳密な型指定が必要なので JavaScript とは違う。


というのも、今まで Script 系しかやってなくて、C# がおもしろそうだから手を出してみようなんていう輩が、「な~んだ、var あるじゃん」とか言いつつ、なんでもかんでもローカル変数に var 宣言する毛がフサフサなヒヨコが出てくる可能性があるんじゃなかろうか?
もしくは、Java (JavaScript ではない) しかやってなくて、「おっ!?これってあの var かい?」とか言いつつ、(同上)


構造型プログラミング言語である「C」のやり過ぎで毛が抜けてしまった SE としては、頭から湯気が出るような感じなのだ。


C# の var の最大の(?)特徴は、
例えば、int などの単純型(実は System.Int32 のエイリアスだったりする。(だから、new int() などという構文が存在する。これはコンストラクタを呼び出している))以外の、ちょっと型名が複雑になりがちなものを省略できるところにある。
おそらく、そういう意味もあって、例1を掲載しているんだと思う。
リファレンスにもあるように、例1は、IEnumerable<string> という型が決定している。
これが var だけで済むのだからありがたい。と、いうもの。


もちろん、例2での使い方が一般的で、LINQ クエリ操作などで真価を発揮するんだろうが、毛の抜けた SE としては、
「左辺が簡単になる」
というのが1番大きい。


Java も最近では色々と試しているようだが、何せほら、あのクラスの量(継承・多態)と何でもかんでもクラスっていうのは半ば辟易するし、コード補完機能が無い(またはレスポンスが悪い)エディタでプログラミングする気にならないし、何だかよく分からないし。。。


要するに、益々持って C# の方が優位性が高くなるなぁ と思う。
じきに、Android アプリが C# で普通に開発できるようになる はず。

2012年1月17日火曜日

.NET で dBASE (古っ!) テーブルの操作

MySQL を語ったので、ついでに懐かしい dBASE も。

DataReader 等の使い方はあちこちにチラホラしてるので割愛する。

て言うか、dBASE なんて今の人は知ってるんだろうか?
そもそも有名なバージョン IV は1988年つまり、Windows 以前に発売されていたとんでもない代物なのだ!
しかもバグが多く、なぜ Borland が買ったのかよく判らないが、Borland が買ったおかげで、BDE (ボーランド・データベース・エンジン) が生まれた。
その BDE を昔は本まで購入して使ってたなぁ...

C++Builder 3/4 の頃には、BDE 関数群の直接の使用は推奨されなくなっていた。(通常は VCL を通して使う。TDateSet や TTable で。)
もちろん私だけでなく、世界中にこの BDE 関数を直接使用していた人も数多く存在していただろうし、その後も当分の間は使っていた。何故かは後述。

dBASE の良さは、
小規模なスタンドアロン使用ではおそらく今でも最上のデータベースであること。
システムの復旧がし易いという理由で、テキストを好む SE にはうってつけのツールだ。
つまり、社内データベースとしては、比較的経験が浅い SE でも充分扱えるもので、同時に勉強にもなる。MySQL や SQL Server、Oracle など、そもそもバックアップってどうやるの?会社と家の両方で常に同じ状態にしたいけどどうやるの?などなど、dBASE なら簡単明瞭!何せ dBASE はテーブルがファイル単位だから、ファイルやディレクトリ単位でコピーすれば済む。それに、フォーマットが公開されているので、自分でテキストから dBASE テーブルに変換できれば、膨大な資産をテキストでバックアップしてあるような場合でも、あまり深く考えずにデータベース化できると思う。
そして実はこれが最大の利点なのだが、大昔のデータベースであるにもかかわらず、検索スピードは異常な程速いこと。
まぁ今でこそ、データベースを語る上でセキュリティは離せなくなったが、昔はデータベースといえばまずは検索スピードだった。いや、それだけだった。
だからこそ、MySQL はここまでもてはやされるのだ。MySQL の最大の売りはその速さにあるのだから。トランザクションな InnoDB の登場以前からデフォルト・ストレージエンジンとして MyISAM があることがそれを証明している。

BDE 関数を直接使用すると、この速さを享受できる。だからプログラマは皆これを使った。もちろん推奨されなくなったことでも分かるように、この関数群が一筋縄ではいかない(笑)
まぁそれが楽しかったとも言えるが、ホントに苦労するんです、dBASE は。
昔の(15年ほど?)、何とかシステム、何たら情報システムの数パーセントは dBASE を使ってるんじゃないだろうかと思わせるほど有名だったが、大量のデータに対応できない構造になっているので(フィールドの構造にもよるが、10万件を超えるとヒーヒー言うようになり、100万件を超えると今の CPU とメモリでも結構きつくなる)、あっという間に廃れていった感じ?

BDE 関数の直接使用は興味がある人はメールください。教えます(笑)。

しかし、日曜プログラミング的な感じで、「なんちゃってシステム」みたいなものを作るとき、殆どの素人に毛が生えた程度のプログラマなら、ACCESS なんかで作ったりしてたよね?(今、書いただけで鳥肌が立った)
でも、dBASE の方が断然速いし、「なんちゃってシステム」はそんなにセキュアじゃなくて良いでしょ?

何はともあれ、こんな感じで。

using System.Data.OleDb;

namespace やら class やら
{
    string conStr =
        "Provider=Microsoft.Jet.OLEDB.4.0;" +    // お約束
        "Data Source=C:\\dBASE_Test;" +          // テーブルがあるフォルダ
        "Extended Properties=dBase IV;";            // dBase IV しか知らない (III はダメ)

    OleDbConnection dBaseCon = new OleDbConnection(conStr);
    OleDbCommand sql = dBaseCon.CreateCommand();
    OleDbDataAdapter adapter = new OleDbDataAdapter(sql);
    OleDbCommandBuilder = new OleDbCommandBuilder(adapter);

    // DataTable を使う場合
    DataTable dt = new DataTable();
    sql.CommandText = "select * from table_name.dbf";
    adapter.Fill(dt);
    // dt を使って Grid 表示したり、あれやこれや...

    dBaseCon.Open();

    // 編集も上記のように SQL 文を発行する (DataTable の機能は使わない)
    // これを身につける
    sql.CommandText = "insert into table_name.dbf values (" +
        "\"" + field1_value + "\", " +    // フィールド値が文字列の場合は「"」で囲む

        "\"" + field2_value + "\", " +
        "\"" + field3_value + "\")";

    sql.ExecuteNonQuery();

    dBaseCon.Close();
}

結局、他のテーブル操作と何ら変わることはない。
ただ若干 dBASE 仕様が所々出ているが、それだけだ。
ちなみに、DataTable は、テーブルのレコードを読み込んで、データを表示したりするのには便利だが、書き込みはイマイチ信用できない(あくまで個人的な意見だが)。
なので、
DataTable でグリッドにレコードを表示して、ユーザに編集させるような場合、Update の段階で、DataTable の各レコードのステータスを確認して、SQL 文を生成するようにした方が、面倒だが確実にテーブルを編集できる。

以前、MySQL の所でも言ったが、SQL 文のベタ書きが一番良い。プログラムで自動生成するようにするのがベスト。Builder の VCL や、.NET の DataTable のようなものは便利だが、他に移植するのが辛くなり、結局移植どころか移行もできないといった状況になる。特に上記のような編集作業は必ず SQL 文で行うようにすれば、移植が大変楽になる。
「オレは移植なんかしないよ!一生このままさ!ざまぁみやがれ!」
と言いたくなるのは、まだまだ毛がフサフサなヒヨコだ。
今の時代は、開発プラットフォームが乱立する、まさに混沌とした時代だ。
こんな状況で、未だに「VB なら出来ます」「Java を勉強して1年経ちました」などと平然と答えている若きプログラマを見ると、”何とフサフサな” と思ってしまう。
VB なら VB.NET だし、VB6 までとはまるで別物だし、.NET 繋がりで C# にもスムーズとはいかないまでも、勉強する価値はあるし、チャンスだと思う。
また、最近は Android の影響でまた「Java」ブームが再燃しているが、私も Java を勉強したので良いところは分かっているつもりだが、それでも Java は遅い、ということを認識しなくてはならない、何でもいいから取り敢えず Java で開発だ、と言うのは愚の骨頂だ。
ちなみに、Java はオブジェクト指向型だが、「オブジェクト指向」が目的となってしまっている。「オブジェクト指向」はあくまでもプログラミングの手段に過ぎない。これを言い出すと長くなるのでやめます。あっ、どこかのサイトに Java と C# を比較してたな、あのサイトは面白かった。また確認できたら載せます。