LINQ to XMLのNamespaceと書き出し時のEncodingについて

ver 0.0.0.2に更新。アップロードするフォルダが指定出来るようになりました。アップロードツール名(FotolifeUploader)が利用されるようになりました。フォルダ指定は再設定が必要なので、前のバージョンを使っている方はsettings.xmlを削除して、再度設定し直してください。あとは間抜けだったUploadToFotolifeメソッドを手直ししたり。

私自身が、そもそもフォトライフのヘビーユーザーではないので、細かいところに気が利いてないかもですね……。そういうのは、よくない。というわけで、当分はFotolifeをちゃんと利用しようキャンペーンを張ることにします。なので、デジタル一眼を買う。と言いたいのだけど、何か微妙なのよねん。いや、そもそも引き籠って家から出ないので撮影するものがないので。かといって熱帯魚や食虫植物とかフィギュアとか、撮影に適した趣味があるわけでもなく。困った困った。まあ、考えます。食虫植物を育てる方向で(?) 部屋が殺風景なので何かは入れたいのだけど、手間はかけたくない。ううむ、難しい。

LINQ to XML

アップロードにはAtomAPIを利用しているので、XMLです。つまりLINQ to XMLの出番です。出力結果がこんな感じなので、そこから逆に考えると……

<entry xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://purl.org/atom/n
s#">
  <title>タイトル</title>
  <content mode="base64" type="image/jpeg">画像BASE64</content>
  <generator>FotolifeUploader</generator>
  <dc:subject>フォルダ名</dc:subject>
</entry>

XElementは、Namespaceの利用が少しややこしいんですよね。最初引っかかりました。「XNamespace.Xmlns + "接頭辞"」で登録できます。

XNamespace ns = "http://purl.org/atom/ns#";
XNamespace dc = "http://purl.org/dc/elements/1.1/";
var xml =
    new XDocument(new XDeclaration("1.0", "UTF-8", null),
    new XElement(ns + "entry", new XAttribute(XNamespace.Xmlns + "dc", dc),
        new XElement(ns + "title", "タイトル"),
        new XElement(ns + "content", new XAttribute("mode", "base64"), new XAttribute("type", "image/jpeg"), "画像BASE64"),
        new XElement(ns + "generator", "FotolifeUploader"),
        new XElement(dc + "subject", "フォルダ名")
    ));
Console.WriteLine(xml); // 出力確認、DeclarationはToStringでは出力されない

少し独特ですが、ほとんど1:1で対応させられるので慣れるとサクサク書けます。非常に快適。個人的にはXMLリテラル的なものよりも好き。Linqがあってほんと良かった……。で、Declarationを出力したい場合の話に続く。(hatena (diary ’Nobuhisa))にもあるように、ToStringでは出力されないのでSaveを使う、と……

var sb = new StringBuilder();
var sw = new StringWriter(sb);
xml.Save(sw);
Console.WriteLine(sb); // UTF-16になる

これでencodingがUTF-16になるのは、Saveメソッド呼ぶとDeclarationは作りなおしているから。.Save("string fileName")ではXDeclarationのエンコーディングを見て、それで保存するけれど、それ以外の場合はXDeclaration無視で再構築される。XDocumentというかXmlWriterのほうの話でしょうか。実際にファイル出力してみると分かる。

var fs = new FileStream(@"C:\text.xml", FileMode.Create);
var sw = new StreamWriter(fs, Encoding.GetEncoding("x-mac-turkish"));
xml.Save(sw);

出力先のエンコードに合わせてくれる、のを便利と見るか、むしろ気が利かない、Writer部分もC#3.0に合わせて作りなおせ、なのかは不明。まあ、嘘エンコード宣言は許しませんよってことですかね。じゃあどうするか、って言ったら

// これで別に何も問題ないと思います、文字列として吐くんだからToStringでいいと思ふ
var xmlString = string.Format("{0}{1}{2}",
    xml.Declaration, Environment.NewLine, xml);
Console.WriteLine(xmlString);

// ToStringがどうしても嫌ならMemoryStream経由で、とか?
string result;
var encoding = Encoding.UTF8;
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms, encoding))
{
    xml.Save(sw);
    result = encoding.GetString(ms.ToArray());
}
Console.WriteLine(result); // 望み通りのUTF-8で出力されてます

結論は、普通にToStringでいいんじゃないかな、と。ToStringメソッドだけではXmlWriterSettingsで言うところのOmitXmlDeclarationを設定出来ないから、デフォルトでは付加しないようにしてる。削除は無理だけど、追加なら簡単だから。XmlDeclarationを付加したい時は別途、自分でくっつければいい。というだけのお話かなー? ToStringで一発で終わらせられないからStringBuilder使って組み立てるってのは、何でそうなるの?と、とても思った。ついでにもう一つ。

// こんなXElementがあるとして
var xElement = XElement.Parse("<hoge>3</hoge>");
// intとして値を取り出す時は
var num1 = int.Parse(xElement.Value); // これダメ。
var num2 = (int)xElement; // こう書こう。

