AssemblyInfoの取得

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ボイスへの投稿)

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でコマンドラインオプション解析

最近、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は凄いですね! まだ触ってないので実際の使いかっては分かりませんが、ラムダ式の使い方に驚きました。非常に上手いやり方だと思います。シンプルで。明快で。覚えやすく書きやすく。私もこういう発想が出来るようになりたいなあ。

TwitterTLtoHTML ver.0.1.0.0

ノートPCを買いました。完全デスクトップ至上主義者だったというのに!あれです、あんまり引きこもってばかりいるのもよろしくないので、ノートPCさえあれば勉強会とかも出れる!のかどうかは、そもそもなくても出れるよねえ、あっても出れないよねえ(私の非コミュ脳的に) などと思いつつも、まあそんなこんなで買いました。流行りのCULVノートって奴です。Visual Studio 2008が思っていたよりも遥かに実用的な速度で動いていて、そう、こんなんでいーんだよ、とか思ったりなどした。けれど、VS2010は絶望的に動かなかった。重過ぎる。世の中厳しい。

引きこもり解消目的の他にもう一つ、常時起動の半サーバー用途というのもあります。ストリームAPIを監視したxboxinfotwitusersリストへの追加プログラムを常時デスクトップPCで振り回すのもカッタルイというか消費電力的に無駄なので、低消費電力なノートPCへ退避させよう、と思ったわけです。そもそも他にも、はてなついったー同期ツールだのXboxInfoTwitだの、PC常時起動を前提のアプリを幾つか公開しているので、調度良いということで。

んで、本題。常時起動PCがあるなら、過去ログも常時起動で定期的に取得して、差分をHTMLに残せばいいよね!それをDropboxなんかの共有フォルダに保存するようにすれば、取りこぼしもないし、何処からでもログを参照できるしで最高ぢゃん(そこで本当に自宅鯖にしてネットワークに公開する、というのは手間がかかりすぎるので超却下)。というわけで定期起動実行用のモードを追加しました。前回取得からの差分のみを、yyyy/MM/dd_HHmmssの形式(/はフォルダ)で保存します。今まで通り、過去800件取得モードも残してあります。

定期取得でやりたい場合は、タスクスケジューラに突っ込めばおk。タスクスケジューラは柔軟に設定出来る分、とっつきづらくて面倒くさいんですねえ。でも、例えば「バッテリ電源の時は実行しない」とか素敵オプションが色々用意されているので、使うといいと思います。トリガを大量に設定しておいて、18-24とかの流速の激しい時間帯は更新間隔短め、0-9とか静かな時間帯は更新間隔長め、12時のお昼休憩の前に一度まとめて読みたいので12時ジャストに設定。とか色々と考えられますので適当に気に入る設定を探ると良いんじゃないかと思われます。

あと一応、TinyUrlとかのデコード機能も入れておきました。実装は超手抜きで、Urlを片っぱしから WebRequest.Create(url).GetResponse().ResponseUri.AbsoluteUri; しているだけです。んま、問題ないでしょう、多分。普段Echofonで短縮Urlのまま表示されていただけに、こうして展開された形で見れると、いかに短縮Urlがイライラさせるものなのかよーく分かりますな。投稿時に必須なのはしょうがないのですけど……。

それと、今回からは初回設定は対話式ウィザードで行うようにしてます。あと、パスワードはそのPCでのみ復元出来る、という形で暗号化されます。設定ファイル直書き換えで一番嫌なのは、パスワードを平文で置く、ということなので、それを避けるために、ですね。書き捨ての小さいコンソールアプリなのでいっかー、と最初思ったんですが、やっぱり気になりました。

最後に、バグフィックス。二重でHtmlエンコードしてた部分を直しました。TwitterからのXMLは既にHTMLエンコードされている状態なので、それをそのままXElementに流し込むと二重でエンコードされてしまいます。なので、一旦デコードしています。この辺は結構よくミスしてしまうんですよねー。取得したものがどんな状態なのか、利用するクラスがどういう動作をするのか、ともにちゃんと把握していないとハマリがちです。

ちなみにまるで利用者がいる風な口で紹介していますが、ダウンロード数は超絶少ないので利用者なんていませんよ! 完全に自分用ですな。

2009/12/09 追記

ダウンロード先ファイルが古いバージョンのままでした……。今、直しました。ただでさえゼロに近いダウンロード数だったというのに、こうして使ってくれるかもしれない/コードを見てくれるかもしれない人を失ってしまう……。

書評 : More Effective C#

結論は「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の書き方を見れるというのも新鮮で面白いんじゃないかなー、なんて思ってます。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive