Archive - C#
C#とLinq to JsonとTwitterのChirpUserStreamsとReactive Extensions
何か盛り沢山になったのでタイトルも盛り沢山にしてみました。SEO(笑)
最近話題のTwitterのChirpUserStreamsを使ってみましょー。ChirpUserStreamsとは、自分のタイムラインのあらゆる情報がストリームAPIによりリアルタイムで取得出来る、というもの。これを扱うには、まずはストリームをIEnumerable化します。そのまま扱うよりも、一度IEnumerable化すると非常に触りやすくなる、というのがLinq時代の鉄則です。C#でのストリームAPIの取得方法は以前にも記事にしましたが、かなり汚かったのでリライト。WebClient愛してる。
public static IEnumerable<XElement> ConnectChirpStream(string username, string password) { const string StreamApiURL = "http://chirpstream.twitter.com/2b/user.json"; var wc = new WebClient() { Credentials = new NetworkCredential(username, password) }; using (var stream = wc.OpenRead(StreamApiURL)) using (var reader = new StreamReader(stream)) { var query = reader.EnumerateLines() // 1行に1JSONなのです .Where(s => !string.IsNullOrEmpty(s)) // 空文字が来るので除去 .Select(s => // 文字列JSONからXElementへ変換 { using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.Default.GetBytes(s), XmlDictionaryReaderQuotas.Max)) return XElement.Load(jsonReader); }); foreach (var item in query) yield return item; // 無限列挙 } } // StreamReaderの補助用拡張メソッド(あると大変便利) public static IEnumerable<string> EnumerateLines(this StreamReader streamReader) { while (!streamReader.EndOfStream) { yield return streamReader.ReadLine(); } }
中々シンプルに書けます。C#もLLと比べても全然引けを取らないでしょう(誰に言ってる)。ChirpUserStreamsはJSONでしか取れないのですが、StreamAPIではXmlよりもJSONのほうが使い易いので、JSONだけでも全然問題ありません。とはいえ、C#でJSONはちょっと扱いにくいんだよねー?と思いきや、意外に普通に標準ライブラリだけで何とかなりました。
参照設定にSystem.Runtime.Serializationを加えます(注:VS2008ではSystem.ServiceModel.Webを参照設定に加えてください)。この参照で通常使うのはDataContractJsonSerializerだと思いますが、もう一つ、JsonReaderWriterFactoryというクラスが用意されていて、このReaderはJSONをXmlReaderとして扱うことが出来ます。この図式は以前のLinq to HtmlのためのSGMLReader利用法と同じです。そのままXElementに流しこめば、JSONをXmlとして扱って、Linq to Jsonが成り立ちます。
さて、StreamAPIなのでEndOfStreamは来ない。繋ぎっぱなし、つまりはConnectChirpStreamメソッドは無限リストになります。この中には色々な情報が混ぜこぜになって来ます。投稿した、という他にも、誰かをフォローした、何かをふぁぼった、何かをリツイートした、などなどなどなど。クライアントソフト作るなら、当然どの情報も漏れ無く扱いたいわけですが、どうしましょう?foreachでグルッと回して、ifで分ける、しか、ない、かもですね? しかしそれは敗北です。退化です。foreachを使ったら負けだと思っている。
ところで突然に、今のところ思うReactive Extensions for .NET (Rx)を使うメリットは3つ。「複雑になりがちな複数イベントの合成」「同じく複雑になりがちな非同期処理のLinq化」そして、「列挙の分配」。従来型のLinqでは、一回の列挙には一個の処理しか挟めませんでした。例えば、MaxとCountを同時に取得する方法はなかった。MaxとCountを別々に二度列挙するか、または旧態依然なやり方、つまりforeachでグルグルと回してMaxとCountを手動で計算するかしかなかった。それはIEnumerableがPullモデルなためで、PushモデルのIObservableならば、出来ないこともない。
では、Rxでこのストリームを分配してみましょう。
static void Main(string[] args) { // IEnumerableをIObservableに変換し、Publish(Connectするまで列挙されない(ので分配が可能になる)) var connecter = ConnectChirpStream("username", "password") .ToObservable() .Publish(); // 1件目は必ず自分のフレンドのIDリストが来るらしいっぽいのでまるっと保存 HashSet<int> friendList; connecter.Take(1).Subscribe(x => friendList = new HashSet<int>( x.Element("friends").Elements().Select(id => (int)id))); // どんなのが来るのかよく分からないのでモニタ用にテキストにまるっと保存 var sw = new StreamWriter("streamLog.txt") { AutoFlush = true }; connecter.Subscribe(x => sw.WriteLine(x)); // userがあるなら普通の投稿(ってことにしておく) connecter.Where(x => x.Element("user") != null) .Select(x => new { Text = x.Element("text").Value, Name = x.Element("user").Element("screen_name").Value }) .Subscribe(a => Console.WriteLine(a.Name + ":" + a.Text)); // favoriteとかretweetは "event":"favorite" というJSONが来る var events = connecter.Where(x => x.Element("event") != null); // favoriteの場合の処理 events.Where(x => x.Element("event").Value == "favorite") .Subscribe(x => Console.WriteLine(x)); // favorite用の何か処理 // retweetの場合の処理 events.Where(x => x.Element("event").Value == "retweet") .Subscribe(x => Console.WriteLine(x)); // retweet用の何か処理 // 同期か非同期かは、ToObservableの引数で変わる。デフォルトは同期 // Scheduler.ThreadPoolを引数に入れるとThreadPoolで非同期になる connecter.Connect(); // 列挙開始 }
ID一覧取得、テキスト保存、投稿時処理、Fav時処理、リツイート時処理の5つへの分配が非常にスマートに出来ました。ToObservable、Publish、Connect。たったこれだけで一つストリームを複数に分配することが出来ます。普通にそれぞれをWhereだのSelectだの、独立してLinqでコネコネ出来ました。で、何が嬉しいかっていうと、それぞれが完全に独立していて見やすいってのは勿論あります。あと、部品化されてるので外部に分割しやすくなるんですね、物凄く。組み合わせたりもしやすいし。
結論
Rxヤバい。というわけで、みんなRx触ろう! .NET Framework 4.0ではRxで使うIObservableとIObserverインターフェイスが搭載されています。インターフェイスだけでどうすんだよボケ、っていうと、実際のところどうにもなりませんね、たはー。それでもインターフェイスだけ先行搭載ということは、RxはDevLabs内だけで終わる実験的プロジェクトではなく、必ず標準搭載するから安心しろよ!というメッセージだと受け取ることにしました。きっと.NET Framework 4.0 SP1には標準搭載されます。される、と、いいなあ。ちなみにRxは初期の頃と結構変わってますし、まだ変わるかも。でも、だからこそ、それに付き合うのも楽しいってものですよ?
ああ、あと、ChirpUserStreamsもヤバいですね。リアルタイムでゴリゴリ迫ってくる感覚は素敵というか、なんか別次元のメディアになった感じでもあります。今、新規にTwitterクライアント作るならStreamAPI完全対応すれば差別化出来て良いですね!私は作りませんが。ただ、ストリームAPIとRxは相性良いと思うので、ストリームAPI時代の到来と同時にRx時代も到来!する、かなあ?
ToDo
linq.jsがようやく一段落したので、Rxの紹介もまたやっていきたいですねー(すみません、かなり長いこと放置していて)。ToObservableによる列挙の分配は中々に強烈なので、linq.jsとRxJSを橋渡しするようなコードというかJSというかも用意したいなあ(軽く作ってみたんですが、RxJSでもPublishで分配出来て中々に威力ありそうでした)。うーん、考えてみるとやることはいっぱいあるねえ。適当に待っていてください。
ParseOrDefault
- C# - 10.04/09
match があれば TryParse いらないんじゃないか - 予定は未定Blog版という記事を見て、outとかrefは確かにしょっぱい。そういえばF#はTryParseはTuple返しで、おまけに let success, value = int.TryParse “123″ という自然な多値返しが出来てCOOLなんだよねー。などと思ってC#でTuple返しにしたものを書いたりしたのですが、どうもしっくりこなくて延々と弄っているうちに明後日の方向へ。
さて、で、「数値に変換できるなら変換して返し、できないなら -1 を返す」というシチュエーションは大変多くて、それにoutを使って組み上げるのは、とてもかったるい、大変避けたい。かといってStringへの拡張メソッドを大量に生やすのも、IntelliSense的な観点からして抑えたい(それにしてもT4 Template使って生成ってのは面白いですね)。なので、汎用的に使えることは捨てて、上述のシチュエーション、変換出来るなら変換して、出来ないなら指定したデフォルト値を返すことにのみ絞って、拡張メソッドにしました。Stringに生やすのは抵抗感があるって場合はUtil.ParseOrDefaultとかにしてもいいと思います。
using System; static class Program { static void Main(string[] args) { // 例えばURLのクエリストリングのパースをしたい時とかありますよね! // num=100にint.Parse(num)はnum=hugaが来たら死ぬのでデフォでは0にしたいとか var r1 = "100".ParseOrDefault(int.TryParse, -1); // 100 var r2 = "huga".ParseOrDefault(int.TryParse, -1); // -1 // 勿論、int.Parse以外でも何でもいけます var r3 = "2000/12/12".ParseOrDefault(DateTime.TryParse, DateTime.Now); // デフォルト値を省く時は型の明記が必要 var r4 = "2000/12/12".ParseOrDefault<DateTime>(DateTime.TryParse); } } public static class StringExtensions { public delegate bool TryParse<T>(string input, out T value); public static T ParseOrDefault<T>(this string input, TryParse<T> tryParse) { return input.ParseOrDefault(tryParse, default(T)); } public static T ParseOrDefault<T>(this string input, TryParse<T> tryParse, T defaultValue) { T value; return tryParse(input, out value) ? value : defaultValue; } }
out付きの汎用Funcはデフォルトでは定義されていません。なので、自前で定義する必要があります。この辺の話は@takeshikさんのスライドわんくま東京#38 LT 「Func<> と ref / out 小咄」が詳しいのでどうぞ。こんなデリゲートは単体では使うことは滅多にないでしょうから、static classの中に閉じ込めておくことで名前空間を汚しません(笑)
引数にT defaultValueを要求することで、型推論が働いてTryParseを渡す際に明示的な型付けが不要になります。C#の型推論(と言っていいのかな?)がScalaやF#に比べて弱いのは事実なので、ここは、C#でも自然な形で扱えるような誘導、設計が大事です。型を書いたら負けだと思っている、みたいな。まあ、どんなに頑張っても負けるときは負けるので、その時は潔く書くことにします。
ちなみに、ParseOrDefaultは最初はこんな形でした。酷過ぎる。私は骨の髄まで関数型言語脳ならぬLinq脳なので、とにかくまず無理矢理にでもLinqで処理する方法を考えて、出来上がってふと冷静に眺めると、普通に書けよ馬鹿、ということに気付いて普通に書き直すというサイクルを取ってます。馬鹿すぎる。DbExecutorで紹介したyield returnで一個のみの列挙を返すというのを、やたら使って見ちゃったりするところが麻疹。Repeat(value,1) と Repeat(value,int.MaxValue)があればLinqは何でも記述出来るんです病。
DbExecutor - Linqで操作しやすいSQL実行ライブラリ
- C# - 10.04/07
前回の記事を書いたところ、ついったで素敵な突っ込みを頂けたので、それを元にもう少し練り直してライブラリ化し、CodePlexに公開しました。ライブラリといってもAnonymousComparerと同じく単純なものですので、ソースコード一本のみ。ご自由にお使いください。例によってCodePlexでの英語がヤバい(小学生レベル、とりあえず何でもforつけておけばいいだろ、的な)ですね、世の中厳しい。
Linq to Sqlと名乗りたいところなのですが、本物がありますから名乗れないー。以前はIQueryableじゃないものをLinq to Hogeって言うのはどうよ、なんて思っていたのですが、考えてみるとLinq to XmlもXMLをIEnumerableベースに処理しやすいような構造を持たせたクラス群にすぎず、別にIQueryableは関係ない。Rxもそうで、あれはIEnumerableでもIQueryableでもなく完全に独立している、けれど、Linq to Events。ようするにLinq的な操作が出来ればLinqなわけです。というわけで、これはSqlをIEnumerableベースで処理出来るようにしたLinq to DB。でも本当に超絶薄いラッパーにすぎないのであんまカッコつけた名前付けるのも恥ずかしくDbExecutorという極々普通の名前に落ち着きました。「Linqで操作しやすい」とかいう釣りタイトルをつけてますが、ただたんにIEnumerable返すというだけです。SQL周りは真剣に追いかけると無限泥沼になる気がする(Entity Framework、そして更にその次へと……?)。追いかけてみたいですが、まあ、まずは、一歩目から。
何で作ったかと言うと、SQLを扱っていて嫌なusing地獄を殺したかったから。
using (var conn = new SqlConnection("connectionString")) using (var cmd = conn.CreateCommand()) { conn.Open(); cmd.CommandText = "select ...."; using (var reader = cmd.ExecuteReader()) { foreach (IDataRecord item in reader) {
ちょっとクエリ呼びたいだけなのに、普通にこの量、このネスト。こんなんだからLLの人に馬鹿にされてしまうんだよ。で、ふと思ったのは、この図式ってもしかしてWebRequestと同じですか?
string html; var req = (HttpWebRequest)WebRequest.Create("http://google.co.jp"); using (var res = req.GetResponse()) using (var stream = res.GetResponseStream()) using (var sr = new StreamReader(stream)) { html = sr.ReadToEnd(); }
ちょっとWebからデータを取得したいだけなのに狂ったようにusingを重ねなければならない!そしてもう一つ嫌なのが、変数に渡す際に、usingのスコープが絡むので場合によっては外で定義しなければならないこと。string html;だってさ。嫌だ嫌だ。別にvarが使えないから嫌だと言っているわけじゃなくて(半分はそうなのですが)、代入位置と宣言が離れるのは可読性が落ちます。あとは、単純に不恰好ですしね。そんなWebRequestですが、WebClientという簡単に使えるものが用意されているので、普段はこっちを使うわけです。
var html = new WebClient().DownloadString("http://google.co.jp");
素晴らしい! 私はWebClientが好きです。簡単なのは良いこと、を体現していますから。ネット上のサンプルがやたらとWebRequestを使うものばかりなことを嘆きます。WebRequestとWebClientでCookie認証をする方法とかいう記事を書いたりと、必死に普及に励んだりしていますが中々どうして焼け石に水、ていうか確かに少し凝ったことをやろうとすると面倒くさいのは否めませんね……。
というわけかで、偉大なるWebClientを見習って、SqlConnectionの一連の流れを抹殺するラッパーを作ってみました。プリミティブなAPIなんて触りたくないっす。プリミティブなものは魅力どころか穢れたものに見えてしまうので、可能な限り隠蔽してやりたいのです。本当は生のSQLだって触りたくないんですけどね……。さて、目標は、簡単に使えることと、usingが極力表に出ないようにすること。とりあえず利用例から。PersonTableというAgeとFirstNameとLastNameが格納されてるテーブルからデータを引っ張ってきます。
// ただの入れ物 public class Person { public string Name { get; set; } public int Age { get; set; } } static class Program { static void Main(string[] args) { // 実行→即Closeの場合静的メソッドを用いる(Regexと同じ感覚で) var maxAge = DbExecutor.ExecuteScalar<int>(new SqlConnection("ConnectionString"), @"select max(Age) from PersonTable"); // 接続を維持して複数回実行する場合はインスタンス生成で using (var executor = new DbExecutor(new SqlConnection("ConnectionString"))) { // 列名とプロパティ名を対比させてマッピング // パラメータはstring.Format的に@p0, @p1などに対して適用される var persons = executor.ExecuteQuery<Person>(@" select FirstName + ' ' + LastName as Name, Age from PersonTable where Age < @p0 and FirstName = @p1" , 20, "Osakana").ToList(); // 遅延評価なのでusingを抜ける前にリスト化などどうぞ // ExecuteReadはIEnumerable<IDataRecord>で一行ずつ取得できる var persons2 = executor.ExecuteRead(@"select * from PersonTable") .Select(dr => new { Age = dr.GetInt32(0), FirstName = dr.GetString(1), LastName = dr.GetString(2) }); } } }
といった感じに、処理用のラッパーにDB接続を渡して実行します。メソッドはExecuteScalar, ExecuteNonQuery, ExecuteRead, ExecuteQueryの4つ。ScalarとNonQueryは普通のと同じ、Readは一行毎に列挙、Queryはオブジェクトへのマッピングが出来ます。4つのメソッドの引数は全て同じで、string query, params object[] parametersになります。パラメータは@p0, @p1, といったように「@p順番」に対して適用されます。
実行して即座にコネクションを閉じたい場合は静的メソッドを、接続を維持したまま複数実行した場合はインスタンスを生成してください。DbExecutor自体がIDisposableで、Dispose時に中のコネクションに対しDisposeを呼びます。なお、トランザクション関連のメソッドはありませんが、それはTransactionScopeを使ってくださいな。
スコープと遅延評価
同じ処理はメソッドに括り出す!Don’t Repeat Yourself!コピペ禁止!ではあるものの、スコープが絡むと結構難しい。usingは難敵です。以下、実装の一部。
// 実装(一部抜粋) public class DbExecutor : IDisposable { // usingを共通化させたいんだけど、普通に値返すとスコープ抜けちゃう // →IEnumerableで包んでしまえばいいぢゃない! private IEnumerable<DbCommand> UsingCommand(string query, object[] parameters) { using (var cmd = dbConnection.CreateCommand()) { if (dbConnection.State != ConnectionState.Open) dbConnection.Open(); cmd.CommandText = query; foreach (var p in parameters.Select((v, i) => CreateParameter(cmd, "@p" + i, v))) { cmd.Parameters.Add(p); } yield return cmd; } } // UsingCommand().First().ExecuteScalar()だとusingを抜けてから実行になるのでダメなのですよー public T ExecuteScalar<T>(string query, params object[] parameters) { return UsingCommand(query, parameters).Select(c => (T)c.ExecuteScalar()).First(); } public IEnumerable<IDataRecord> ExecuteRead(string query, params object[] parameters) { return UsingCommand(query, parameters).SelectMany(c => c.EnumerateAll()); } } public static class IDbCommandExtensions { public static IEnumerable<IDataRecord> EnumerateAll(this IDbCommand command) { using (var reader = command.ExecuteReader()) { while (reader.Read()) yield return reader; } } }
UsingCommandメソッドが苦心の跡です。ExecuteScalarとExecuteReadの処理(DbCommand取ってパラメータ足す)を共通化したかったのですが、usingが難敵で。ExecuteScalarはTを返すから即時実行、これをusingで一部括り出すのは簡単なのですが、問題はExecuteRead。IEnumerableを返すから遅延実行で、これに対してusing(cmd){return cmd}なんて関数を使ってしまうと、実行時に即座にusingのスコープを抜けてしまってusingが無効になってしまう(どころかDispose済みになってしまうので実効時エラー)。
じゃあどうすればいいか、というと、usingに括り出した部分も遅延評価してしまえばいい。そこで要素一つのみでyield returnする。そして、即時評価のものはSelect->First、遅延評価のものはSelectManyを使うことで、無事Usingを共通化出来ました。おお、Linqは何と素晴らしいのでしょうか!何かと言うと、つまりは、Linq to Objectsの用途はリスト処理だけじゃないんですね。IEnumerableはインフラ。そして、Reactive Extensionsに入っているEnumerableEx.Return(これはEnumerable.Repeat(elem,1)に等しい)の意味合いがジワジワくる。Returnは明らかにHaskell由来の命名で、モナドが(以下略、もしくはナンダッテー)
で、まあ、利用時は結局データベースとの接続やトランザクションとの兼ね合いもあるので、接続の状態自体は割と意識してないとダメですね。適当なところでToListとでもしておいてください。この辺も含有した上での解決策は課題ですね、今はうまいやり方が全然思いつかない。
SQLiteで使う
勿論、SqlServer以外でも使えます。System.Data.SQLiteで試しましたが、問題なく動きました。というわけで、Hello, SQLite。SQLiteをインストールしたら参照設定にSystem.Data.SQLiteを加えて
var builder = new SQLiteConnectionStringBuilder { DataSource = "test.db" }; using (var executor = new DbExecutor(new SQLiteConnection(builder.ConnectionString))) { var existsTable = executor.ExecuteRead(@"select * from sqlite_master where type='table' and name = @p0", "test") .Any(); if (!existsTable) { executor.ExecuteNonQuery(@"create table test (Age, Name)"); executor.ExecuteNonQuery(@"insert into test values(10,'hoge')"); executor.ExecuteNonQuery(@"insert into test values(20,'tako')"); executor.ExecuteNonQuery(@"insert into test values(30,'ika')"); } executor.ExecuteRead(@"select * from test where Age >= @p0", 20) .Select(dr => new { Age = dr.GetInt32(0), Name = dr.GetString(1) }) .ToList() .ForEach(a => Console.WriteLine(a.Name + ":" + a.Age)); }
existsTableのクエリはテーブルがあるかないかを調べるもので、一行帰ってくるならテーブルが存在する、何も帰ってこない時はテーブルが存在しない。というわけで、それAny()で、ですね。IEnumerableベースで扱えると、こういうことが非常に楽です。
それにしてもSystem.Data.SQLiteいいですね、初めて使ったんですが拍子抜けするぐらいに簡単に試せました。インストールしたら参照設定に加えるだけ、DataSourceに直にファイル名指定すれば、あれば読み込み、なければ生成してくれる。データベースは設定が面倒っちいですからねー、こう簡単に出来るのは嬉しいです。
おまけ
で、まあ、VS2008ならLinq to SQL/Entities使わないのー?って話であり、まあ、ねえ、確かに、ねえ。なので、ひっそりとVS2005バージョンも作ってみました(zipに同梱してあります)。内部的にも(当然)Linq未使用なので 、VS2008で対象フレームワークが.NET 2.0の場合でも、こちらなら使えます。内部でSelectとかSelectManyを再定義して、拡張メソッドの呼出じゃなくて普通の呼び出しに書き換えただけです。あまりの型推論の効かなさにイライラしました。もうC#2.0に戻るとか無理すぎるだろ常識的に考えて。
List<Person> persons = executor.ExecuteRead<Person>(@"select * from PersonTable", null, delegate(IDataRecord dr) { Person p = new Person(); p.Age = dr.GetInt32(0); p.Name = dr.GetString(1) + " " + dr.GetString(2); return p; });
ExecuteReadとExecuteQueryは、IEnumerableを返されても困ると思うので、Listを返すようにしています。ExecuteReadはSelectがないので、第三引数でConverterデリゲートを受けるようにして、Selectの代わりにしました。 使う分には、VS2008のものとさして変わらないと思います。
追記
初回リリース時の名前はDbExecuterだったんですが、DbExecutorに変更しました。stableとか言っておきながら4時間で撤回とか、殺されていいですね、ほんとすみませんすみません。あまりの恥ずかしさに穴掘って埋まりたいです……。まあ、executerでもよくね?と思わなくもなくもないのですが、実際問題ぐぐる先生の検索結果で大きな差があるので、むしろ変えるなら、たった4時間の今のうちしかない、と思ったので変更しちゃいました。しかし、ああ……。スペルは結構気をつけてるほうだと思ったんだけどなあー。
C#で原始的にSQLを扱うお話
- C# - 10.04/05
世の中はLinq to SQLだのLinq to Entitiesだのを羨ましいなあ、と指を加えて眺めている昨今ですがこんばんわ。Linq好きーな私ですがこのサイトではObjectsとXmlしか扱っていないのは、単純に私が触った事ないから、です。あうあう。そんな私ですが、SQLを(大変嫌々ながら)触らなければならなかったりする場合もないわけじゃないのですが、アレですね、思うのはパラメータ。あれにadd。add。するのが大変美しくない。もっと格好良く、一発で決めようぜ。と思って色々悩んだんですが、どうにも上手く行きそうにない。やけくそになってビルダー+コレクション初期化子を考えてみました。
var cmd = new SqlCommand(); cmd.CommandText = @"select * from Foo where Bar > @Hoge and Tako = @Ika"; cmd.Parameters.AddRange(new SqlParameterBuilder { {"@Hoge", "2"} {"@Ika", "たこやき"} }.ToArray());
分かりづらくなってるだけで、普通にadd, addでいいですね。ダメだこりゃ。ボツ。ちなみに実装は超単純。
public class SqlParameterBuilder : IEnumerable<SqlParameter> { List<SqlParameter> parameters = new List<SqlParameter>(); public SqlParameterBuilder Add(string parameterName, object value) { parameters.Add(new SqlParameter(parameterName, value)); return this; } public IEnumerator<SqlParameter> GetEnumerator() { return parameters.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } }
コレクション初期化子の復習をしますと、IEnumerableかつAddメソッドが実装されているクラスに対してコレクション初期化子が使えます。Addは名前で決め打ちされています。メソッド名はAddじゃなくてAppendがいいなー、とか思ってもダメです。コレクション初期化子を使いたい場合はAddです。何でこんなヘンテコなことになってるのか、の理由は2008-02-08 - 当面C#と.NETな記録の記事を参照に。Addが複数引数を取る場合は{{},{}}って書けますが、IntelliSenseの補助がないので割と不便だったりしますねえ。
関数渡し
whileでEndまで読んで、ってのは嫌いです。Stream系のは全部yield returnでライン毎に返す拡張メソッドを定義しますね、私は。というわけで、SQLのDataReaderもStreamと同じ図式なので、同じ感じにしましょー。
var command = new SqlCommand(); command.CommandText = @"select hogehogehoge"; var result = command.EnumerateAll(dr => new { AA = dr.GetString(0), BB = dr.GetInt32(1) });
いい感じに見えないでしょうかどうでしょうか?といっても、これは以前書いたものの使い回しだったりしますが、その以前書いたコードとやらは若干反省してます。以下、書き直したコード。
public static IEnumerable<T> EnumerateAll<T>(this IDbCommand command, Func<IDataReader, T> selector) { using (var reader = command.ExecuteReader()) { while (reader.Read()) { yield return selector(reader); } } } public static T[] ReadAll<T>(this IDbCommand command, Func<IDataReader, T> selector) { return command.EnumerateAll(selector).ToArray(); }
そう、以前は無理やりEnumerable.Repeatで無限リピートさせておりましたが、あのテクニックはyield returnが使えない(別関数に分けない場合)時のためのテクニックであって、yield returnが使えるなら素直にwhileループを回して書いた方がマシなのです。どうにも、まずLinqで書いたらどうなるのか、というのが頭に最初に浮かんでしまってフツーの書き方を忘れてしまいがちなのですが、大事なのはシンプルに表現すること、です。何故Linqを使うのか、そのほうがシンプルに書けるから。whileのほうがシンプルになるなら、そちらを選ぼう。弁解のために言っておくと、あれはreturn IEnumerableとyield returnの挙動の違いの例のために書いたわけですががが。あと、yieldが言語によってサポートされてるからってのもありますね。C#1.0のように自前でEnumerator用意して書かなきゃならないのならば、Linqで生成したほうが良いわけで。
マッピング
GetInt32(1)とか列を意識してオブジェクトに詰めるのがダルい。定形作業だし列がズれたら修正面倒だし殺したい。こんなの人間のやる作業じゃない。つーわけで、入れ物に詰めるのぐらいは自動化しよう。ああ、Linq to Sql使いたいなあ。
// このクラスにデータベースから値を入れるとして class MyClass { public int IntProp { get; set; } public string StrProp { get; set; } } static void Main(string[] args) { // プロパティ名と対比させて自動詰め込み var command = new SqlCommand(); command.CommandText = @" select hoge as IntProp, huga as StrProp from NantokaTable"; var result = command.Map<MyClass>(); } public static T[] Map<T>(this IDbCommand command) where T : new() { Dictionary<string, PropertyInfo> properties = typeof(T) .GetProperties(BindingFlags.Public | BindingFlags.Instance) .ToDictionary(pi => pi.Name); return command.ReadAll(dr => { var result = new T(); for (int i = 0; i < dr.FieldCount; i++) { properties[dr.GetName(i)].SetValue(result, dr[i], null); } return result; }); }
列名とプロパティ名を摺り合わせているだけで、非常に単純なものです。気になるreaderのFieldCountとかGetNameの処理効率ですが、実行した時点で一行のキャッシュが生成されて、そこから取ってくる感じになるので重たくはないっぽいです。たぶん。ちゃんと追っかけたわけじゃないので全然断言は出来ませんが。あとは辞書生成のコストですかね。Type毎で固定なら毎回辞書作らなくてもキャッシュ出来るじゃん、という。静的コンストラクタを使えば、実現できます。
public static class Extensions { public static T[] Map<T>(this IDbCommand command) where T : new() { return SqlMapper<T>.Map(command); } private static class SqlMapper<T> where T : new() { static readonly Dictionary<string, PropertyInfo> properties; static SqlMapper() { properties = typeof(T) .GetProperties(BindingFlags.Public | BindingFlags.Instance) .ToDictionary(pi => pi.Name); } public static T[] Map(IDbCommand command) { return command.ReadAll(dr => { var result = new T(); for (int i = 0; i < dr.FieldCount; i++) { if (dr.IsDBNull(i)) continue; properties[dr.GetName(i)].SetValue(result, dr[i], null); } return result; }); } } }
割とスマート。ジェネリック静的クラスには直接拡張メソッドは定義出来ないので、入れ子のprivateな静的クラスを挟んでいます。 と、こんな感じにSQLを触っていると非常に原始人っぽい。先日のテンプレート置換と同じく、コピペに優しい小粒でピリッと役立ち、なメソッドの作成を志してる感じです。まあ実際は自分だけが使うUtilなんて許されるわけもなく普通に地味にドロドロと(ああ、胃が……)。
そういえばであまり関係ないのですが、SQL文ってどこに置くべきなんでしょうかね。外部ファイルにしておいて読み込み、などというのは個人的にはどうかなー、と思っていて。どうせ呼び出し部分と1:1になるなら、↑のようにハードコードでも別によくないかしらん、そのほうがソースコード上の距離が近いこともあって分かりやすくなる。とか、どうなんでしょうかねえ。再コンパイルが不要になるといって、どうせSQLに変更入ったらコードのほうも変更入れないとマズい可能性が高そう、とか。
追記
マッパーはSystem.Data.LinqにあるDataContext.ExecuteQuery(TResult) メソッド そのものですね、たはは、シラナカッタヨ。↑のほうが超単純実装+キャッシュなので速いとは思いますが、どうでもいい差ですな。全く同じものだと悔しいので、outer joinとかでNullが混じる場合もすんなり使えるように、IsDBNullを足してスキップするようにしました。まあSQLでcoalesceで明示的にやったほうが良いとは思いますが。EnumerateAllのほうは普通に使えると思うのでぜひぜひ。
RxJS用IntelliSense生成プログラム(と、VisualStudioのJavaScript用vsdocの書き方)
- C# JavaScript Rx - 10.03/26
先日、Reactive Extensions for JavaScript(RxJS)の記事を書いたわけですが、触っていて困るのは、どのメソッドが使えるの?ということ。リファレンスもない中で、C#版の記憶を頼りに手打ちでメソッド名を探るなんて、無理。ましてやそんな状況じゃあ人に薦められないよ!というわけで、必要なのはIntelliSense(入力補完)です。rx.jsはある。rx-vsdoc.jsはない。ないものは、作ればいいぢゃない。そこで諦めてメソッド全部暗記してやるぜ、とか思うのはどうかしてる。諦めたら試合終了ですよ。楽するために手間を掛けるのです<プログラマの三大美徳。というわけで、作りました。
- RxVSDocGenerator.zip (source and binary)
vsdocファイルをそのまま配布するのはライセンスの問題が出そうなので、生成プログラムを配布します。手作業じゃなく自動生成で作ったので(面倒くさくて手作業なんてやってられるか!)。Rxをインストールしたフォルダ(デフォルトだとProgramFiles\Microsoft Reactive Extensions)のScriptSharpフォルダの下のRxJS.dllとRxJS.xmlを、生成プログラムと同じ階層に置いて実行すると、rx-vsdoc.jsが生成されます。
利用するにはvsdoc対応パッチをあてたVisualStudio 2008 SP1(VS2010はパッチをあてなくても対応しています)を用意して、rx.jsと同じ階層に置くだけです。HTMLで使う場合はscript src=”rx.js”で読み込むだけ、独立したjsファイルで補完を使う場合は、行頭に/// <reference path=”rx.js” />と記述すれば補完が読み込まれます。この辺は、以前に最もタメになる「初心者用言語」はVisualStudio(言語?)という記事を書いたときに補完愛してる愛してる愛してると連呼しながら解説してました。jQueryのドットで補完効かせながらのメソッドチェーンは気持ちイイんだって!
折角作ったので海外の人にも利用してもらおうと、また、標準でvsdocも同梱して欲しいと訴えるためにもとRxの公式フォーラムでスレ立てたけど、奇怪英語(機械翻訳英語)が恥ずかしいです……。ニュアンスをミジンコほどにも伝えられた気がしません。英語読めない書けないプログラマなんて小学生までだよねー、とかいう自己啓発系ブログ記事は山のようにあるわけですが、ふん、どうせ英語読めませんよ書けませんよ、ぐぐる先生による機械翻訳さえ超進化してくれれば小学生でも生きていけるもん!(ちなみに私はヤフー翻訳派です)
MIX10の発表によるとMicrosoftはAjax関連はjQueryに一本化する、ということで、C#erもますますJavaScriptを書かなければならないシーンは増えていきそうなので、せっかくなのでJavaScript用のvsdocの書き方を解説します。ついでに、LinqまみれなRxVSDocGeneratorのコードの解説も若干します。Mono.Cecil.dll使ってたりするんですよー(モジュールの参照用にしか使っていないので些かオーバースペック)。
C#と比較するJavaScriptの構造
JavaScriptは割とヒネクレた書き方が幾らでも出来るわけですが、VisualStudioの入力補完は、素の状態だと素直に書かないとついてきてくれません。というわけで素直に書きましょう。素直に書けば素直なIntelliSenseが手に入ります。以下、10秒でわかるC#とJavaScriptとの構造比較。
// 名前空間、もしくは静的クラス Rx = {} Rx.Disposable = {} // クラス(コンストラクタ) Rx.Observable = function(){ } // 継承 Rx.AsyncSubject.prototype = new Rx.Observable; // 静的フィールド Rx.Disposable.Empty = null; // インスタンスフィールド Rx.GroupedObservable.prototype.Key = null; // 静的メソッド Rx.Observable.Range = function(start, count, scheduler){ } // インスタンスメソッド Rx.Observable.prototype.Select = function(selector){ }
ヒネクレたことさえしなければ、JavaScriptはシンプルです。オブジェクトとファンクションしか存在しない。シンプルさ故の制限を回避するために、また、幾らでも回避可能なためバッドノウハウのようなヒネクレた手段が大量に溢れていて、シンプルさとは無縁の奇怪な代物と成り果てていますが(JSはシンプルだよ、初心者にお薦め!というそばからクロージャがどうのapplyがどうのと言うのはどうなのよ、勿論、その柔軟さもまたJSの魅力の一つだとは思いますが、それをシンプルとは言わない)、素直に見れば、シンプルです。
そしてまあ、C#と割と似てます。構文似てるし。単一継承だし。prototypeに後からメソッドを足せるのは拡張メソッドのよう。違いは、privateはないしプロパティはないしインターフェイスはないしオーバーロードもない(但し引数は省略可能)、いつでも簡単に全てが変更可能(不注意に扱えばすぐ構造をぶっ壊せる←だからライブラリの衝突の問題がある)。といった問題は、若干ヒネクレればある程度は回避可能です、privateとか。でも、素直に書いた方が良いと思います。JavaScriptにprivateはない。と、割り切ってしまうと非常に楽になれます。良いか悪いかはともかく。
ただ、素直に書こうと、素のJavaScriptでは、補完は簡単に限界がきます。例えば以下のコード。
var func = function(bool) { return (bool) ? "string" : [4, 5, 2, 3, 1]; } var b = Math.random() < 0.5; // true or false func(b).toUpperCase(); func(b).sort(); // どちらかで必ずエラー
引数の型が自由なら、戻り値の型もまた自由。じゃあどうするの?というと、どうにもなりません。戻り値の型はなるべく統一しましょう、IntelliSenseに優しくするために。これもまた素直の一つでしょうか、さてはて。
vsdoc.js入門
素直に素直に、と言ったところで何処かで破綻する。だいたい、JavaScriptの言語としての柔軟さを生かさないでどうする!という話は尤もなこと。そこで、VisualStudioはJavaScriptの入力補完に気の利いた仕組みを用意しています。ファイル名-vsdoc.jsが同階層にある場合、vsdoc.jsの構造を利用して入力補完を行います。なので、オリジナルに手を加える事なく補完を利用することが出来ますし、また、オリジナルが補完生成し辛い構造をしていても問題はありません。最終的にユーザーが利用するPublicの構造というのは、上で書いた素直なJavaScriptで再現出来るわけなので、それで構築すればいいだけです。勿論、別箇に構造を作成するというのは手間が増えるので、可能な限りは素直な構造にしておいたほうが無難です。
「IntelliSenseに候補が出ないものは存在しないに等しい」。これは.NETのクラスライブラリ設計という本に書かれている言葉なのですが(神本なので未読の人は絶対購入しましょう)、候補を出しさえしなければ、利用者にとって存在しないようなものに見えます。実際はpublicであっても、補完候補から削ってしまえばprivateに見える。擬似的なprivateの表現としては、中々スマートではないですか?
そんなvsdocですが、ちゃんとしたドキュメントが今ひとつ見あたらないので、jQuery用のvsdocを参考にすると良いでしょう。色々な属性が用意されているようですが、実際の入力補完に利用されるものは少ししかありません。optional属性なんて、オーバーロード的なものの表現に使えるのでは?と期待をかけたのですがそんなことはなくて、IntelliSense用には動作しませんでした。よって、summary, param, returnsだけ抑えておけば良いです。
var sum = function(x, y) { /// <summary>足し算</summary> /// <param type='Number' name='x'>引数1</param> /// <param type='Number' name='y'>引数2</param> /// <returns type='Number'></returns> } Rx.Disposable.Empty = new IDisposable;
C#と違ってfunctionの「下」にドキュメントコメントを書きます。また、ドキュメントコメントを使う場合は、関数本体はあってもなくても無視されるので、不要です。なお、ドキュメントコメントは-vsdoc.jsだけで有効なわけではなく、普通のjsファイルでも有効です。summary, paramは面倒くさかったら書かなくてもそんなに害はなさそうですが(但し引数違いのオーバーロードがある場合はsummaryで伝えてあげると使う人に優しい)、returns typeだけは欠かさず書いておきたい。これを書いておくと戻り値の型がVisualStudioに認識されるので、IntelliSenseを途絶さず利用できます。
制限事項としては関数のみにドキュメントコメントを埋め込むことが出来ます。vsdocを作る際にフィールドの型も認識させたい場合は、ダミーの変数を与えてあげればOK。
ジェネレータの解説
と、いった基本を抑えておけば、どんなライブラリに対してもvsdocを作れるね!じゃあ、rx-vsdoc.jsも手作業で作ろうか。と、思った時もありました。構造自体はjs自体をダンプでなんとかなる(と、いいなあ)だろうし、summaryやparamは諦めるとしてreturns typeだけを手作業で書くなら、どうせほとんどRx.Observableなので手間もそんなでもない。けど、rx.jsは難読化されていて引数の名前がイミフ、例えばRx.Observable.Range(k0, l0, m0)というんじゃ苦しい……。やっぱsummaryもparamも必要。でもどうすれば……?
そこで、インストールディレクトリを見てみるとScriptSharpなんてフォルダがあるんですよ。そう、RxJSはScript#でC#コードから生成されたJavaScriptライブラリだったのだよ、ナンダッテー!そして、ScriptSharp用のRxJS.dllには当然、完全なクラス構造と、引数の名前と型が保存されているし、更にはsummary用のxmlも用意されていた。つまり、ここからrx-vsdoc.jsを生成すればいいわけです。
というわけでリフレクション。型情報を取るため、早速Assembly.LoadFrom(”RxJS.dll”).GetTypes()とすると、落ちる。はあ、ScriptSharpのdllに依存してるのでそっちもないとダメなのね。というわけでScriptSharpのdllを幾つか参照に加えると、なんかうまく動かせない。ScriptSharpのdllはmscorlibの代替となってる(JSに変換可能なもののみに制限を加えてる?)から、一緒には動かせないとかそんな感じなのかなー、よくわからないけどとにかく動かせない、諦める。南無。無念。
そもそもLoadするからダメなわけで、Loadしなくていいよ、型情報だけ取れればそれでいいんだって。でも標準ライブラリには、それを可能にするのはないっぽい。けど、Monoにはあった。Cecil - Mono。参照も書き換えも出来るようですが、今回は参照のみで。色々出来そうなので、いつかもう少し触ってみたいですね。私は今回はじめてMono.Cecil.dllを使ったのですが、リファレンスの類も見てない(あるのか知らない)し、チュートリアルの類も見てない(ていうか日本語の情報がない)。でも、IntelliSenseでドット打ってれば何とかなりました。しっかりした構造とちゃんとしたメソッド名とIntelliSenseがあれば、リファレンスがなくても問題なく使えるわけです。すばらしきこのせかい!
Mono.Cecil
型情報を取ってくるだけなら簡単で、というかSystem.Reflectionと大して変わりません。
var rxjsTypes = AssemblyFactory.GetAssembly("RxJS.dll") .MainModule.Types.Cast<TypeDefinition>()
TypeDefinition, MethodDefinition, ParameterDefinitionといったのが個の要素。そして、対応するコレクションHogeCollectionが用意されています。HogeCollectionは残念ながらジェネリックではないため、Linqに流すためにはCastが必要になります。今回はParameterDefinitionCollectionのSelectを多用することが多かったので、Cast無しで使えるよう拡張メソッドを定義しちゃいました。
static IEnumerable<T> Select<T>(this ParameterDefinitionCollection source, Func<ParameterDefinition, T> selector) { return source.Cast<ParameterDefinition>().Select(selector); }
この手のレガシーなコレクションに対するアドホックな対応は、例えば正規表現のMatchCollectionなんかにも使えそうです(と、いった発想の元ネタはAchiralから)
テンプレート置換
必要なJSの構造は上のほうで書いた通り決まったパターンがあるので、雛形を元に置換するのが楽。テンプレートエンジン、なんていう大仰なものは必要ないけれど、string.Formatでも{5}とか出てくると引数の管理が面倒だし、順番の変更にも弱い。なので簡易置換用の拡張メソッドを用意してみました。
static string TemplateReplace(this string template, object replacement) { var dict = replacement.GetType().GetProperties() .ToDictionary(pi => pi.Name, pi => pi.GetValue(replacement, null).ToString()); return Regex.Replace(template, "{(" + string.Join("|", dict.Select(kvp => Regex.Escape(kvp.Key)).ToArray()) + ")}", m => dict[m.Groups[1].Value]); }
オブジェクトを渡すと、{プロパティ名}の部分をプロパティの値に置換します。オブジェクトなのでクラスインスタンスでもいいのですが、匿名型も使えます。例えば
const string classTemplate = @" {FullName} = function({Parameters}) { /// <summary>{Summary}</summary> {Param} }"; var r = classTemplate.TemplateReplace(new { FullName = "Rx.Notification", Parameters = "kind", Summary = "Represents a notification to an observer.", Param = " /// <param type='String' name='kind'></param>" }); Console.WriteLine(r);
割と便利。たった9行なので、ちょっと気の利いた置換が欲しいなあ、って時にササッとコピペして取り出せるのが魅力です。最近、コピペに優しいプログラミングをよく考えてる。というのはともかくとして、実際どんな風に使っているかというと、
var classes = rxjsTypes .Where(t => t.Constructors.Count > 0) .Select(t => new { t.FullName, Parameters = t.Constructors.Cast<MethodDefinition>() .Select(m => m.Parameters) .MaxBy(p => p.Count) .Select(p => p.Name) .ToJoinedString(", "), Summary = summaries[t.FullName] + " " + t.Constructors.Cast<MethodDefinition>() .OrderBy(m => m.Parameters.Count) .Select(m => m.Parameters.Select(p => p.Name).ToJoinedString(", ")) .Select((s, i) => string.Format("{0}:({1})", i + 1, s)) .ToJoinedString(", "), Param = t.Constructors.Cast<MethodDefinition>() .MaxBy(m => m.Parameters.Count) .Parameters .Select(p => string.Format(Template.Param, p.ParameterType.ToJSName(), p.Name)) .ToJoinedString(Environment.NewLine), }) .Select(a => Template.Class.TemplateReplace(a));
前段階で匿名型を生成して、最後のSelectで置換をかけてます。Select二段にしないでもいんじゃね?というとYESですが、このほうが見やすいと思うので。それにしてもリフレクションなわけで、Linqと非常に相性が良い。というか、Linqなしだと大量のforループとifで涙を流すことになりそう。なので、昔はリフレクションって結構敷居が高かったのですが、今はもうLinqでサクサクとWhereで切って捨ててSelectで繋げて繋げて、って出来るので書く分には楽チンです。これがLinq以前のC#2.0だったら、考えたくないなあ。
出力
クラス、継承、オブジェクト、メソッド、プロパティは全部バラバラに抽出しています。そして、全部IEnumerable<string>で止めています。最後にそれらをまとめて、テキストとして出力。
var vsdoc = Enumerable.Repeat(string.Format(Template.Object, RootNamespace), 1) .Concat(classes) .Concat(inheritance) .Concat(objects) .Concat(methods) .Concat(properties) .ToJoinedString(Environment.NewLine); File.WriteAllText("rx-vsdoc.js", vsdoc, Encoding.UTF8);
せっかくクエリ遅延評価にさせているので、書き出しも一度stringに貯めないでストリームで書きだせば高効率ですねー。でも、一度文字列に出した方が書くの楽なので。せいぜい1000行程度なので、ケチッても意味ないですな。
全部rxjsTypesをルートにして生成しているので、クエリ構文を使って巨大な一塊にしてみたら面白かったかな、なんて思いますが若干悪趣味な気もするのでやめておきます。そもそも、このドットだらけ、Selectだらけの時点で若干どうよ、といった趣が漂っているのは間違いない。いやいや、ドット素敵です。Linq素敵なんだって、本当に。こういうの書いてるとC#2.0と3.0は別物だろ常識的に考えて、と思わなくもない。セミコロン率は物凄く低くなりましたね……。あと、LinqとSQLを関連付けるのはそろそろやめようぜー、的な思いがふと過ぎったり。Twitterのpublic検索でlinqをキーワードに毎日眺めてるんですが、今でも割とそういう印象持ってる人多いんだなー、と。だからどうしたとかどうなるってこともないですが。
まとめ
IntelliSenseでLinqはより楽しくなる。メソッドチェインはIntelliSenseでより楽しくなる。そして、VisualStudioはJavaScriptエディタとしても優秀なので皆VisualStudio使おう!インストールが面倒?Microsoft Visual Studio 2008 Express EditionからWeb インストールをクリックするだけでオールインワンでダウンロード含めて10分ぐらいで全部やってくれる。時間がかかるというのは正しいですが、意外と面倒くさくはないんです。それと、このExpress Editionは無料です。
入力補完だけじゃなく、コード整形やデバッガ(開発環境と完全統合されているためFirebugよりもずっと使いやすい)などもあるし、ある程度は裏でインタプリタをぶん回して変数名間違いなどのエラーを補足してくれるので、IDE無しでJavaScript書くなんて、そんな苦労、しなくてもいいんだよ……。
はてなダイアリー to HTML
- C# - 10.03/09
はてなダイアリーの記事を根こそぎ取得してローカルHTMLに保存するアプリケーションです。過去ログを全部取得して昇順に並び替えます。カテゴリ指定も可。 本文抽出アルゴリズムだなんて高尚なことはせず、HTMLをそのまま切り出しているだけのはてなダイアリー完全特化なぶんだけ、デザインやsyntax-highlightなどもそのままで見ることができます。上の画像はNyaRuRuの日記(勝手に貼ってすみません)の.NETカテゴリーを抽出しているところ。私がC#やLinqを覚えられたのはNyaRuRuさんの日記のお陰といっても過言ではなく、しかも読み返す度に新しい発見があって本当に素晴らしい。ので、度々読み返しているのですが、はてな重い。重い。なら全部ぶっこぬけばいいぢゃない。というのが作った理由でして……。
あと、最近こそこそごそごそとC++も勉強中なので、 [C++] - Cry’s Diaryや [C++] - Faith and Brave - C++で遊ぼうを読むと、(大体は全く分からないのですが)勉強になります。なお、Permalinkは相対パスになってしまい使えないのですが、日付の部分は絶対パスなので、コメント見たくなったりPermalinkを取りたくなったら日付から辿れます。
こうしてHTMLを自炊(?)すると、電子ブックリーダー欲しくなりますね。それと、リーダーはやっぱブラウザが載ってないとダメよねー。PDF(と独自形式?)だけ見れても嬉しくぁない。そんなに本には興味ない。HTMLが見たいのです。Twitterのログが見たいのです。2chまとめサイトが見たいのです。海外の技術書は結構PDFで買える感じなのでそれはそれで気になるところですが――。
以下ソースコード。↑のzipにも同梱してありますが。コンパイルにはSGMLReaderが必要です。
static class Program { static IEnumerable<T> Unfold<T>(T seed, Func<T, T> func) { for (var value = seed; ; value = func(value)) { yield return value; } } const string HatenaUrl = "http://d.hatena.ne.jp"; static void Main() { Thread.GetDomain().UnhandledException += (sender, e) => { Console.WriteLine(e.ExceptionObject); Console.ReadLine(); }; Console.WriteLine("抽出対象のはてなIDを入力してください"); var id = Console.ReadLine(); Console.WriteLine("カテゴリを入力してください(全ての場合は空白)"); var word = Console.ReadLine(); Console.WriteLine("出力ファイル名を入力してください"); var fileName = Console.ReadLine(); // 抽出クエリ! var root = XElement.Load(new SgmlReader { Href = HatenaUrl + "/" + id + ((word == "") ? "" : "/searchdiary?word=*[" + Uri.EscapeDataString(word) + "]") }); var contents = Unfold(root, x => { var prev = x.Element("head").Elements("link") .FirstOrDefault(e => e.Attribute("rel") != null && e.Attribute("rel").Value == "prev"); if (prev == null) return null; retry: try { var url = HatenaUrl + prev.Attribute("href").Value; Console.WriteLine(url); // こういうの挟むのビミョーではある return XElement.Load(new SgmlReader { Href = url }); } catch (WebException) // タイムアウトするので { Console.WriteLine("Timeout at " + DateTime.Now.ToString() + " wait 15 seconds..."); Thread.Sleep(TimeSpan.FromSeconds(15)); // とりあえず15秒待つ goto retry; // 何となくGOTO使いたい人 } }) .TakeWhile(x => x != null) .SelectMany(x => x .Descendants("div") .Where(e => e.Attribute("class") != null && e.Attribute("class").Value == "day")) .TakeWhile(e => !Regex.IsMatch(e.Value, @"^「\*\[.+\]」に一致する記事はありませんでした。検索語を変えて再度検索してみてください。$")) // 間違ったカテゴリ入力した時対策 .Reverse(); // 古いのから順に見たいので // style抽出 var styles = root.Element("head").Elements("link") .Where(e => e.Attribute("rel").Value == "stylesheet") .Select(e => { e.SetAttributeValue("href", HatenaUrl + e.Attribute("href").Value); return e; }) // 副作用ダサい .Concat(root.Element("head").Elements("style")); // HTML組み立て! var html = new XStreamingElement("html", // まあ、Reverseでバッファに貯めるので焼け石に水ですけどね、XStreamingElement new XStreamingElement("head", styles), new XStreamingElement("body", // new XElement("div", new XAttribute("class", "hatena-body"), サイドバーとか邪魔なので無視 // new XElement("div", new XAttribute("class", "main"), new XStreamingElement("div", new XAttribute("id", "days"), contents))); // 保存 var path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullName), fileName + ".html"); var xws = new XmlWriterSettings { Indent = true, CheckCharacters = false }; // 不正な文字のあるサイトを書き出すと落ちるので防止 using (var xw = XmlWriter.Create(path, xws)) { html.Save(xw); } } }
try-catchが出るとゴチャついて嫌。なのだけど、しょうがないか。それと Unfoldはどうしたものかねえ。極力、標準演算子のみで済ませたいんですが、今回はちょっと使わざるを得なかったと思っています。次のページのURLを得るには、取得したHTMLから解析しなければならない。下流で解析し取得した次のURLは、上流に渡さなきゃいけない。のですが、通常は下から上に渡せないのがLinqなのよね。そんな場合、外部変数を介して渡すか、Unfoldか、場合によってはScanなんかを使うかになるわけで、とにかく外部変数は避けたかったのでUnfoldを使いました。
HTMLへの書き出し部分では物珍しい XStreamingElementを使ってみました。今回はただ単に書き出すだけなので、通常のXElementのようにメモリ内にツリーを保持する必要はないし、相当大きいXmlを扱うため効率も気になってくるところ。そこで遅延ストリーム書き込みを可能にするXStreamingElementの出番です。詳しくは 方法: 大きな XML ドキュメントのストリーミング変換を実行する をどうぞ。とはいっても、このプログラムでは反転させるためReverseでバッファに全て溜め込んでいるので、まあ……。XStreamingElementって言いたいだけちゃうんか、みたいな。
デザインはdiv class=hatena-bodyとdiv class=mainを抜いているので(不必要なサイドバーの描画を除去するため)、この二つに依存するCSSが書かれているサイトの場合はデザインが崩れることがあります。ちなみにneuecc clipはこの二つどころか、その他にもwrapperを置いているというデタラメなCSS構造をしているため、デザインは保存出来ません。全くもって酷い。もっとスクレイピングに優しいHTMLを書かないとダメですな。
HTMLへの書き出し部分ははまりどころでした。最初Save(fileName)で保存していたんですが、特定のサイトの特定の部分で落ちてしまって困りました。具体的には 2008-07-23 - Faith and Brave - C++で遊ぼう で(例に出してすみません)、Protocol Bufferによる出力結果がInvalidXmlCharに引っかかってアウト、のようです。回避する方法は、XmlWriterSettingsのCheckCharactersをfalseに設定したXmlWriterを生成して書き出せばOK。
C#でスクレイピング:HTMLパース(Linq to Html)のためのSGMLReader利用法
- C# - 10.03/02
Linq to XmlがあるならLinq to Htmlもあればいいのに!と思った皆様こんばんは。まあ、DOMでしょ?ツリーでしょ?XHTMLならそのままXDocument.Loadで行けるよね?XDocument.Parseで行けるよね? ええ、ええ、行けますとも。XHTMLなら、ね、ValidなXHTMLならね。世の中のXHTML詐称の99.99%がそのまま解析出来るわけがなく普通に落ちてくれるので、XDocumentにそのまま流しこむことは出来ないわけです(もちろん、うちのサイトも詐称ですよ!ていうかこのサイトのHTMLは酷すぎるのでそのうち何とかしたい……)。
そこでHtmlを整形してXmlに変換するツールの出番なわけですが、まず名前が上がるのがTidy、の.NET移植であるTidy.NETで、これは論外。とにかく面倒くさい上に、パースしきれてなくてXDocumentに流すと平然と落ちたりする。おまけにXDocumentに入れるには文字列にしてから入れる必要があって二度手間感がある、などなど全くお薦めできません。XboxInfoTwitはTidy使ってますが、後悔してますよ……。
次にHtml Agility Packで、これは中々良いです。Linq to Xml風で大変使いやすい。のですが、あくまで風味であって、なんで本物のLinq to Xmlが目の前にあるのに、それっぽく模したものを覚えなきゃいけないの?二度手間で面倒くさいよ。
そこで、第三の選択としてSGMLReaderを使うという方法を提案します。SGML Reader自体は古くからあるのですが、日本語での情報はあまりないみたいだしLinq to Xmlと組み合わせたスクレイピング用途、に至っては皆無のようなので、ここで紹介しましょう。とりあえず例を。
// たったこれだけのメソッドを用意しておけば static XDocument ParseHtml(TextReader reader) { using (var sgmlReader = new SgmlReader { DocType = "HTML", CaseFolding = CaseFolding.ToLower }) { sgmlReader.InputStream = reader; // ↑の初期化子にくっつけても構いません return XDocument.Load(sgmlReader); } } static void Main(string[] args) { using (var stream = new WebClient().OpenRead("http://www.bing.com/search?cc=jp&q=linq")) using (var sr = new StreamReader(stream, Encoding.UTF8)) { var xml = ParseHtml(sr); // これだけでHtml to Xml完了。あとはLinq to Xmlで操作。 XNamespace ns = "http://www.w3.org/1999/xhtml"; foreach (var item in xml.Descendants(ns + "h3")) { Console.WriteLine(item.Value); // bingでlinqを検索した結果のタイトルを列挙 } } }
見たとおり、信じられないほど簡単です。SgmlReaderはXmlReaderを継承しているため、XDocument.Load(xmlReader)にそのまま流し込めます。また、SgmlReader自体もDocTypeとInputStreamを設定するだけという超簡単設計になっているため、楽にHtml to Xmlが実現。HtmlはXDocumentになってしまえさえすれば、あとは慣れ親しんだLinq to Xmlの操作で抽出していけます。
例では、BingでLinqを検索した結果の検索結果見出し部分を抽出しています。見出しはh3で囲まれているので、Descendants(ns + “h3″)。以上。超簡単。C#のスクレイピングの簡単さはRubyも超えたね!
残る問題は、日本語を扱う際はエンコーディング周りの設定が面倒くさい(間違ったエンコーディングだと文字化けする)、ということなのですが、そのWebClientのエンコーディング問題は、.NET Framework 4.0から修正された System.Net.WebClient は、HTTP ヘッダーから Encoding を自動的に認識してほしい | Microsoft Connect らしいです。素晴らしい!提案して頂いたbiacさんに感謝。
追記(より簡単に)
上の記事を書いてから気づいたのですが、HrefプロパティにURLを指定するだけで、中でStream類を作って自動的にHtmlだと判別してくれるようです。更には、エンコーディングもContentTypeを見て自動調整してくれます(詳しくはSgmlParser.csのOpenメソッドを参照)。よって、もっとずっと簡単に書けます。
static void Main(string[] args) { XDocument xml; using (var sgml = new SgmlReader() { Href = "http://www.xbox.com/ja-JP/games/calendar.aspx" }) { xml = XDocument.Load(sgml); // たった3行でHtml to Xml } // Xboxの発売スケジュールからタイトルと発売日を抜き出してみる var ns = xml.Root.Name.Namespace; var query = xml.Descendants(ns + "table") .Last() .Descendants(ns + "tr") .Skip(1) // テーブル一行目は項目説明なので飛ばす .Select(e => e.Elements(ns + "td").ToList()) .Select(es => new { Title = es.First().Value, ReleaseDate = es.Last().Value }); // 書き出し foreach (var item in query) { Console.WriteLine(item.Title + " - " + item.ReleaseDate); } }
usingの辺りが若干鬱陶しいので、最初の例のようにメソッドに切り出してもいいかもしれません(CaseFolding.ToLowerも付けたいし)。Loadし終わったらストリームはもう不要です。XDocumentはメモリ内に全部構築するタイプのもので、実質XmlDocument(DOMツリー)の代替となっています。抽出時のXml名前空間ですが、サイトによってついていたりついていなかったりするので、var ns = xml.Root.Name.Namespaceとしておくと、全てのサイトに対応出来ます。
抽出のテクニック
上の例は Xbox.com | Xbox ゲームソフト 発売スケジュールからタイトルと発売日を抽出するというものです。定期的にページを監視して、更新されたらTwitterに投稿するXbox発売予定BOTとか作れますね!Xmlで取れるAPIさえあれば……なんてことはなくなりました!これからは 全てのサイトが易々とスクレイピング可能な代物として浮き上がってきますな。
ただし、元がHTMLのものはAPIとして用意されているXMLと違って、抽出に優しくない構造をしています。 Descendants一発でOk、というわけにもいかないので若干の慣れは必要かもしれません。今回の例の抽出コードが何やってるかよくわからない、という人はHTMLソースと見比べてみてください。目的のTableに辿り着くにも、いくつかの方法があります。決め打ち成分が入ってしまうのはどうにもならないのですが、何を決め打ちにするのがスッキリ書けるのか、となると色々です。今回はTableがLastである、という点を使いましたが、他の方法を考えてみると
var case2 = xml.Descendants(ns + "div") .Where(e => e.Attribute("class") != null && e.Attribute("class").Value == "XbcWpFreeForm1") .SelectMany(e => e.Descendants(ns + "tr")); var case3 = xml.Descendants(ns + "table") .First(x => x.Ancestors().Any(e => e.Attribute("class") != null && e.Attribute("class").Value == "XbcWpFreeForm1")) .Descendants(ns + "tr");
目的のTableを囲むdivのclassが XbcWpFreeForm1であり、XbcWpFreeForm1が適用されているdivは一つしかない、ということに着目するとこうなります。case2は、divを全て列挙して探し出す方法。First(predicate)ではなくWhere.SelectManyにすることで、目的のTableが複数個ある場合でも対応出来ます。case3はTableに絞った上で、そのTableの上位階層(Ancestors)にXbcWpFreeForm1が含まれるかを探し当てる方法。Last、という決め打ちが難しい(場合により変動するケース)場合には有効でしょう。この、上位階層(Ancestors)や下位階層(Descendants)の要素に特異な要素はないか(Any)、と探す手法は、ターゲット自体に特徴がなく抽出し難い場合に活用出来ます。
そもそもDescendantsなんて富豪すぎて許せん、実行効率命!という人はElement(”body”).Element(”div”)….とトップから掘っていってもいいわけですが、さすがにElementの連続はダルいのでXPathを使うのもよいでしょう。Linq to XmlでのXPathの利用法は、以前neue cc - そしてXPathに戻るに書きました。XPathは、複雑なことを書こうとすると暗号めいた感じになるから余りすきじゃないですね。私は多少冗長なぐらいでもLinqで書くのが好きだなあ。
何故C#には(Javaの)Collections.sortに相当するものがないのか
Java は Collections.sort があるのに .NET は List 自身が Sort メソッドを持っているのはなぜ?
だそうです。あまり疑問でもないです。だってIListはSortないし。比較するならIList - ListでありList
そして何故かスレッドの話は迷走していて不思議。パフォーマンス? んー……。勝手な印象論ですみませんが、1さん(名前を出すのもアレなので1さん、ということにさせてください)はJavaのCollections.sortの性能を勘違いしているんじゃないかな? 配列のコピーを問題にしてるようだけど、Javaの方式はコピー、してますよ。もっとも最初の方の発言(効率は重要じゃない)と後ろの発言(コピーが嫌)が矛盾していますが。ともかく、実際のSunの実装を見てみましょう。
public static <T extends Comparable<? super T>> void sort(List<T> list) { Object[] a = list.toArray(); Arrays.sort(a); ListIterator<T> i = list.listIterator(); for (int j=0; j<a.length; j++) { i.next(); i.set((T)a[j]); } }
toArrayしてコピー。それをsortして、forで詰め替え。C#で同じような外部Sort関数を書くのならば以下のようになります。厳密には違いますが、それは最後に述べます。
static void Sort<T>(IList<T> list) { T[] array = new T[list.Count]; list.CopyTo(array, 0); Array.Sort(array); // List<T>のSortもArray.Sortを利用しています for (int i = 0; i < array.Length; i++) { list[i] = array[i]; } }
forで詰め替えている様は、マジマジと見ると微妙。実際、このコードには問題があります。クイズだと思って、どこが問題なのか考えてみてください、答えは最後に述べます。
List<T>のSortは、内部に配列を持っているため、CopyToとforでの詰め替えが不要です。パフォーマンスで言えば理想的な形になっているわけです。 ゲッタとセッタ経由でソートの入れ替えすればいいぢゃん、というのはそうですね、違います。配列のほうが速いし!というのもそうでしょうが、それ以前にListインターフェイスの実装は自由です。例えばLinkedListのゲッタを考えてみたらどうでしょう。getの度に前(もしくは後ろ)から走査があるため、パフォーマンスが悲惨な事になるのは容易に想像出来ます。ListはArrayListだけじゃないので、ゲッタ/セッタに頼るのは無理がある。汎用的に、Listインターフェイスならば何でも受け入れるという設計にする以上、コピーを作るのは不可避です。
というわけで、パフォーマンス云々を言うならば、内部を知っているListクラスがSortを持つのはベストな選択でしょう。そして利便性を考えてもベストな選択。ならばいったい何処に不満があるのでしょうか?
Javaのほうが優れているのは、「自前でIListを実装したクラス」を破壊的ソートするのに、クラスにSortメソッドを用意したくない。といったところでしょうか。Sortの実装自体は通常はArray.Sortを呼ぶだけなので簡単なので別に手間でもないのですけどね……。というか、この手の基本アルゴリズムを自前実装したのを使うのは悪です(勉強用に、なら当然すべきで悪なのは自前実装の妄信です)。
ようするところ、Sortメソッドを自前で用意したくないけど破壊的ソートが欲しい、ということになる。ふーむ、個人的にはなくてもいいかな。非破壊的なソートがLinqのOrderByを使うことで可能なので、破壊的のほうを欲しいとはあまり思わない。
ListIterator
では本題。何でC#には破壊的ソートをしてくれる外部関数がないの?というと、インターフェイスの都合上、不可能だから。が理由だと私は考えています(そもそも必要性薄いから、が最大の理由だと思いますがそれはそれとして)。Javaのsortと私の書いたC#のSortを見比べてください。大きな違いがあります。それは、forでソートした配列をリストに詰め直している部分。
i.set((T)a[j]); // Java list[i] = array[i]; // C#
C#の場合、listがLinkedListのようなものだった場合は悲惨なことになります。それに比べて、JavaではListIteratorを用いているため、配列と同じ処理効率で値をセット出来ます。C#には、このJavaのListIteratorに相当するものがないので、全てのIListに対して問題なく性能を発揮する破壊的ソート関数を作成することは不可能です。
「LinkedListのようなもの」という歯切れの悪い言い方をしたのは、.NETのLinkedList
ただ、IListのインデクサは取得にコストがかからないことを期待、してもいいとは思います。そんなのを一々気にしてたら何も作れない。それにちゃんと、.NETのLinkedListはIListじゃないしね。ね。というわけで上のほうで出したクイズは、問題があるかないかは何とも言えない微妙ラインです。んーと、つまりはfor(int i;i < hoge.length(); i++)の問題点はどこだー!みたいな話で、基本的にはhoge.length()なんてコストがかからないのを期待して問題ないし、コストがかかるんならそのクソクラスが悪い、みたいな。
ついでに個人的な意見ですがListインターフェイスにListIteratorはそこまで必要ではない。普通のIteratorと機能がかなり被る割には、使う機会はとても少ない。おまけに、ListIteratorのsetってoptionalで、実装されていることが保証されてない。この手の、実装しなくてもいいインターフェイスって撲滅した方がいいと思うんですけどねー。私は怖くて呼べません。
とはいえ、保証されないインターフェイスを完全に撲滅など出来はしません。例えばList(Java)/IList(C#)のAddは実行出来ることが保証されていない。JavaならArrays.asList(1, 2, 3).add(4)を、C#なら(array as IList
まとまってませんが、結論としては疑問に思ったらソース読むのが手っ取り早い、とかそんなところで。Javaの良いところはC#に比べてフレームワークのコードへのアクセスが簡単なところですね。C#は部分的にはコードは公開されていて大変タメになるのですが、色々と面倒くさいし、公開されてない範囲も少なくないし……。.NET Reflectorのお世話になりまくってイリーガルな気分を味わうのはもう嫌ぽ。嘘。リフレクタ大好きですがそれはそれ。
AnonymousComparer - ver.1.2.0.0
- C# - 10.02/04
AnonymousComparerを再度バージョンアップしました。ダウンロードは上記リンク先、CodePlexからどうぞ。バグがなければ、これで最後だと思います。いやもう内容的には出尽くしたかな、と。更新内容はIComparer<T>を作成可能にしました。また、OrderByでIComparer<T>を利用するものへ、拡張メソッドを追加しました。
// こんなシーケンスがあるとして、IComparer<T>を使用してその場で自由に比較を指定したい var seq = new[] { 1, 2, 3 }; // IComparer<T>を作る var comparer = AnonymousComparer.Create<int>((x, y) => y - x); seq.OrderBy(x => x, comparer); // OrderBy/ThenByに拡張メソッドが追加されているので、型推論が効いたまま書けます // List.Sort(Comparison)みたいなイメージですかね seq.OrderBy(x => x, (x, y) => y - x); // 3, 2, 1 seq.OrderByDescending(x => x, (x, y) => y - x); // 1, 2, 3
LinqにはDescendingが用意されているので、あまり使い道はなさそうですね。私もOrderByのICompare<T>オーバーロードを使いたいと思ったシチュエーションが今までにありませんし……。第一引数がkeySelectorなので、それで十分用を足せちゃうのですよね。それにしても、DescendingでIComparerを指定した場合の結果は紛らわしくていかんですな。
さて、更新内容はもう一つあって、むしろこっちのほうが重要なのですが、compareKeySelectorを利用したオーバーロード(Linq演算子への拡張メソッドは全部それです)で、シーケンスにnullが含まれている場合にnullで落ちるのを修正しました。今回からはヌルぽで落ちません。どういうこっちゃ、というと説明しづらいのでコードで。
class MyClass { public int MyProperty { get; set; } public override string ToString() { return "Prop = " + MyProperty; } } static void Main() { var array = new[] { new MyClass{MyProperty=1}, null, new MyClass{MyProperty=2}, null, new MyClass{MyProperty=1} }; var r1 = array.Count(); // 5 var r2 = array.Distinct().Count(); // 4 (nullが重複として消える) foreach (var item in array.Distinct(mc => mc.MyProperty)) { Console.WriteLine((item == null) ? "ヌルぽ" : item.ToString()); } // 出力結果は // Prop = 1 // ヌルぽ // Prop = 2 }
といった感じです。分かったような分からないような?
AnonymousComparer - ver.1.1.0.0
- C# - 10.01/26
AnonymousComparerをバージョンアップしました。ダウンロードは上記リンク先、CodePlexからどうぞ。更新内容はCreateのオーバーロードに追加して、IEqualityComparerの完全模写を可能にしました。今まではキー選択だけだったのですが、今回からはEqualsとGetHashCodeを個別に指定することが可能です。
var myClassComparer = AnonymousComparer.Create<MyClass>( (x, y) => x.MyProperty == y.MyProperty, // Equals obj => obj.MyProperty.GetHashCode()); // GetHashCode
こんな感じに指定します。全くもってそのままです。Equalsとかをラムダ式で指定するというだけです。型推論は効きませんので指定してやってください。なお、使い方自体は全然変わってませんので、普通の使い方は初回リリース時の記事を参照ください。
以下オマケ。
// ランダムでtrueかfalse返すComparer var rand = new Random(); var randomComparer = AnonymousComparer.Create<MyClass>( (_, __) => rand.Next(0, 2) == 0, _ => 0); // 間引くのに使えるぜ! var mabiita = list.Distinct(randomComparer).ToArray(); // とか思ったけど、最初の一つ目は必ず選択されるのよね(Distinctなので当然……) // といった、特殊な間引き方をしたい人はどうぞ(いません) // 普通にやるならWhereでランダムにフィルタリングするのを選びます
色々と用途が考えられるようで、そもそもEqualityComparer自体があまり使う状況ってないので、使い道が考えられない微妙な感じが素敵です。まあ、AnonymousComparer自体はふつーに使う分にはふつーに便利ですので、Linqのお供にどうぞ。もう一つ、今度は役に立つ例でも。
class MyClass { public int Prop1 { get; set; } public string Prop2 { get; set; } public string Prop3 { get; set; } } static void Main() { var array = new[] { new MyClass{Prop1=30, Prop2="hoge", Prop3="huga"}, new MyClass{Prop1=100, Prop2="foo", Prop3="bar"}, new MyClass{Prop1=100, Prop2="hoge", Prop3="mos"}, new MyClass{Prop1=30, Prop2="hoge", Prop3="mos"} }; // Prop1とProp2が一致するのだけ省きたい!といった複数キー指定は匿名型を作る array.Distinct(mc => new { mc.Prop1, mc.Prop2 }); }
複数キーで比較したい時は、匿名型を作るのが手っ取り早いです。こういった用途に匿名型を使うというテクニックは、Linqの他のところでも結構出てくるので覚えておくと便利です。富豪的?気にしない気にしない。
C#(.NET Framework)の文字列連結について
- C# - 10.01/07
一般に文字列を+=で連結するのは遅いと言われています。事実そのとおりで、多量の連結を+=で行うと死ぬほど時間がかかります。例えば、以下のようなコードで示されることが多いでしょう。
// ベンチマーク用関数(10万回実行) Func<Action, TimeSpan> bench = action => { var sw = Stopwatch.StartNew(); for (int i = 0; i < 100000; i++) { action(); } return sw.Elapsed; }; // StringBuilderの計測(最後のToStringを入れてませんが、あまり変わらないのでスルー) var sb = new StringBuilder(); var sbTime = bench(() => sb.Append("hoge")); // stringの+=での計測 var s = ""; var stringTime = bench(() => s += "hoge"); Console.WriteLine(sbTime); // 0.004sec Console.WriteLine(stringTime); // 14.97sec
0.004secと15secでは話になりません(正確には、StringBuilderでは最後に文字列に変換するToStringを入れるべきですが、それでも1secは超えなくて差は歴然なので省略します)。ならば、文字列を連結する場合は、どのような時でもパフォーマンスのためにStringBuilderを使うべきでしょうか? 答えは違います。
// ILではひとつにまとまる // IL_0001: ldstr "abcde" var s = "a" + "b" + "c" + "d" + "e";
定数の連結はコンパイル時にひとまとめにされるので、StringBuilderを使うのは愚かな選択となります。この辺はILDASMで見ればわかるし、Reflectorでもひとまとめになって展開されているのが確認できます。では定数ではなく動的に値を返すものは?
static string Get() { return DateTime.Now.ToString(); } static void Main(string[] args) { // IL_0031: call string [mscorlib]System.String::Concat(string[]) var s = Get() + Get() + Get() + Get() + Get(); }
ILを見ると、s += Get(); s += Get(); みたいな展開のされかたにはならず、String.Concat(string[])が呼ばれることになります。よって、速度を心配してStringBuilderを使う必要は全くありません。測定してみましょう。(ちなみにs+=Get()だとString.Concat(string,string)が大量に呼ばれることになるのが遅い理由)
// benchとGetは上で使ったのと同じものを流用 var sbTime = bench(() => { var sb = new StringBuilder(); sb.Append(Get()) .Append(Get()) .Append(Get()) .Append(Get()) .Append(Get()); var s = sb.ToString(); }); var stringTime = bench(() => { var s = Get() + Get() + Get() + Get() + Get(); }); Console.WriteLine(sbTime); // 0.65sec Console.WriteLine(stringTime); // 0.64sec
速度はほとんど変わりません。StringBuilderとString.Concatでは処理の中身は結構違いますが、速度変わらないのならどっちでもいいよね。なら、記述しやすいほうを選ぶのが良いでしょう。妄信的にパフォーマンスのためにStringBuilder!とか思っている人は、少し考え直してみてください。そんなの当たり前だろ常識的に考えて、と思っていた時期が私にもありました……。世の中は存外StringBuilder神話に溢れているかもですよ? いやほんと。あとC#なら逐語的リテラル文字列もお忘れなく。
AssemblyInfoの取得
- C# - 09.12/31
public sealed class AssemblyInfo { public string FileName { get; private set; } public string Version { get; private set; } public string FileVersion { get; private set; } public string Title { get; private set; } public string Description { get; private set; } public string Configuration { get; private set; } public string Company { get; private set; } public string Product { get; private set; } public string Copyright { get; private set; } public string Trademark { get; private set; } public string Culture { get; private set; } public AssemblyInfo(Assembly assembly) { FileName = assembly.GetName().Name; Version = assembly.GetName().Version.ToString(); FileVersion = GetAttributeName<AssemblyFileVersionAttribute>(assembly, a => a.Version); Title = GetAttributeName<AssemblyTitleAttribute>(assembly, a => a.Title); Description = GetAttributeName<AssemblyDescriptionAttribute>(assembly, a => a.Description); Configuration = GetAttributeName<AssemblyConfigurationAttribute>(assembly, a => a.Configuration); Company = GetAttributeName<AssemblyCompanyAttribute>(assembly, a => a.Company); Product = GetAttributeName<AssemblyProductAttribute>(assembly, a => a.Product); Copyright = GetAttributeName<AssemblyCopyrightAttribute>(assembly, a => a.Copyright); Trademark = GetAttributeName<AssemblyTrademarkAttribute>(assembly, a => a.Trademark); Culture = GetAttributeName<AssemblyCultureAttribute>(assembly, a => a.Culture); } private string GetAttributeName<T>(Assembly assembly, Func<T, string> selector) where T : Attribute { var attr = assembly.GetCustomAttributes(typeof(T), true).Cast<T>().FirstOrDefault(); return (attr == null) ? "" : selector(attr); } } // 利用例 class Program { static void Main(string[] args) { var assembly = Assembly.GetEntryAssembly(); var info = new AssemblyInfo(assembly); // こんな感じに。 Console.WriteLine(info.Title); } }
通常AssemblyInfo.csに記載する、タイトルとか説明とかバージョンの取得って、用いたいシーンも少なくないわりに存外面倒くさい。そんなわけで補助クラスを作ってみました。コンストラクタにAssemblyを投げ込むと、文字列にして返してくれます。
これと同じことをやるコードを一年ぐらい前に書いたのですが、今見たらどうしょうもなく酷かった……。(あまりにも酷いので見せられません!)。なので、今基準で書き直してみました。いかに型を書かないで済ませるか、いかに行数を少なく見た目をすっきりさせられるか。Func<T, string>という発想が一年前は出来なかったんだなあ。CastやFirstOrDefaultも知らなかったやも。
そんなわけで今年もありがとうございました。無事、閉鎖せずに一年を乗り越えられました。ただ、ある意味閉鎖してますけれどね、ゲサイト的な意味では。今年を振り返るとプログラミング、プログラミング、プログラミングでした。一年前とは見比べるまでもなく成長出来たと思います。ブログにコードを晒すこと、小さくてもいいのでソフトウェアを作って公開すること、というのが確実に貢献してくれました。よく言われる、コード晒せば他の人の添削が期待できるよ!ってのはそこまで期待できないと思うのですが(勿論、ありがたい指摘も幾つかありました、感謝です)、それよりも他の人が見る、という意識をもってコードに取り組むのが効いた気がします。カッコつけて書こうと、何度も練り直すのが結果的には良かったかな、と。
もう少し振り返れば、なんといってもC#、というかLinq。Linqの魅力に取りつかれて、そのままフルスロットルで加速した一年でした。C#や.NET Frameworkに詳しいか? と言われるとまだまだモニョるのですが、Linq to Objectsなら詳しい、と言えるだけの自信はつきました。これは、linq.jsとしてJavaScriptに移植したのが大きいです。動作が完全に一致するよう何度もチェックしたり、Monoのソース読んだり、リフレクタでSystem.Core.dll読んだりしたので、内部をきちんとイメージ出来るようになったので。
来年は、んー、とりあえずlinq.jsのWSH拡張の早期リリースを目指したいです。linq.jsは、個人的には非常に便利だと思っているのですが、ウェブ用のライブラリとしてアピールするのはどう考えても「無理」。パフォーマンス無視で、リスト処理がこんなに簡単に書けるんです、どうでしょう?ってんじゃあ請求力もないって話です。そもそもウェブ用JavaScriptで多用するDOM操作関連は未実装部分(linq.js Xml Extensions)多いし。とはいえ、せっかく作ったわけなので、真面目に布教させたいと思っています。今のところJavaScriptライブラリの隙間、WindowsScriptHost用やテキストエディタのマクロ用としての応用例を探っている、というか実装中。特に、WSH用に使うと物凄く便利なことが分かったので、とっとと実装を終えて、使ってみて欲しいところです。
実装中というか、途中で放置しちゃってるのがアレですが。ちょろっと記事書いたのが8月。今まで、それから全く進んでおりません。XboxInfoTwitのリリースに追われたり、Rxで遊んだりで放置ルートに入ってしまったのですねえ。やる気はめっちゃあるので、来年はまず一番に、linq.jsのWSH拡張のリリースを目指します。本気で本当に。
プログラミング以外だと、今年で一番影響が大きかったのがTwitterかなあ→neuecc on Twitter。今までチャットやネトゲなど、コミュニケーション系のウェブサービスを全く受けつけなかったコミュ不全の私が、唯一利用出来たサービスだという。一人で書き飛ばしていればよくて、無理に繋がらなくていいのが非常に楽。……。まあ、私はもう少し@飛ばしてもいいと思います。むしろ飛ばせ。これも来年の目標、ですかね。
C#のWebRequestとWebClientでCookie認証をする方法(と、mixiボイスへの投稿)
- C# - 09.12/17
WebからHTMLをダウンロードするにはWebClientが便利です。が、そのまんまだとCookie認証で躓きます。せっかく便利にダウンロード出来るのに、認証を超えられないんじゃ意味が無いよ!というわけかで幾つかのやり方を紹介したいと思います。海外だと沢山情報が出回っているのですが、日本だとWebClientはクッキーがとれないが検索上位に出てくるので、WebClientの利用を諦めて面倒くさいWebRequestを使う羽目になっている人が多いんじゃないかしらん。WebRequestなら@ITの記事、@IT:.NET TIPS クッキーを使ってWebページを取得するには?が引っかかりますからね。
とりあえず、@ITのmixiへの認証を例題に、まずはWebRequestでのやり方を見てみます。
// WebRequestによるCookie認証 // POSTしてCookieContainerに書き込む var data = Encoding.ASCII.GetBytes(string.Join("&", new[] { "next_url=/home.pl", "email=めるあど", "password=ぱすわど" })); var cookieContainer = new CookieContainer(); var req = (HttpWebRequest)WebRequest.Create("https://mixi.jp/login.pl"); req.CookieContainer = cookieContainer; req.Method = "POST"; req.ContentType = "application/x-www-form-urlencoded"; req.ContentLength = data.Length; using (var stream = req.GetRequestStream()) { stream.Write(data, 0, data.Length); } var res = req.GetResponse(); // ここでCookieContainerに書き込まれる // 以下、そのCookieを使えばアクセスし放題 var reqLog = (HttpWebRequest)WebRequest.Create("https://mixi.jp/show_log.pl"); reqLog.CookieContainer = cookieContainer; // CookieContainerセット var resLog = reqLog.GetResponse(); using (var stream = resLog.GetResponseStream()) using (var sr = new StreamReader(stream, Encoding.GetEncoding("euc-jp"))) { Console.WriteLine(sr.ReadToEnd()); // アクセスできてるのを確認 }
CookieContainerを設定すれば、Cookieのサーバーからの取得も送信も全部自動でやってくれる、というのがポイント。そこは楽です。楽なのですが、WebRequest自体が使いづらい。何をやるにも、いちいちStreamがどうだのこうだのなんてウンザリです。ていうか何だこのvarの多さ、変数乱れ打ち! そうなるとついつい、よーしパパ、ラッパー作っちゃうぞー、とか言ってしまいますが、もう見てらんない。.NET FrameworkにはWebClientというMS謹製のラッパーがあるわけなので、それを使いましょう。認証?Cookie?自前で取ればいいんですよ、ヘッダーから。
// WebClientならポストは超簡単! var wc = new WebClient { Encoding = Encoding.GetEncoding("euc-jp") }; wc.UploadValues("https://mixi.jp/login.pl", new NameValueCollection { {"next_url", "/home.pl"}, {"email", "めるあど"}, {"password", "ぱすわど"} }); // じゃあCookieはどうするの?というと、ResponseHeaderから自前で抽出します var setCookie = wc.ResponseHeaders[HttpResponseHeader.SetCookie]; var cookies = Regex.Split(setCookie, "(?<!expires=.{3}),") .Select(s => s.Split(';').First().Split('=')) .Select(xs => new { Name = xs.First(), Value = string.Join("=", xs.Skip(1).ToArray()) }) .Select(a => a.Name + "=" + a.Value) .ToArray(); var cookie = string.Join(";", cookies); // 以降は取得したCookieをHeaderに設定しておけばOk wc.Headers[HttpRequestHeader.Cookie] = cookie; var result = wc.DownloadString("https://mixi.jp/show_log.pl"); Console.WriteLine(result); // アクセスできてるのを確認
そう、WebClientでも、ResponseHeaderからSetCookieは取れるのです。なので、ここからCookieにバラしてやれば、あとはHeaderに設定するだけなので簡単です。一見WebRequest並に行数がかかっているのですが、大変なのはCookie分解部分だけです。分解がちょっと面倒なのは否めませんが……。基本的にカンマ区切りとなっていますが、有効期限の設定されているものが含まれていると「expires=Fri, 16-Dec-2011」のようにカンマが入ってしまい、単純なSplit(’,')では失敗します。なので正規表現の否定戻り読みでexpires=***,の場合は除外しています。あとは、バラしてクッツケテ、を繰り返して生成。そういえばSelect三連打ですが、これはもちろん複数行にすることでSelect一つで済ますこともできます。でも、そこはそれぞれ役割を切って3つに分けるのが、私の美意識、でしょうか。効率を考えれば匿名型なんて作らない方がいいぐらいなのですけどね、効率じゃない良さってのがあるんです。Linqには。
やり方はまだあります。WebClientは本当にただのWebRequestのラッパーで、中では普通にWebRequestを呼んで処理しています。よって、継承してoverrideしてGetWebRequestの辺りを書き換えて、CookieContainerを使うようにすれば非常に簡単です。
class CustomWebClient:WebClient { private CookieContainer cookieContainer = new CookieContainer(); // WebClientはWebRequestのラッパーにすぎないので、 // GetWebRequestのところの動作をちょっと横取りして書き換える protected override WebRequest GetWebRequest(Uri address) { var request = base.GetWebRequest(address); if (request is HttpWebRequest) { (request as HttpWebRequest).CookieContainer = cookieContainer; } return request; } } // WebClientを継承してちょっと書き換えてやれば一番簡単 var cwc = new CustomWebClient { Encoding = Encoding.GetEncoding("euc-jp") }; cwc.UploadValues("https://mixi.jp/login.pl", new NameValueCollection { {"next_url", "/home.pl"}, {"email", "める"}, {"password", "ぱす"} }); var result = cwc.DownloadString("https://mixi.jp/show_log.pl"); Console.WriteLine(result); // アクセスできてるのを確認
私的にはこれがお薦め。どうせWebRequestはそのまんまじゃ使い辛いので、多かれ少なかれラッパー作るでしょう。出来の悪いラッパーを作る/使うぐらいなら、WebClientの気の利かない部分だけ書き換えた方が良い。 ちなみにCookieの他にもWebClientの気の利かないところとしては、自動でリダイレクトするところが辛い、場合がある。普段はリダイレクトでいいんですが、リダイレクトされると困るシチュエーションもあります、たまに。そんな問題も、CookieContainerと同じくGetWebRequestの部分で、request.AllowAutoRedirectを設定すれば回避出来ます。
Web上のものをゴニョゴニョ処理するのに「Rubyなどのスクリプト言語の良さが目立つ。」というのは、ライブラリの問題にすぎない、ってことですな。XML処理には今やLinq to XMLがあるし、HTMLの取得にしてもちょっと工夫するだけで回避できるのでC#だから書きにくい、なんてことは無いと思っています。いやまあMechanize便利やん、とかありますがありますが。しかしC#には最終兵器、WebBrowserがあるので何とでもなる。HTML解析ならHtml Agility Packを使えば、物凄く簡単に出来ます。
最後に、Twitterの自分の投稿最新20件をmixiボイスに投げ込む、というコードを例として出してみます。CustomWebClientクラスは上に乗っけた奴を使っています。
static void Main(string[] args) { var encoding = Encoding.GetEncoding("euc-jp"); // ログイン var cwc = new CustomWebClient { Encoding = encoding }; cwc.UploadValues("https://mixi.jp/login.pl", new NameValueCollection { {"next_url", "/home.pl"}, {"email", "めーる"}, {"password", "ぱすわど"} }); // 投稿に必要なpost_keyをhtmlから取り出す var echo = cwc.DownloadString("https://mixi.jp/recent_echo.pl"); var postKey = Regex.Match(echo, "id=\"post_key\" value=\"(.+?)\"").Groups[1].Value; // 例なので簡易化するため認証無しのTwitterステータスを取得します // HttpUtilityの利用にはSystem.Webの参照設定が別途必要 var id = "自分の(じゃなくてもいいけど)TwitterID"; var texts = XDocument.Load("http://twitter.com/statuses/user_timeline/" + id + ".xml") .Descendants("status") .Select(x => HttpUtility.HtmlDecode(x.Element("text").Value)) .Reverse(); foreach (var text in texts) { // mixiボイスに投稿(UTF-8以外の日本語の投稿はUploadValuesが使えない(泣) cwc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded"; cwc.UploadString("http://mixi.jp/add_echo.pl", string.Join("&", new[] { "body=" + HttpUtility.UrlEncode(text, encoding), "post_key=" + postKey, "redirect=recent_echo" })); } }
差分を記録するようにしたり、@付きを除外したりするようにすれば、そこそこ使えるんじゃないかしらん。利用はご自由にどうぞ。
Linqでコマンドラインオプション解析
- C# - 09.12/13
最近、Linqでの副作用について考えこむことが多くなりました。きっかけはSelectメソッド内で、外部のListに対してAddしたあげくreturn null -> ToArrayとかいうForEach代わりに使うかのような超勘違いしたコードを見せられたことなのですが(自信満々にどうだ!って感じで出されたのでモニョるしかなかったという苦い記憶ががが) と、そんな私の愚痴はどうでもよくて、副作用。基本的には邪悪ですよね。個人的に嫌なのは、せっかくスコープが狭く、ラムダ式だけを見つめれば良い状態になっているのに、副作用が入ると広い範囲を意識しなければならないこと。この変数名はどこからきたの? インスタンスの状態はどうなるの? 考えごとが増えるのは嫌なものです。ミスも増えるでしょう。エラーの温床となってしまいます。
とはいえ、使いどころによっては強力な効果を発揮するのも事実。例えば、以前書いたIEnumerableの文字列連結なんて、カウント用変数を一つ用意するだけで、Aggregateでサクッと書けてしまいます。というわけで、無駄な多用は厳禁だけど、使いどころをちゃんとおさえて書きましょう、というイイコな結論を出しつつ本題というか例題。
コマンドラインオプション解析。シーケンスを前方から解析して、次のキーが現れるまでは以前のキーで分類する。という分かるような分からないようなお話です。コマンドラインオプションだけでなく、たとえば決まった形式のテキストファイルを解析するとかでよくありそうなパターンだと思います。これがXMLなら簡単に解析出来るのに、クッ…… みたいな。
さて、グループ分けとなると、じゃあLinqで出来るよね? GroupByかなんかを使えばいいっしょー。と思い浮かぶわけなのですが、素の状態だと上手くいきません。GroupByを使うためのキーを列挙内部だけで保持することは出来ないからです。じゃあどうするか、というと、そうそう、副作用です副作用。はいはいクロージャクロージャ。というわけで列挙中にサクサクッとキーを書き換えてしまいましょう。
// こんな風に来るコマンドラインオプションを解析しよう var args = new[] { "-i", "input.txt", "-hoge", "-huga", "-o", "output.txt" }; // グループ分けといったらLinqだよね? // ディクショナリに分解したい、dict["-i"]で"input.txt"が取れる、というように // コマンドラインオプションをHashSetに格納する var options = new HashSet<string> { "-i", "-hoge", "-huga", "-o" }; string key = null; var result = args .GroupBy(s => options.Contains(s) ? key = s : key) // 副作用! .ToDictionary(g => g.Key, g => g.Skip(1).FirstOrDefault()); // 1番目はキーなのでSkip
とまあ、こうなります。副作用便利!
そういえばコマンドラインオプションの解析は.NET Framework標準では用意されていないのですよねえ。外部ライブラリのものは、当然なのですがあらゆるものに対処するため、どれもこれもヘヴィーすぎです。別にそんな複雑なのいらないよー、-oを解析出来ればそれだけでいいんだよー、的な小さいシチュエーションなら、この程度でも問題ない、はずです、きっと。
例としてコマンドラインオプションの解析を出したのは、id:coma2nさんのNDesk.Options(Mono) - コマンドラインパーサー - Programmable Lifeという記事を見てのことです。このNDesk.Optionsは凄いですね! まだ触ってないので実際の使いかっては分かりませんが、ラムダ式の使い方に驚きました。非常に上手いやり方だと思います。シンプルで。明快で。覚えやすく書きやすく。私もこういう発想が出来るようになりたいなあ。
書評 : More Effective C#
- C# - 09.12/06
結論は「Linq to Objectsの本」です。全編に渡って例題がLinqの再実装となっていて驚きました。「作って学ぶLinq」のほうが題として正しいぐらい。冗談じゃなく本当に、7割ぐらいが実質Linq周りです。実質、と言ったのは本書中では特に明言されていないからですが、見ればすぐにこれLinq to Objects……と突っ込みたくなること請け合いの例が沢山収録されています。
以前からLinq to Objectsに絞った解説書が出るべきだ、と思っていました。Linq to Objectsはこれでいて結構深いのです。どうもLinqというとLinq to SQLとか、データベース周りの喧伝の印象が強いようで、Linq to Objectsの実態が正しく伝わっていない気がします。今時リスト処理に高階関数使うなんてどのLightweight Languageでも常識よねー、というお話でもあります。Rubyのメソッドチェイン+ブロックなんて見た目だけで言えばLinqと丸っきり一緒ですし。昨今のモダンな言語の最も優れた部分を、最も優れた形で掲示しているのがLinqです。(優れた形、というのに異論はあると思いますが突っこまんで下さい)
そんなわけで褒め称えたいところだし、内容は結構良いと思っているのですが難点が一つ。対象範囲がC#3.0までのわりに、書き方が微妙に2.0っぽいこと。これはよろしくない。Linqに関しても再実装であることが本書中に明言がなく、書き方が2.0なので、「2.0でLinqをやるには」になっています。別に原理を知れればいいわけで、何も本書中の書き方を真似る必要はないのですが、それだと人に薦めづらいのですね。Linq知らない人に、これ見て学ぶといいよ、と素直に手渡したいのだけど手渡しにくい感が悔しいです。変にC#2.0と3.0を行ったり来たりするようなフワフワした構成じゃなく、Linqであることを明言した上で、その解説に徹してくれればよかったなあ、なんて思うんですね。
本の意義というか効用は、Linqや高階関数を多用してしまっても、この本が免罪符になるというのが一番大きいですね! C#3.0というのはLinqを使いこなし、更にはLinq風に設計構築していくのがEffectiveなのです、と大手を振って言える、かも。でもまあ、実際Linq風に扱うのが基盤になっているのは確かなので、変に凝るよりはLinq to Xxxみたいになっているほうが嬉しいです<ライブラリのような根幹部分での設計
Moreが先に出ていて、無印の発刊はこれから先です。無印がMoreの後に出るのは、本国では無印はC# 4.0対応の第二版が出るのでそれを待つのかなー、と思ったのですがそういうわけでもないようで。というわけで恐らくC#1.0まで対応のものだと思うので残念のような、そうでもないような。私はC#3.0から入ったにわかC#使いなので、1.0の書き方を見れるというのも新鮮で面白いんじゃないかなー、なんて思ってます。