です。LINQ to XMLは既存のものを上手く使ってシンプルに書けるように作られてる。気がする。このキャストもそうだし、ToStringもそう。Parseは頻繁に行うから汚くなるよね→キャストでよくね? 文字列化はよくやるけどSaveもXmlWriterSettingsも面倒くさいよね→ToStringでよくね? といった感じ。関数型構築もそうだけど、今までのもの(XmlDocument)を踏まえて、よく練り直されているなー、と思います。

半自動はてなフォトライフアップローダー ver 0.0.0.1

はてなフォトライフに画像をワンクリックでアップロードするプログラムです。ワンクリックの手間があるので、半自動。主な機能は、実行すると設定したフォルダの最新の更新画像一枚をアップロード。利用例としてデジカメ接続時やメモリーカード内の画像フォルダを指定することを想定しています。写真撮る→PCに繋げる→プログラムを実行する→アップロード完了。みたいな流れです。Twitterに載せるための写真とか最新一枚で十分でしょう? Blogに載せる場合でも、一枚で済む場合って結構多いよね。そんな感じに、サクサクッと写真と付き合えたらいいな、と。

設定

まさかのCUI設定画面(笑) 初回起動時にこの画面になります。設定し直したい時は、生成されるsettings.xmlを削除してください。レトロでアナログで半自動を貫く感じがいいかなー、と思ったんですが、どうでしょう。

最新画像一枚のアップロード

設定終了後にexeファイルを実行すると、設定時に指定したフォルダの中の、拡張子が「jpg/jpeg/gif/png/bmp」で更新日時が最も新しいもの一枚をアップロードします。設定によってはアップロード後にブラウザでフォトライフのURLが開きます。なので、そこからそのままTwitterにURLをポストするなりBlog書くなりがシームレスに行えるわけです。キリッ。ちなみにリサイズ等はこちら側では一切しません、そのまま丸投げ。リサイズ処理もはてな任せ。

任意画像複数枚のアップロード

フォルダ/画像をまとめてexeファイル(本体じゃなくてショートカットでもOKです)にドラッグアンドドロップすると、そのファイルをアップロードします。フォルダはサブディレクトリを含めて全てのファイルをアップロードします。拡張子が「jpg/jpeg/gif/png/bmp」以外のものはちゃんと無視しますので、多少適当でも大丈夫。また、いわゆる「送る」にショートカットを登録することで、このドラッグアンドドロップと同様の結果になります。Vistaの場合はエクスプローラー上で「%AppData%\Microsoft\Windows\SendTo」と入力するとSendToのフォルダに飛べますので、ここにショートカットを登録してみてください。

今回コンソールアプリにしたのは、実行にかかる手間を最小にしたかった、というのがあります。普通のアップロードアプリだと、「アプリを起動→画像フォルダを開く→ドラッグアンドドロップで画像を乗っける→アップロードボタンを押す→アプリを閉じる→Fotolifeにアップロードされた画像を確認しにいく」 これじゃあ工程多すぎであまりにも面倒くさい。というわけで、最新画像一枚ならば、アプリ起動だけで完了。複数毎でも画像フォルダ→ドラッグアンドドロップだけで完了という、考え得る限りの最短を目指しています。

ソースコード

ソースコードも同梱してあります。csファイル一つだけの、200行ちょいのちっぽいコンソールアプリです。好きに改変とか突っ込みとかディスとかしてください。しいていえば、Linqだらけです。個人的には

.SelectMany(s => (Directory.Exists(s))
  ? Directory.GetFiles(s, "*", SearchOption.AllDirectories)
  : Enumerable.Repeat(s, 1))
.Select(s => new FileInfo(s))
.Where(fi => fi.Exists && FotolifeExtensionPattern.IsMatch(fi.Extension))

この部分が気に入ってます。ドラッグアンドドロップで来る文字列配列からファイル抜き出しの部分。SelectManyでディレクトリをファイル名配列に、ディレクトリじゃない場合はEnumerable.Repeatで繰り返し回数が1回のファイル名配列にする。あとはまあ普通に、SelectしてWhereしてToArray。Linqがあって良かったーと本当に思う。逆にAtomPub APIでアップロードする部分はLinqでやる意味がなかったというか、当初予定と変わってあれ追加これ追加で肥大化してしまった結果でして……。

LLの人はこの手のちょっとしたスクリプトをほいほい公開しているわけだから、C#もコンソールアプリぐらいほいほい公開出来ないといかんのぅ、と思いつつもページ用意して云々かんぬんは面倒くさくて、そうホイホイってわけにもいかない感じ。もちっと軽くやれる環境作らないとね……。まあ、でも、このちょっとした重苦しさも悪くはないんだ。だってほら、Rubyでスクリプトがホイッって転がってても、普通の人は動かせもしないわけですよ。だから、少し面倒くさいなー、と思いつつ設定画面つけてexeの形式にして、それだけで幸せになれないかな、どうだろう。

