はじめに
使用する言語がC#にせよVB.NETにせよ、また、アプリケーションのフロントエンドとバックエンドのどちらを主に担当するにせよ、データ処理の技能は、プロジェクトの中で自分がどれだけ力となれるかを直接左右する要因です。
.NETの新しいジェネリッククラスでは、シンプルなコードで多くの処理を実行できるため、効率が飛躍的に向上します。また、ASP.NET 2.0のObjectDataSourceクラスでは、中間層のデータ処理クラスとWebページ上のデータバインドコントロールを簡単に連係できます。さらに、前編でも取り上げたように、T-SQL 2005には、データベース開発者の生産性向上に寄与する新しい言語機能が備わっています(今回の記事にも登場します)。
データ処理をテーマとした全2回の後編となる今回は、.NETのジェネリック、ASP.NET 2.0のObjectDataSource、前回取り上げた以外のT-SQL 2005の新機能について解説します。
過去の記事
目的:データ処理の包括的なヒント集
私は、太陽がさんさんと降り注ぐ自宅のテラスでこの文章を書いています。ちょうど、コミュニティイベントやカンファレンスでの講演回りの長旅を終えたばかりです。その経験で得た大量のエネルギーをこの記事に注入し、これまでになくホットなVisual Studio 2005とT-SQL 2005のデータ処理機能について解説したいと思います。
では、プレイボールといきましょう。「The Baker's Dozen」のダブルヘッダー第2戦、今回の打順を紹介します(もちろん、野球のメンバーは9人ですが、この記事のヒントは13個です)。
- 1番~7番は、.NETのジェネリックを活かすための7つのヒントです。今回の記事で特にポイントとなるのはヒント7です。型指定されたデータセットにストアドプロシージャからデータを読み込む、ジェネリックデータアクセスクラスについて取り上げます。
- 中位打線のヒント8では、ASP.NET 2.0の新しいObjectDataSourceの機能について、簡単な例で紹介します。
- 残る下位打線は、バックエンドのデータベースについてです。つまり、ヒント9~13では、T-SQL 2005でのデータの処理方法について、前編で取り上げた以外の話題を解説します。
ヒント1:.NETのジェネリックの概要
多くの場合、.NETのジェネリックは、Visual Studio .NET 2003で開発者が目指した処理に照らしてみるとわかりやすくなります。
Visual Studio 2003では、オブジェクトをリストに格納するときに、ArrayListコレクションクラスを使用することがよくありました。次のコードのような形です。この例では、整数のリストを格納しているだけですが、フォームオブジェクトやデータテーブルなど、その他のオブジェクトのコレクションの場合も、同じように簡単に格納できます。
// Visual Studio 2003 code ArrayList list = new ArrayList(); // implicit boxing, must convert value // type to reference type list.Add(3); list.Add(4); list.Add(5.0); int nFirst = (int)list[0]; int total = 0; foreach (int val in list) total = total + val;
このコードには、次のような3つの問題が潜んでいます。
- 1つ目は、ArrayListに実際に格納する具体的な型に関係なく、ArrayListにはオブジェクトとして格納されてしまうという点です。したがって、「ボックス化」という暗黙的な変換処理が発生します。
- 2つ目は、ArrayListから特定のアイテムを取得するときに、.NETがそのアイテムをオブジェクトから元のデータ型に変換する必要があり、そのデータ型をプログラマが指定する必要があるという点です。したがって、「ボックス化解除」の処理が発生します。
- 3つ目は、コンパイル時のタイプセーフ性のチェックがないという点です。上のコードでは、整数2つと実数1つをArrayListに格納しています。最後の2行で、各値に対して反復処理を行うときに、実行時エラーが発生します。実数値(5.0)を整数値にキャストしようとするからです。
Visual Studio 2005では、System.Collection.Generic名前空間にある、新しいListクラスを使用できます。Listクラスでは、ArrayListクラスが持つ問題に対処できます。クラスのインスタンスに格納する型を具体的に指定できるからです。
// Visual Studio 2005 code // define the stored data type in the placeholder List<int> aList = new List<int>(); aList.Add(3); aList.Add(4); // The following line generates a compile error aList.Add(5.0); // no need for unboxing, value type stored in // List<int> as an int, not an object int nFirst = aList[0]; int total = 0; foreach (int val in aList) total = total + val;
Listクラスのインスタンスを作成するときに、格納する型を指定しているので、.NETがボックス化やボックス化解除を実行する必要がなくなります。整数を格納するということを判断できるからです。また、List内の特定のアイテムをキャストする必要もありません。
インスタンス化の回数がよほど多くない限り、パフォーマンスに差は出ないでしょうが、格納の効率やコンパイル時のタイプセーフ性が高まるという点だけでも、ArrayListクラスとListクラスの違いとしては十分すぎるほどです。
とはいえ、ArrayListクラスの方がパフォーマンスが若干低いという点も、きちんと押さえておくことにしましょう。次のコードでは、ArrayListを作成し、100万個の整数を格納したうえで、リスト内の各項目に対して反復処理を実行します。このコードの実行には、約0.2秒を要します。
// Visual Studio 2003 code - takes .2 seconds ArrayList aBigList = new ArrayList(); DateTime dt1, dt2; dt1 = DateTime.Now; for (int nCtr = 0; nCtr < 1000000; nCtr++) aBigList.Add(nCtr); int nSum = 0; foreach (int nn in aBigList) nSum += nn; dt2 = DateTime.Now; TimeSpan ts = dt2.Subtract(dt1); MessageBox.Show(ts.TotalSeconds.ToString());
一方、新しいListクラスを使う形でこのコードを書き直すと、実行に要する時間は0.04秒になります。つまり実行速度は5倍になります。
// Visual Studio 2005 code - takes .04 seconds List<INT> aBigList = new List<INT>(); DateTime dt1, dt2; dt1 = DateTime.Now; for (int nCtr = 0; nCtr < 1000000; nCtr++) aBigList.Add(nCtr); int nSum = 0; foreach (int nn in aBigList) nSum += nn; dt2 = DateTime.Now; TimeSpan ts = dt2.Subtract(dt1); MessageBox.Show(ts.TotalSeconds.ToString());
次に、ジェネリックメソッドを見てみましょう。これは非常に柔軟なコードを実現できる機能です。昨年のMSDN CodeCampのイベントでCarl Franklin(.NET Rocks!で有名)が話していたことですが、ジェネリックの優れた利用法の1つに、型のみが異なる複数のクラスでの使用があります。
ここでは、2つの値を比較して、大きい方の値を返すというコードで考えてみることにしましょう。このコードは、文字列、日付、整数など、さまざまな値に対して使えるものとします。この場合、複数のメソッドを作成する方法、1つのメソッドをさまざまにオーバーロードさせる方法、何らかの裏技を使う方法などが考えられます。しかし、.NETのジェネリックを利用すれば、メソッドを1つ作成するだけで複数のデータ型に対応できるのです。これこそ、.NETのジェネリックの本領発揮です。
次のコードでは、CalcMax
というメソッドを作成しています。T
という文字と、プレースホルダ<T>
を使用している点が目を引きます。ここで使う文字はTでなくてもかまいません。別の文字や、まったく異なる単語を使用することもできます。しかし、型(Type)を表すプレースホルダとして、Tという文字は理にかなっています。
public T CalcMax<T> ( T compVal1, T compVal2) where T : IComparable { T returnValue = compVal2; if (compVal2.CompareTo(compVal1) < 0) returnValue = compVal1; return returnValue; }
このCalcMax
メソッドでは、1つの戻り値と2つのパラメータに型のプレースホルダを定義しています。渡す型が従うべき唯一の規則は、IComparable
を実装している必要があるという点です。なぜなら、パラメータのCompareTo
メソッドを使用するからです。
CalcMax
メソッドは何回でも呼び出すことができ、そのたびに異なるデータ型を指定できます。
double dMax = CalcMax<DOUBLE>(111.11,333.23); int intMax = CalcMax<INT>(2, 3); string cMax = CalcMax<STRING>("Kevin", "Steven"); DateTime dtMax = CalcMax<DATETIME> (DateTime.Today, DateTime.Today.AddDays(1));
ヒント2:ジェネリックによるカスタムクラスの格納
Listクラスはカスタムクラスの格納にも使用できます。次のコードはBaseballClass
という簡単なカスタムクラスです。チーム名と選手名を表すプロパティを持ちます。
using System; using System.Collections.Generic; using System.Text; namespace GenericsDemo{ public class BaseballClass { private string name; public string Name { get { return name; }} private string team; public string Team { get { return team; } } public BaseballClass(string name, string team) { this.name = name; this.team = team; } public override string ToString() { return team + ", " + name; } } }
次のコードに示す関数(PopulateBaseballClass
)では、Listクラスのインスタンスを作成し、そこにBaseballClass
のインスタンスを格納しています。
List<BaseballClass> PopulateBaseballClass() { List<BaseballClass> oBaseball = new List<BaseballClass>(); oBaseball.Add(new BaseballClass("Kenny Rogers", "Tigers")); oBaseball.Add(new BaseballClass("Michael Young", "Rangers")); oBaseball.Add(new BaseballClass("Ken Griffey, Jr.", "Reds")); oBaseball.Add(new BaseballClass("Tom Glavine", "Mets")); oBaseball.Add(new BaseballClass("David Ortiz", "Red Sox")); oBaseball.Add(new BaseballClass("Derek Jeter", "Yankees")); oBaseball.Add(new BaseballClass("Roger Clemens", "Astros")); oBaseball.Add(new BaseballClass("Roy Oswalt", "Astros")); return oBaseball; }
そして次のコードでは、BaseballClass
のインスタンスを作成し、上記のPopulateBaseballClass
関数を呼び出し、その後でこのコレクションを反復処理します。
private void TestBaseballClass() { List<BaseballClass> oBaseball = new List<BaseballClass>(); oBaseball = this.PopulateBaseballClass(); string cResults = ""; foreach (BaseballClass oRecord in oBaseball) cResults += oRecord + "\r\n"; MessageBox.Show(cResults); }