2008年01月30日

C#3.0:オブジェクト初期化しを使うべし

   このエントリーをはてなブックマークに追加 Clip to Evernote
ブログ「当面C#と.NETな記録」の記事『■[C#] オブジェクト初期化子とコレクション初期化子』の「オブジェクト初期化子とコレクション初期化子が LINQ を支える影の大黒柱だったんですね。」

という言葉に触発されて、C#でちょっとしたLINQのコードを書いてみた。

var db = new NorthwindDataContext();
db.Log = Console.Out;
var query = from e in db.Employees
select new SampleEmp {
Name = e.FirstName + " " + e.LastName,
Age = DateTime.Today.Year - e.BirthDate.Value.Year
};
foreach (var e in query) {
Console.WriteLine("{0} {1}", e.Name, e.Age);
}


以下は、まったく同じことをやるのに、クラスのコンストラクタを使っている。

var db = new NorthwindDataContext();
db.Log = Console.Out;
var query = from e in db.Employees
select new SampleEmp(
e.FirstName + " " + e.LastName,
DateTime.Today.Year - e.BirthDate.Value.Year
);
foreach (var e in query) {
Console.WriteLine("{0} {1}", e.Name, e.Age);
}


なお、SampleEmpクラスは、以下のようなクラス。

class SampleEmp {
public SampleEmp(string name, int age) {
Name = name;
Age = age;
}
public SampleEmp() { }
public string Name { get; set; }
public int Age { get; set; }
}


結果は2つとも同じだし、発行されるSQLも同じ。

しかし、次のコードはどうなるのか?

var db = new NorthwindDataContext();
db.Log = Console.Out;
var query = from e in db.Employees
select new SampleEmp {
Name = e.FirstName + " " + e.LastName,
Age = DateTime.Today.Year - e.BirthDate.Value.Year
} into t
orderby t.Name
select t;
foreach (var e in query) {
Console.WriteLine("{0} {1}", e.Name, e.Age);
}


このSQLは、

SELECT [t1].[value] AS [Name], [t1].[value2] AS [Age]
FROM (
SELECT ([t0].[FirstName] + @p0) + [t0].[LastName] AS [value],
@p1 - DATEPART(Year, [t0].[BirthDate]) AS [value2]
FROM [dbo].[Employees] AS [t0]
) AS [t1]
ORDER BY [t1].[value]


だ。ほー、良くできていますね。DATEPARTなんて関数があったんだ。

では、コンストラクタを使った場合は、どうなるのでしょうか?

var db = new NorthwindDataContext();
db.Log = Console.Out;
var query = from e in db.Employees
select new SampleEmp(
e.FirstName + " " + e.LastName,
DateTime.Today.Year - e.BirthDate.Value.Year
) into t
orderby t.Name
select t;
foreach (var e in query) {
Console.WriteLine("{0} {1}", e.Name, e.Age);
}


無事、コンパイルできました。
しかし、実行してみると、以下の例外が発生してしまいます。

System.NotSupportedException はハンドルされませんでした。
Message="メンバ 'Gushwell.SampleEmp.Name' には、サポートされる SQL への変換
がありません。"


つまり、コンストラクタを使ってしまうと、コンストラクタの中で何が起きているかが分からないので、orderby t.Name の Nameプロパティが、その前のクエリとどう関連しているのかが理解できず、SQL文にすることが出来ないわけです。

そういう意味からも、LINQ + C#では、「オブジェクト初期化しを使うべし」が当てはまることになりますね。



この記事へのコメント
おぉー、LINQ to SQL でもオブジェクト初期化子は重要なんですね。LINQ のオマケかと思ってたら実は重要な役割を担ってたようですねぇ。
Posted by siokoshou at 2008年01月30日 22:33
オブジェクト初期化子は、隠れたヒーローというわけですね。
それと、例が匿名クラスになっていました M(_ _)m
直しておきます。
Posted by Gushwell at 2008年01月31日 08:59
 

この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/gushwell/51376741