私はプログラム書き始めたのがほんとつい最近で、利用するだけ人間の歴が何年も何年もあるので、その辺は極力優しくやりたいなあ、と思ってます。

可変のLookup

一対多の Dictionary が欲しい - present

今だと、やっぱLookupがあるので、ILookupの実装という形で作った方が統一感取れていいのかな……? 通常のLookupはToLookupでしか作成できず不変なので、追加したり削除したり出来るILookupの実装を作りました。MultiDictionaryとかMultiMapとか言われているもの、ですが名前をimmutableの反対なのでMutableLookupとしてみました。かなり、微妙。名前ってむつかしい。んで、中身はDictionary<TKey, List<TValue>>のゆるふわラッピング。コンストラクタでIEqualityComparerを突っ込めるので、大文字小文字無視とかも出来ます。

// こんな風に使います
var mutableLookup = new MutableLookup<string, string>();
mutableLookup.Add("食べ物", "たこやき");
mutableLookup.AddRange("食べ物", new[] { "いかやき", "さかなやき" });
mutableLookup.Add("飲み物", "ぽかり");

IEnumerable<string> tabemono = mutableLookup["食べ物"]; // インデクサでアクセス
foreach (var item in mutableLookup) // 列挙するとIGroupingが出てくる
{
    var key = item.Key; // キー(食べ物,飲み物)
    var array = item.ToArray(); // 配列
}
public class MutableLookup<TKey, TValue> : ILookup<TKey, TValue>
{
    private readonly Dictionary<TKey, List<TValue>> dictionary;

    // Constructor

    public MutableLookup()
    {
        dictionary = new Dictionary<TKey, List<TValue>>();
    }

    public MutableLookup(IEqualityComparer<TKey> keyComparer)
    {
        dictionary = new Dictionary<TKey, List<TValue>>(keyComparer);
    }

    // Property

    public IEnumerable<TKey> Keys
    {
        get { return dictionary.Select(kvp => kvp.Key); }
    }

    public IEnumerable<TValue> Values
    {
        get { return dictionary.SelectMany(kvp => kvp.Value); }
    }

    // Methods

    public ILookup<TKey, TValue> AsReadOnly()
    {
        return dictionary.SelectMany(kvp => kvp.Value, (kvp, Value) => new { kvp.Key, Value })
            .ToLookup(t => t.Key, t => t.Value);
    }

    public void Add(TKey key, TValue value)
    {
        if (dictionary.ContainsKey(key)) dictionary[key].Add(value);
        else dictionary.Add(key, new List<TValue> { value });
    }

    public void AddRange(TKey key, IEnumerable<TValue> values)
    {
        if (dictionary.ContainsKey(key)) dictionary[key].AddRange(values);
        else dictionary.Add(key, new List<TValue>(values));
    }

    public void RemoveKey(TKey key)
    {
        dictionary.Remove(key);
    }

    public void RemoveValue(TKey key, TValue value)
    {
        if (!dictionary.ContainsKey(key)) return;

        var list = dictionary[key];
        list.Remove(value);
        if (!list.Any()) dictionary.Remove(key);

    }

    public void RemoveWhere(TKey key, Func<TValue, bool> predicate)
    {
        if (!dictionary.ContainsKey(key)) return;

        var list = dictionary[key];
        list.RemoveAll(new Predicate<TValue>(predicate));
        if (!list.Any()) dictionary.Remove(key);
    }

    public void Clear()
    {
        dictionary.Clear();
    }

    public bool Contains(TKey key, TValue value)
    {
        if (dictionary.ContainsKey(key))
        {
            return dictionary[key].Contains(value);
        }
        return false;
    }

    #region ILookup<TKey,TValue>

    public bool Contains(TKey key)
    {
        return dictionary.ContainsKey(key);
    }

    public int Count
    {
        get { return dictionary.Count; }
    }

    public IEnumerable<TValue> this[TKey key]
    {
        get
        {
            return (dictionary.ContainsKey(key))
                ? dictionary[key].AsEnumerable()
                : Enumerable.Empty<TValue>();
        }
    }

    #endregion

    #region IEnumerable<IGrouping<TKey,TValue>>

    public IEnumerator<IGrouping<TKey, TValue>> GetEnumerator()
    {
        return dictionary
            .Select(kvp => new Grouping(kvp.Key, kvp.Value))
            .Cast<IGrouping<TKey, TValue>>()
            .GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    #endregion

    // Nested Types

    private class Grouping : IGrouping<TKey, TValue>
    {
        private TKey key;
        private List<TValue> list;

        public Grouping(TKey key, List<TValue> list)
        {
            this.key = key;
            this.list = list;
        }

        #region IGrouping<TKey,TValue>

        public TKey Key
        {
            get { return key; }
        }

        #endregion

        #region IEnumerable<TValue>

        public IEnumerator<TValue> GetEnumerator()
        {
            return list.GetEnumerator();
        }

        #endregion

        #region IEnumerable

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }
}

利用はご自由にどうぞ。パブリックドメイン、でいいのかな? あー、AsReadOnlyは新しいのに作り替えちゃってるのでAsじゃないですねえ。まあ、放置。あと、new Predicate()が果てしなくダサい。これ何とかならないのかな。delegateはFuncとAction以外は消滅しちゃえばいいのに。ジェネリクス以前のコレクションクラスと同じで、3.0以前の負の遺産だと何となく思ってる。

LinqとIEqualityComparerへの疑問

Distinctの引数はラムダ式でのselectorを受け付けてくれない。IEqualityComparerだけなので、抽出のためにわざわざ外部にIEqualityComparerを実装したクラスを作る必要がある。それって、面倒くさいし分かり辛いし、何でここだけ古くさいような仕様なのだろう。C#3.0っぽくない。しょうがないので、単純ですけど汎用的に使えるようなものを作ってみた。

// IEqualityComparer<T>の実装が面倒なのでセレクタ的なものはこれで賄う
public class CompareSelector<T, TKey> : IEqualityComparer<T>
{
    private Func<T, TKey> selector;

    public CompareSelector(Func<T, TKey> selector)
    {
        this.selector = selector;
    }

    public bool Equals(T x, T y)
    {
        return selector(x).Equals(selector(y));
    }

    public int GetHashCode(T obj)
    {
        return selector(obj).GetHashCode();
    }
}

class MyClass
{
    public int MyProperty { get; set; }
}

static void Main(string[] args)
{
    // このクラスのMyPropertyで重複除去したい
    var mc1 = new MyClass { MyProperty = 3 };
    var mc2 = new MyClass { MyProperty = 3 };
    var array = new[] { mc1, mc2 };

    var r1 = array.Distinct().Count();
    Console.WriteLine(r1); // 勿論2です
    // 比較用のIEqualityComparer<T>インスタンスを渡す
    var r2 = array
        .Distinct(new CompareSelector<MyClass, int>(mc => mc.MyProperty))
        .Count();
    Console.WriteLine(r2); // 1です
}

newするから、型を書かなければいけなくてね、記述量が多くて嫌だ。重たい重たい。C#3.0ってのは、もっとライトウェイトじゃなきゃダメなんだ。推論!型推論!しょうがないので、Distinctそのものに拡張メソッドを定義すれば……

public static class ExtensionMethods
{
    public static IEnumerable<T> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
    {
        return source.Distinct(new CompareSelector<T, TKey>(selector));
    }
}

var r3 = array.Distinct(mc => mc.MyProperty).Count();
Console.WriteLine(r3); // 1になる

ラムダ式で書けるようになる。この調子でIEqualityComparerを使ってるメソッドの全てに拡張メソッドを定義すれば問題なし。しかし準備が面倒。このことは、ForEachが搭載されないことと並ぶLinq最大の謎だと私は思っているのですけど、どうなんでしょうか。何か理由があるのかなあ。とても気になるのだけど……。

どう書く。ドキュメントコメント。

オブジェクト倶楽部、コーディング規約の会の「C# コーディング標準」の駄目なところ - 予定は未定Blog版

全く持ってその通りで、このコーディング規約はねえ、害悪を撒き散らしてるだけだよね……。さて、ところでドキュメントコメント。私は、つけます。(プロパティにはつけないかも、メソッドにはつける)。IntelliSense大好きっ子なので。但しほとんどの場合summaryのみを一行。

/// <summary>何たらメソッドです</summary>
public int Method(string input)
{
    // 色々処理ってことで。
    return 0;
}

こんな感じ。何で一行にするかといったら、上にぐちゃぐちゃXMLで書かれてるのって汚いし読みづらいぢゃん、ということもあるんですが主な理由はアウトラインに折りたたんでも解説が見えるから。

ね?///で出てくるものをご丁寧に全部埋めると、アウトラインに畳んだ時に内容が読めなくて困るのです。自分のソースの時は構わないんですが、人のソースを読むときはアウトラインを閉じたり開いたりを多用するので、かなり困ります。というわけで一行summaryお薦め。問題があるとすれば、手書きが面倒(///は一発で出てくるので)ということですが、この程度はキーマクロで記録してショートカットに割り振れば一発なので是非。あと、///で同時に出てくるparamsやreturnsて、大抵は面倒くさいだけであまり必要ないよね。「そういうの、書かなくていいんだよ、summaryだけなら面倒くさくないでしょ?」という心理的な安心感を得られるのも良いです。

ついでに、もう一つ、コメントの飾り枠についても。

// こういうコメントの入れ方は嫌!
class MyClass
{
    // -------------------------
    // Property
    // -------------------------

    public int MyProperty { get; set; }
}

// これでいいでしょ?
class MyClass
{
    // Property

    public int MyProperty { get; set; }
}

領域を目立たせるために囲むんでしょうけど、アウトラインで折りたたむとコメントがばっさり畳まれて見えなくなるんです。無意味な飾りはほんとーに、止めて欲しい。こういうのは一行でいいんです。別に機械に頼ればいいじゃない、機械があるんだから、機械には頼るべき、だからこそソースは機械に優しく書いて欲しいと私は思います。

そういえば#regionの話も出てましたけど、私は#regionはあんまし使いません。かなり好きくない。フィールドとか短いモノを畳まれると、逆にいちいち展開しなきゃならなくて面倒くさいんですよねー。というわけで、よほど長ったらしい状態で任意のグループ分けがしたい時には使いますが、フィールド、プロパティ、メソッド、みたいな分け方のために#regionを使いたくはない。

まあしかし、つまりはアウトラインってあまり使わないのかなあ。「アウトラインの中止」と「定義に折りたたむ」の二つでバサバサと畳んだり開いたりを連発するなんて、しないのかなあ。しないのかも……。周りの人にキモ!このソースキモ!一行summaryキモ!と思われないためにも、むしろ一行summaryをスタンダードにしようの会。コーディング規約ってつまりそういうことでしょうか、なんて独善的な!

勿論、Sandcastleとか絡むんなら別のお話です。

linq.js :: Next

音沙汰のないlinq.jsなんですが、現在はWindows Script Host対応を進めています。対応といっても数カ所書き換えるだけなんですけど、それに合わせてWSH用のラッパーライブラリを書いているので、それに時間を取られてる感じです。基本的にはほんと薄いラッパーで、列挙処理がlinq.jsに渡せるのでそっちで好きに処理してね、という方向なので機能面での補助は一切なく別にすぐ出来上がるというかもうほとんど出来てるんですが、IntelliSenseを聞かせるためのvs-doc書きに物凄く時間喰われています。とにかくIntelliSense命な私は、IntelliSenseが動かないものなんて書きたくない!なければ自分でIntelliSenseを書く!という意味不明な方向で頑張ってます。

画像の一枚目は一週間前のものなので、現在はW.ToLinqは廃止して、E.Fromで動くようになってます。何のこっちゃ。

このご時世、WSHなんて下火、これからはPowerShellだよねー、って感じですが、それでも私はWSHで頑張る! WSHでLinq書けるのかゴルァ、を合い言葉に。JavaScript好きだし。まあ、素のJScriptだとEnumeratorを被せなきゃいけなくて列挙処理がゴミで使う気になれないのは確かなのですが、そこをlinq.jsがあれば何とか出来るわけなので、全然WSHは現役で行ける、Windows7時代でも全然行ける、と思います、思いたいです。まあ、あとWindowsサイドバーガジェット(デスクトップガジェット)にも使えるので、もうちっと踏ん張っていきたいな、というところ。Web系で頑張るのは無意味なのでニッチを狙いだしたとかそういうことではありま、す。

ver 1.3.0.5 バグ修正とか動かない人用とか色々

動かない-、という人にログを出して貰ったお陰で、幾つか問題を潰せました。本当にありがとうございます。自分の環境で再現出来るエラーを潰せるのは当たり前。再現しないエラーを潰せないのは三流。というわけで三流な私はさっぱり分かりませんでした。分かってみれば、ああ、確かに問題だなーって感じなんですけどねえ。

変更内容は「オフライン→オンライン時投稿のチェックを外していた場合、同期に失敗する不具合を修正」だそうです。えー、こんな初歩ミス埋め込んでたのー、っていうと、そうです、はい、埋め込んでました、はい。げふんげふん。これは酷い。そう、「実績解除」だけ利用できればいいや、って人が利用出来なかったのです、なんだってー。あともう一つ、「GamerTagの入力を大文字小文字を区別しないように変更」です。今まで区別していたので、例えばnEUEcCとか入力すると、同期出来てませんでした。これはいかんですね。いかんので、区別しないようにしました。

ていうか、自分の環境で再現しない問題、じゃなくてただたんに例外ケースの見積もりが甘すぎなだけですなあ。もうちっと気を引き締めて書かないとダメですね。

追記

ver 1.3.0.6になりました。1.3.0.4以降は「+記号」が使えなかったっぽいので、それを直しました。ダメダメすぎて涙。

MinBy

        var minBy = list.Aggregate((a, b) => a.Age < b.Age ? a : b);
        Console.WriteLine(minBy);

お題はGroupBy、OrderByを使ってみる - waりとnaはてな日記のもの。ただの例題だと思うので(例だとGroupByを使う意味がないし)突っ込むとかそういうつもりはなく、そういえばRubyでいうmin_byってないなあ、と思ったので。Aggregateはとても素敵メソッドだと思うけど、いざ使おうと思うとあまり使うシーンがなくてとても悲しい。

ver 1.3.0.4 動かなくなったのでちょっと書き換えた

昨日の朝だか昼だかから動かなくなってたっぽいので、それを直しました。何で動かなかったのかよく分かってないんですが、ダメになった箇所だけは分かったので別のアプローチで、ということで。応急処置もいいとこで、まー情けないのですが、動けばいっか、ってことで。眠いし。ダメですかダメですねすみません。

今後だと、一応ダッシュボードのアップデートが8月に控えているらしいので、それでも引っかかってダメになるかもしれませんが、ダメっぽくなったのを捕捉次第、速やかに直したいとは思っているのでダラダラとお待ちください。

LinqとCountの効率

IEnumerableを受け取ってのReverse(), Count(), ElementAt(), Last()は普通に考えると先頭から舐めるので効率がよろしくない。じゃあLinqの実装はどうなってるかというとSystem.Core.dllを眺めると

// Reverse
ICollection<TElement> is2 = source as ICollection<TElement>;
// ElementAt
IList<TSource> list = source as IList<TSource>;
// Count
ICollection<TSource> is2 = source as ICollection<TSource>;
// Last
IList<TSource> list = source as IList<TSource>;

というわけで、IListを実装しているものなら、ちゃんと変換してくれているので大丈夫。Count()なんかは普通にLength/Countを使うから別にどうでもいいって話なのですが、Last()は[hoge.Length - 1]って書くのは嫌なので、こうして安心して使えると嬉しい話。まあ、こんなことは過去何回も話題に上っているのですが、一応自分で確認しておかないとね、ということで。

MSDNのCountの説明にはICollectionを実装してるとそれ使う、って書いてあるけど、Lastのページには何も書いていなくて困ります。内部でisだのasだのやってゴニョゴニョしてるのなら、そのことも全部記載して欲しいなあ。

といったことを何故突然というと、C#にもほしい ~rubyのeach_with_index~ - SEの勉強日記という記事を見かけたので。Count()のうえにElementAt()のラッシュというのは、IListだけならいいんですけどIEnumerableで拡張メソッドを作っているので、少しよろしくない。

public static class Extension
{
    public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var item in source)
        {
            action(item);
        }
    }

    public static void ForEach<T>(this IEnumerable<T> source, Action<T, int> action)
    {
        var index = 0;
        foreach (var item in source)
        {
            action(item, index++);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Enumerable.Range(5, 6).ForEach((item, index) =>
            Console.WriteLine("item={0} index={1}", item, index));
    }
}

という風にしたほうがいいと思われます。まあ、あと、new List()なんてやるぐらいなら.ToList()かしらん。

ver 1.3.0.3 / ちょっとした拡張メソッド群

Gears of War 2やCall of Duty : World at Warでステータスが取得出来なかった件を修正しました。GoW2は修正を確認出来ましたがCoD:WaWは持ってないので分からない。多分直ってると思うけど。あと、この修正の影響で他がダメになる可能性がなくもないです。ダメっぽいのを発見したら即直す、という方向で暴走さえしなければある程度はいっかー、的にゆるふわ気分でいるのですがダメでしょうかダメですね。

ところで、int.Parseが多くなって結構面倒くさいので文字列に拡張メソッド。

public static int ToInt(this string value)
{
    return int.Parse(value);
}

public static int ToIntOrDefault(this string value)
{
    int result;
    int.TryParse(value, out result);
    return result;
}

public static string Remove(this string input, string pattern)
{
    return Regex.Replace(input, pattern, "");
}

public static Match Match(this string input, string pattern)
{
    return Regex.Match(input, pattern);
}

public static string Replace(this string input, string pattern, MatchEvaluator evaluator)
{
    return Regex.Replace(input, pattern, evaluator);
}

RemoveとReplaceは既存のメソッド名のものに足してるので、別の名前のほうが良いかしらん。あと、Replaceは引数が被るので、ただの文字列置換は用意できなかった。まあ、_ => "hogehoge" といった具合に「_ => 」が増えるだけならそう手間でもないような十二分に手間のような。

totalGamerScore = document
    .Descendants("div")
    .First(e => e.GetAttribute("className") == "XbcProfileSubHead")
    .Descendants("strong")
    .Last()
    .InnerText
    .Match(@":\s*(\d+)/")
    .Groups[1]
    .Value
    .ToInt();

こんな風に書けます。これでスクレイピングも快適ー。ドット繋げまくれてバンザイしちゃう。ただまあ、デバッグはし辛いですね。ログ吐くメソッドを仕込んだりもいいんですが、もっと単純に、ラムダ式挟めばデバッガで止められるので

public static class Ext
{
    public static TResult Tap<T, TResult>(this T self, Func<T, TResult> func)
    {
        return func(self);
    }

    public static T Tap<T>(this T self, Action<T> action)
    {
        action(self);
        return self;
    }
}

RubyのTapみたいな、ということで。

ダミー置いて、その場で止められるようになる。それで作って、出来あがったらTapの行をCtrl+Xでゴミ箱ぽい。IEnumerableの場合は、普通にダミーのSelect置けばいいだけでしたね! 今、そのことに気付いた。うーん、あと.Groups.Cast<Group>().Skip(1).Select(g => g.Value) も、頻繁にあるあるなので、Matchに拡張メソッド埋めときますか。

ver 1.3.0.2 色々バグ修正

バグフィックス祭り! まずバグ報告があってそれを直して、つまりは普段と少し違う状態の時の処理が全く入ってなかったので、洗い出して処理を入れました。別に全く考えていなかったわけじゃないのですが、想定からの漏れが幾つか、というかたっぷりありまして……。具体的には「申請待ちのフレンドがいるとエラーになる」「ステータスが「退席中」の際にデータが正しく取得出来ない」「ステータスが「取り込み中」の際にエラーになる」です。不具合くせえええ、と思ったら遠慮無く言っていただけるととっても助かりますので、お願いします。

ver 1.3.0.1(XboxInfoTwit) / exception(アーケード)

全てのゲームタイトルで、オンラインマッチ時にステータスが途中参加可能な状態で実績取得が出来ず、タイトル名がおかしくなる件を修正しました。Xbox.comから文字列決めうちで切り出しているので、どんな文言が来るのかのパターンに漏れがありましたというか知らんかった……。(ちなみに現在、待機中でも同様の問題があったりするので、直します、次回に、他にもないかちゃんと調べてから)。こうしてバグ発覚な上に(しかもこの問題は以前からあったっぽいです、全然気づいてなかった)、自信満々に互換性向上!とか言っておきながら、動かなかったって人がいるみたいでごめんなさい。しかも以前のバージョンでは動いていたとなると、もう本当にごめんなさい……。

//

ところで話かわって、exceptionのアーケード版がロケテされてるらしい、新宿南口ワールドで7/9~15だそうだ。というわけで行ってきましたは二日目。実は初日にも行ったんですが、お上りさんなので間違えて東口のタイトーステーションに行って、置いてないなあ、と思ってた。いや、「タイトーステーション 新宿南口ゲームワールド店」自体には何度か行ったことはあるんですよ!でも、タイトーって印象がないというか真面目にセガだと思ってた(笑) 別に西口のセガのと思い違いしてたわけでもなく素で。それで、東口のほうにはインベーダー(=タイトー)の印象があったので、ほぅほぅ、あんなところも南口に含めるのか、とか思ってたとか思ってたとか。悲しい。ゲーセンの店名って意識したことないのよね、あまり行かない人なので。

アーケード版のゲーム内容ですが、普通にexceptionでした。ステージは若干アレンジされていたり、オリジナルステージがあったりなどですが、内容は全くそのままPC版のexception。処理落ちなどもなく、ヌルヌルと動いておりました。ただし破片量はノーマルか、それより少ないか、といった感じで若干の寂しさは否めない。

気になる操作方法はジョイスティックとボタン3つ。全方位STGでスティック一本ということで、ボタン一つが方向 転換用の固定ボタン(PC版のディレクション)に割り当てられていて、これがぶっちゃけ操作しづらい。残りのボタンはレーザーとレイ、二つを(一度離して)同時押しでカタパルト。このカタパルト動作が筋力貧弱な私には存外辛くて、終わり頃には腕が疲れちゃってた。カタパルト→カタパルト→カタパルトと連打するわけなので、ワンボタンで出したいなあ、と思いつつも、まあボタンあまってないししょうがないね。

そんなこんなだから、というわけじゃないのですが、あえなくPC版3面ボス(アーケードでは5面、だったかな?)で死亡してスコアは5位でした。ランキング一位は、PC版作者のi-saint氏のよう。i-saint氏が初見で?クリアしたようなので、なら私も!とか意気込んでいたのですがズタボロ。いてれーたんに普通にいいように弄ばれるという微笑ましさを見せつけました。

ステージ構成はPC 版->アーケードオリジナル->PC版……と交互のようです。PC版は若干短く簡素にアレンジ。アーケードオリジナルのほうは破片の少なさを補うためにギミック中心の構造になっていて新鮮で面白い。ボスも同様に飛び道具系、というか変形する。アーケードオリジナルボス2体目の倒し方が分からなかくてライフが半分減るまで無意味なことやってたり。新鮮なのはよいことです。

全体的には、昔のアーケード→ファミコン移植のような印象。おお、頑張ってる!という。破片量はしょうがないとしても、操作し辛いのはどうしたものかなー。今回私は3ボタン使う上級者向け操作を選んだので、2ボタン操作の初心者向け操作だと印象違うかもしれません。そういえば、エフェクトも弱めなのでカタパルトの爽快感が薄かったような気がする。回りを破片で囲まれてピンチをカタパルトで一気に突破!的なのが、画面が真っ白になって抜け出せたのか何なのかわけわからないというか自機が本当に白に埋まって見失う、のはどうなのだろう、慣れかな?ジオメトリとかも見失うって言われるわけだし。

とはいえ普通にexceptionだったので普通に面白いと思います。オリジナル面も良い感じだし。PC版に比べてどうよ?と言われると完全にノーコメント(すみません)ですけど、ジョイスティックでやるのも新鮮、ということで遊びに行ける人は遊びに行くと良いやも。とりあえず私は途中で死亡したのが悔しいので、明日も行きます、というかロケテ中にはクリアしたい。(それにしても音楽も効果音も全く聞こえなかった、周囲の音うるさすぎ、私はひなびた田舎のゲーセンが好きですよ(笑))

あと、せっかくなので明後日、7/12の日曜日の22:00頃からUstreamでexceptionとexception conflictを実況プレイしようと思うので良ければ見てやってください。

ver 1.3.0.0 ロジック変更/OAuth対応

公開から半年以上経って、何故かここ数週間で利用者が増えてきたようで同時に動かない報告も目にするようになって嬉しくも悲しい。なので、今回ロジック部分を完全に書き換えました。これで色々な環境でも動作するようになった、はず、です。以前に試して動かなかったという方は、再度試していただけると嬉しいです。ついでに取得処理も軽量化しました。真面目に10倍ぐらいは速くなってます(今までが酷過ぎただけなんだけどね!)。そんなこんなで手を入れた部分が多いので、(ただでさえ不安定との定評があったのに)安定性はむしろ下がった、かも……。

それと、認証がパスワード方式からOAuth方式に変更になりました。ユーザーにとってOAuth対応によるメリットは、クライアントアプリではぶっちゃけ別にないどころかむしろ色々な不都合のほうが多いのですが私がやりたかった、ということで勘弁してください。投稿にfrom XboxInfoTwitってクライアント名が表示されるようになって嬉しいのです、にひひ。自動アップデート経由の方は、認証を一度済ませなければならないので手間です、すみません。

あ、で、ver1.2からそうなのですが、現在はAPIは一切利用していませんので遅延はありません。Xbox.comのステータス反映速度(1分以下で追随しているようです)で処理していますので、原理的には他の同種のアプリと同じです。が、多機能風味なため、巡回ページ数が多いのでチンタラ通信してたりする時があって、そのせいで反映が遅いという印象を持たれるかもしれません。今回の軽量化で若干印象は変わると思いますがそんなに変わらないかもしれません。その他、投稿の法則やFAQは同梱の説明書きを見てください。

↑通信内容はこんな感じです(Fiddlerで観測)。30秒かかりました。結構長いけれど、Xbox.comが重いサイトだからねえ……。まずフレンドリストの取得、そこからフレンドのフレンドに飛んで自分のステータスを取得(取得できない場合は取得出来るまで次のフレンドに飛ぶ)、トップページに飛んでプレイ中ゲームのURLを取得、プレイ中ゲームのページから実績を取得。という流れです。これがステータス取得の最短パスじゃないかと思っています。自分のステータスがFriendsOfFriend経由でしか見れないというのが意味不明で困るところ。

そういえばでどうでもいいんですが、タスクバー右クリックに「ゲイツポイントを買う」が追加されてます。アフィです、アフィ。んーと、ノーコメント。

Linqで文字のバイト単位でグループ分け

var input = "e4bba5e4b88be381aee69687e5ad97e58897e381af5554462d38e38292e69687e5ad97e382a8e383b3e382b3e383bce38387e382a3e383b3e382b0e5bda2e5bc8fe381a8e38199e3828b3136e980b2e695b0e381aee38390e382a4e38388e58897e381a7e38182e3828be38082";
var count = 0; // 外部にカウント用フラグ変数を置く
var words = Regex.Matches(input, "..")
    .Cast<Match>()
    .Select(m => Convert.ToByte(m.Value, 16))
    .Select(b => Convert.ToString(b, 2).PadLeft(8, '0'))
    .ToLookup(s => (s.StartsWith("10")) ? count : ++count);
Console.WriteLine(words.Count);

前回(たった10時間前ですが)の続き。16進数バイト列で表現されたUTF-8の文章の文字数を数えろ、というお話でした。せっかくなのでビット見てうんたらかんたら、のほうでも。律義に2進数に変換しながら分類。例えばwords[0]は[11100100,10111011,10100101]になる。こんな感じで41個分グルーピングされています。.Keyで何文字目かが取得出来るところが地味に素敵でいて微妙に混乱を招く(配列の何番目、ではない)。 で、まあしかし、場外にフラグ変数を置くことで何でもこなせるけれど、それってアリなの?という疑問はある。Linq連鎖の場外に変数を置くなんてグローバル変数的な害悪です。かどうかは分からないけれど、イマイチだとは思う。

場外に置かないで分類するとしたら、どうすればよいかなー。うーん。思い浮かばない。

var text = words.Select(g => g.Select(s => Convert.ToByte(s, 2)).ToArray())
    .Select(bytes => Encoding.UTF8.GetString(bytes))
    .Aggregate(new StringBuilder(), (sb, s) => sb.Append(s))
    .ToString();
Console.WriteLine(text);

こっちはどうでもいいんですが、文章への復元はこんな感じで。面倒くさー。何個Select使えば気が済むんだ、みたいなみたいな。でもLinq使わないとそれこそもっと面倒くさい気はする。とりあえず=>かわいい。=>のかわいさは異常。=>がかわいいすぎて萌え死ぬ。

Prev | | Next

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(.NET)
April 2011
|
July 2026

X:@neuecc GitHub:neuecc

Archive