Archive - 2009.08

3桁到達

開発者用のOAuth管理ページで認証ユーザー数が見れるのですが(「誰が」までは分からないので安心してください)、いつのまにやらユーザー数が100を超えていました。三桁!奇跡的ですね。はてな同期云々とかフォトライフ云々は壊滅的な状態なので。世の中そんなものです。ついでにこのサイトのアクセス数も壊滅的だったりはします。成り行きでプログラミング系サイトに転換してから半年、以上は経つ感じですが、伸びもせず縮みもせず、ずっと低調をキープ。サイトの内容がガラッと変わったのにアクセス平均が変わらないってのも面白いですけど。検索サイトからのアクセスがほとんどなので、検索キーワードが入れ替わって、でも流入人口は変わらずという。あー、まあ、少し増えた、かな。つまりかわりに常連的な人口が減った、と。おお、虚しい!悲しい!RSSリーダー登録数も伸びないしね。しょぼーん。

ゲーム系サイトへは、XNAで返り咲きしたいとこっそり思ってます。XNAは使用言語がC#だからね。C#好きーなのです、私。積みタスクを全部消化したらXNAやりたいんですが中々どうして……。 そういえばインディーズゲーム(XBIG)を完全スルー状態なのはぶっちけ(略)

fromにクライアント名が出るようになってからは、googleのサイト検索で利用具合が見つかるので、毎日24時間以内の結果を眺めていたりします。見ていて思うというか教訓は、デフォルト設定大事ってことでしょうかね。カスタマイズせずそのまま、カスタマイズする場合も、デフォルトを残しつつ細部を変える、という感じなので、デフォルトの投稿文はちゃんとしたものを用意しなきゃダメなんですね。当たり前といえば当たり前なのですけど、この辺はてなついったー同期ツールは大失敗していて、どうせカスタマイズするだろうと踏んで、書式のサンプルとばかりにゴテゴテのものをデフォルトにしてしまったので……。反省。

あと、デフォルトでは「プレイ中タイトルの状況が変わった時の投稿」はオフにしているのですが、意外とこれをオンにする人が多かったのも驚き。これオンにすると物凄い勢いで投稿されるんですよ。更新間隔を5分にすると、例えばGoW2のHordeすると、WAVE44,45,46…と、全部のWAVE投稿するんじゃないか、最新50個の投稿が全部XboxInfoTwit経由になってますが大丈夫?みたいなことになる。こういう滅茶苦茶なことが出来るのはローカルで動くツールならでは、なのですが(ウェブサービス系じゃあ、ちいと無理ですね、秋のTwitter対応が仮に実績やプレイ状況の投稿に対応するとしても、ここまでの連投は無理かと)フォロワーの目からどうなのか、というと、まあ、分からにゃい。いや、本人の満足が一番だと思いますよ。一日のプレイ後に投稿を眺めると、状況の変化がよく見えて結構楽しかったりはします。お薦めはしませんけどお薦め。別アカでやるなら何も問題なくお薦め。

JavaScriptで要素追加するやり方

ド素人がjQueryとprototype.jsではどう書くのかな、と思っただけです。メジャーな両者ですが実はまともに使ったことがないのです。困ったことに。しょうがないので見よう見まねで書く。

<!-- このselectにoptionを一個追加する -->
 
<select id="selectID">
    <option value="1">hugahuga</option>
</select>
 
<script type="text/javascript">
 
    // 素のJavaScriptその1(古臭いというか微妙な……)
    var option = new Option("要素", "属性");
    var select = document.getElementById("selectID");
    select.options[select.options.length] = option;
 
    // 素のJavaScriptその2(これはダルい)
    var option = document.createElement("option");
    option.setAttribute("value", "属性");
    option.appendChild(document.createTextNode("要素"));
    document.getElementById("selectID").appendChild(option);
 
    // みんな大好きjQuery
    $("<option>").attr({ value: "属性" }).text("要素").appendTo("#selectID");
 
    // 何だかんだで好きなprototype.js
    var option = new Element("option", { value: "属性" }).update("要素");
    $("selectID").insert(option);
 
    // linq.js + linq.xml.jsの関数型構築
    var option = X.Elem("option", X.Attr("value", "属性"), "要素");
    X.ID("selectID").Add(option);
 
</script>

素のJavaScriptその1はねーよ、というわけで、その2をいかにスマートにやるかという話。だと思う。jQueryのappendToが合理的というか便利なのは分かるけど、キモく感じてしまう。んで、どれが好きかっていたら、当然自分で作ってるlinq.jsのが一番好きですよ(笑)

// linq.js + linq.xml.js
var options = E.RangeTo(1, 12).Select(function(i)
{
    return X.Elem("option", X.Attr("value", i), i + "月");
});
X.ID("selectID").Add(options);
 
// prototype.js
var options = $R(1, 12, false).map(function(i)
{
    return new Element("option", { value: i }).update(i + "月");
});
var elem = $("selectID");
options.each(function(e) { elem.insert(e) });

X.Elem()もAdd()もLinqオブジェクト/可変長配列を受け取れるので、まとめてドバーっと追加が結構楽かな、と思います。eachとかじゃなく、そのまんま追加出来るってのが大事。上の例だと、prototype.jsではmapでoptionsを作らずそのまんまeachでinsertしちゃえばいいぢゃん、というのはそのとーりなんですが(2回もループ回ることになるしね、あ、linq.jsのは遅延評価しているのでループはAddで呼び出される時の1回しか回りません)、配列(的なもの)が既にある状態ってのは、結構ありますよね?

と、何故か突然アピールしてますがlinq.xml.jsは作りかけで放置しているので足りない関数がいっぱいあるんですけどね!

enumの日本語別名とか三項演算子ネストとか

enumのToStringで日本語名になって欲しいというケースはとてもあるある。enum に文字列の属性を: いげ太のブログ2008-01-24 - 当面C#と.NETな記録の記事を見て、今まで拡張メソッドで処理することが多かったのですが、やっぱり見た目は属性のほうがスッキリするなあ、と思った。記述が本体と離れないのが良いですよね。処理速度とか、そんなに頻繁に繰り返し繰り返し呼ぶものでもないしリフレクション上等!それ気にしたらSerializeとか出来ない!とか思ったので、基本は属性で処理することにしてみた。

enum Fruit
{
    [AliasName("ブドウ")]
    Grape,
    [AliasName("リンゴ")]
    Apple,
    [AliasName("オレンジ")]
    Orange
}
 
static void Main(string[] args)
{
    var fruit = Fruit.Orange;
    Console.WriteLine(fruit.ToAliasName());
}

こんな感じに定義してこんな感じに使う、と。定義もスッキリ、呼び出し時もToString的に拡張メソッドでスッキリ。

[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class AliasNameAttribute : Attribute
{
    public string AliasName { get; private set; }
 
    public AliasNameAttribute(string aliasName)
    {
        AliasName = aliasName;
    }
}
 
public static class Ext
{
    // どうしてもメソッドチェインを崩したくない人用
    public static T ThrowIf<T>(this T value, Func<T, bool> predicate, Exception exception)
    {
        if (predicate(value)) throw exception;
        else return value;
    }
 
    public static string ToAliasName(this Enum value)
    {
        return value.GetType()
            .GetField(value.ToString())
            .GetCustomAttributes(typeof(AliasNameAttribute), false)
            .Cast<AliasNameAttribute>()
            .FirstOrDefault()
            .ThrowIf(a => a == null, new ArgumentException("属性が設定されていません。"))
            .AliasName;
    }
}

ドットが縦にならんでると何だか楽しい。というわけで、チェーンを崩さずに例外を放り投げるための拡張メソッドThrowIfを用意してみた。nullが邪魔!邪魔!nullが出現するせいで一個変数を置いてnullチェックかまさなきゃいけない!というシーンは多いので、predicateじゃなくてnull限定決め打ちでも良いぐらいかもかも。とにかくnull撲滅。まあ、この場合はFirstOrDefaultじゃなくてFirstにすれば、Firstが例外を吐いてくれるのですけど、一応ちゃんとメッセージ用意したいとか、ありますよね?ね?

// 三項演算子のチェーンを崩さないためにダミーの型を返して例外を投げる
static T Throw<T>(Exception exception)
{
    throw exception;
}
 
static string Test(Fruit fruit)
{
    return (fruit == Fruit.Apple) ? "あぷる"
         : (fruit == Fruit.Grape) ? "ぐれえぷ"
         : (fruit == Fruit.Orange) ? "おれんじ"
         : Throw<string>(new ArgumentException("引数ダメぽ!"));
}

三項演算子を延々とネストしてコロンを前置にするのが好き、と以前に書いたのですが、そうすると、最後にdefault的なものを書く必要があって困る。”"とか0とかでお茶を濁さずに、例外を吐きたいのですが、ネスト三項演算子では例外を吐けない。困った困った。というわけでThrowという補助メソッドを用意してみた。ただ例外を吐くだけメソッド。おー。これでもう大量にネストしても大丈夫!万歳!三項演算子でネストしよう!ついでにネスト時のコロンは前置にしよう!の会。

使用しないでください?

ところで、EnumのToStringはIntelliSenseに使用しないでください、が出てきてビビる。んが、よくよく眺めてみると……

public override string ToString();
[Obsolete("The provider argument is not used. Please use ToString().")]
public string ToString(IFormatProvider provider);
public string ToString(string format);
[Obsolete("The provider argument is not used. Please use ToString(String).")]
public string ToString(string format, IFormatProvider provider);

ObsoleteなのはIFormatProviderが絡んでるものだけで、普通のToStringは使用していいんでないのん?文字列の列挙体←ここのコメント欄が白熱していたのを見て、どうなのかなー、と思っているのですが、どうなんでしょうか。いやほんと。

WSH用にCOMのIntelliSenseを自動生成する

本題、の前にJavaScript用のIntelliSenseの作成方法について。以前、linq.jsにIntelliSense用ファイルを追加した時に利用法を書きましたが、今回はvsdocの作成方法を、ざっと書きます。まずVS2008 SP1とパッチを適用する。hoge.jsに対しhoge-vsdoc.jsを用意するとhoge.jsのかわりにhoge-vsdoc.jsがIntelliSense用に読み込まれる。IntelliSenseでVSが見ているのはメソッド名と引数だけなので、コードの中身はなくてもいい。例えば本来は存在するがprivateメソッドのつもりのものは、vsdoc.js側に書かなければIntelliSenseには表示されない。C#と同様に関数にはXMLドキュメントコメントを記述することが出来る。記述箇所はC#とは異なり開始の波括弧の直下。ドキュメントコメントのタグで現在機能しているものはsummary, params, returnsのみ。特に重要なのはreturns typeで、これにprototypeが存在する関数(ようするにクラスですなー)を指定することで戻り値の型をVSに認識させ、IntelliSenseを働かせることが出来る。ちなみに、ただのオブジェクトだと認識してくれない。

function Hoge(aaa, bbb)
{
    /// <summary>hogehogehogehoge</summary>
    /// <param name="aaa" type="String">a!a!a!a!a!</param>
    /// <param name="bbb" type="Optional:Boolean">b?b?b?b?b?</param>
    /// <returns type="Number"></returns>
}

基本はこんな感じ。引数の省略や、型が目で見て分かるので大分書きやすくなります。 で、いつぞやかに作成中とか言っていた通りにlinq.jsをWSH対応させようとしているわけですが、IntelliSense書きの量が思ったよりも膨大なわけです。実は最初は普通に手書きしてました。一応勉強も兼ねて書写みたいなノリで。ですが、あまりにも手間かかりすぎるので自動生成することにしました。こんなの人力でやるもんじゃないですよ。微調整はどちらにせよ必要なのですか、大枠だけでも書きだされていると物凄く楽になる。

static void Main(string[] args)
{
    // add reference and replace dll path
    var assembly = Assembly.LoadFrom(@"Interop.IWshRuntimeLibrary.dll");
    var types = assembly.GetTypes();
 
    var enums = types
        .Where(t => t.IsEnum)
        .OrderBy(t => t.Name)
        .Select(t => string.Format("{0}: \r\n{{\r\n{1}\r\n}}",
            t.Name,
            Enum.GetNames(t).Select(s => string.Format("\t{0}: {1}", s, (int)Enum.Parse(t, s))).Join(",\r\n")));
 
    var classes = types
        .Where(t => t.IsClass)
        .Select(type =>
        {
            var bindingFlag = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
 
            var properties = type.GetProperties(bindingFlag)
                .OrderBy(pi => pi.Name)
                // .Select(pi => string.Format("{0}: {1}", pi.Name, pi.PropertyType.Name
                .Select(pi => string.Format("{0}: null", pi.Name));
 
            var methods = type.GetMethods(bindingFlag)
                .Where(mi => !mi.IsSpecialName && mi.Name != "GetEnumerator")
                .Select(mi => new { mi.Name, Parameters = mi.GetParameters(), ReturnType = mi.ReturnType.Name })
                .OrderBy(t => t.Name)
                .Select(t => string.Format("{0}: function({1})\r\n{{\r\n{2}\t/// <returns type=\"{3}\"></returns>\r\n}}",
                    t.Name,
                    t.Parameters.Select(pi => pi.Name).Join(", "),
                    t.Parameters.Select(pi => string.Format("\t/// <param name=\"{0}\" type=\"{1}{2}\"></param>\r\n",
                            pi.Name,
                            (pi.IsOptional) ? "Optional:" : "",
                            pi.ParameterType.Name.Replace("Void", "void").Replace("Int32", "Number")))
                        .Join(""),
                    t.ReturnType.Replace("Void", "void").Replace("Int32", "Number")));
 
            var result = properties.Concat(methods).Join(",\r\n");
            return string.Format("{0} = function() {{ }}\r\n{0}.prototype =\r\n{{\r\n{1}\r\n}}",
                Regex.Replace(type.Name, "Class$", ""),
                result.Split(new string[] { "\r\n" }, StringSplitOptions.None).Select(s => "\t" + s).Join("\r\n"));
        });
 
    var name = assembly.GetName().Name.Split('.').Last();
    File.WriteAllText(name + "_enum.js", string.Format("{0}Enum =\r\n{{\r\n{1}\r\n}}", name,
        enums.Join(",\r\n").Split(new string[] { "\r\n" }, StringSplitOptions.None).Select(s => "\t" + s).Join("\r\n")), Encoding.UTF8);
    File.WriteAllText(name + "_class.js", classes.Join("\r\n\r\n"), Encoding.UTF8);
}
 
static string Join<T>(this IEnumerable<T> source, string separator)
{
    var index = 0;
    return source.Aggregate(new StringBuilder(),
            (sb, o) => (index++ == 0) ? sb.Append(o) : sb.AppendFormat("{0}{1}", separator, o))
        .ToString();
}

参照設定で対象のCOMを読み込んで、一旦ビルド。生成されてるInterop.hoge.dllを読み込んで解析、という流れをとってみました。もっとマシなやり方があれば教えてください。コードは、んーと、string.Formatがクドくて見難いですね! 最初はVSのフォーマッタに後で手動でかければいいや、と思ってたのを、出力状態でちゃんとフォーマットされてるようにと継ぎ接ぎしてたら酷いことに。一回文字列にしたものを改行でバラして再生成とか笑えない。こういうの、HTML/XMLが対象なら何も考えなくてもLinq to Xmlで綺麗に書けるのになあ……。 それ以外はまぁ、こんなものかしらんって感じでしょうか? リフレクションが絡むとLinq大活躍。Selectがネストしまくるのでクエリ構文の出番はない。リフレクションは掘り進める都合上、ネストが深くなるのでLinqがなかったら、と思うとゾッとします。最近はforの二重ループですらウエエ、とか思ってしまうので。

IWshRuntimeLibraryEnum =
{
    IOMode:
    {
        ForReading: 1,
        ForWriting: 2,
        ForAppending: 8
    }
}
 
Folder = function() { }
Folder.prototype =
{
    Name: null,
    ParentFolder: null,
    Drive: null,
    CreateTextFile: function(FileName, Overwrite, Unicode)
    {
        /// <param name="FileName" type="String"></param>
        /// <param name="Overwrite" type="Optional:Boolean"></param>
        /// <param name="Unicode" type="Optional:Boolean"></param>
        /// <returns type="TextStream"></returns>
    }
}

一部抜粋ですが、こんなようなデータが出力されます。プロパティが全部nullなのは、ここに未指定のものが記述されているとIntelliSenseがエラー起こしてしまうから。例えばDrive: new Drive()で戻り値の型指定が可能といえば可能なのですが、出現位置の上下が問題になってくるので結構面倒くさい。ようはFolderよりも上にDriveがあれば関数が存在するので大丈夫だけど、下にあれば存在しないのでエラー。当たり前といえば当たり前なのですが、ダミーでのIntelliSense作りとしては面倒な問題でして。 この辺は素直に諦めて全部nullで型連鎖を止めてしまうのがお気楽といえばお気楽。あと、Dateはnew Date()にしてもDate.prototypeにしても型指定出来なかったりする問題もある。これは今のところ対処不能。

そんなわけで、linq.js + WSHはわりと順調に作成中なので期待しないでも待っててください。IntelliSenseというだけじゃなく、JScriptではバイト配列が扱いにくいので、扱いやすく出来るような補助メソッドを用意したりとか色々やってたらいつになっても終わらないー。例としてWSHでMD5を作る、とか。.NET Frameworkを通しているのでSHA1でも何でもいけます。これでWSHでwsse認証通してAtomAPIで投稿、とか出来ますね。まあ、もうC#でいいぢゃん、って気がかなりしなくもないですけど、うーん。C#4.0からdynamicが追加されるからCOM連携も楽になるしねえ。ただサイドバーガジェットに使うとか、JavaScriptの用途はまだまだあるもん!(これもSilverlightでやれば?って話がなくもないので、うーん)

簡易文字列置換

static void Main(string[] args)
{
    // {}で括った部分を(ラムダ式/匿名型)を使って置換する
    var input = "食べ物は{tabemono}で飲み物は{nomimono}";
    var r1 = input.Replace(new { tabemono = "たこ焼き", nomimono = "コーラ" });
    var r2 = input.Replace(tabemono => "たこ焼き", nomimono => "コーラ");
}

前も書いたというかJavaScriptでやりましたが、それのC#移植。簡易テンプレート的な文字列置換。ラムダ式版と匿名型版の二つでやってみました。ラムダ式だと、見た目が少し短くて何となく格好良いのですが、変数名の入力時にIntelliSenseが動いてしまうので結構鬱陶しかったり。匿名型のほうはObjectを受け取る関数なので、危なっかしいのが嫌ですね……。

static class Extensions
{
    // 正規表現の|でキーを連結して辞書から置換
    private static string Replace(string input, Dictionary<string, string> dict)
    {
        var pattern = string.Format("{{({0})}}", dict.Select(kvp => Regex.Escape(kvp.Key)).ToJoinedString("|"));
        return Regex.Replace(input, pattern, m => dict[m.Groups[1].Value]);
    }
 
    /// <param name="anonymousType">{pattern = "replacement"}</param>
    public static string Replace(this string input, Object anonymousType)
    {
        var dict = anonymousType.GetType()
            .GetProperties()
            .ToDictionary(pi => pi.Name, pi => pi.GetValue(anonymousType, null).ToString());
        return Replace(input, dict);
    }
 
    /// <param name="exprs">pattern => "replacement"</param>
    public static string Replace(this string input, params Expression<Func<Object, string>>[] exprs)
    {
        var dict = exprs.ToDictionary(e => e.Parameters[0].Name, e => e.Compile().Invoke(null));
        return Replace(input, dict);
    }
 
    // 文字列連結補助メソッド(これないとシンドイので)
    public static string ToJoinedString<T>(this IEnumerable<T> source, string separator)
    {
        var index = 0;
        return source.Aggregate(new StringBuilder(),
                (sb, o) => (index++ == 0) ? sb.Append(o) : sb.AppendFormat("{0}{1}", separator, o))
            .ToString();
    }
}

速度は、匿名型版のほうが圧倒的に速いです。100000回の繰り返しが匿名型だと2秒なのに対してラムダ式版は30秒かかった。Compile().Invokeがアレなんですかねえ。ちなみに、string.Formatでふつーにやった場合は0.03秒でした。まー、でも、メール雛型の置換に、とかちょっとしたことにそこそこ便利に使えるかなー、とは思いつつもっと効率考えてちゃんと組んだ方がいい気もする。

ラムダ式から取り出すのはC# 3.0 における疑似 Map 生成リテラル - NyaRuRuの日記から、匿名型はprototype.jsとかPHPのstrtrとか色々。あとややニッチな Anonymous Types の使い方をまとめてみる (C# 3.0) - NyaRuRuの日記です。

ver 0.0.0.3

拡張子が大文字だとアップロード出来なかったので直しました。XboxInfoTwitの時も同じのやってたのにまたかよって感じです。拡張子判定部分は、ちゃんとIgnoreCaseにしたしあれえ?と思ってたんですがContentType作るところで漏れがあって、ウッカリ。てへ。

まあ、そんなこんなでちゃんと一眼レフも買いました。わざわざこのためだけに!neuecc’s fotolife 。それで、しかし撮るものが悲しいほど無いんですよね、やっぱり。とはいえ、自分で撮って自分で上げてかないと、何をどうすれば良くなるのか分からないので、室内写真で栄える何かを探し中です。現在は多肉植物でも育てようかな、と思ってるんですがどうでしょうかねえ。

ちなみに現在までのDL数は余裕で一桁。べ、別に悲しくないもん! そういえばこの半自動ってぶっちゃけ機能的にいらなくね?むしろフォルダ監視で自動化したほうがよくね?とも思ってきたので、まあ、そのうち。そのうち。

ネストした三項演算子の書き方

javaプログラムの組み方がわかりませんorz 無理な課題を学校で出されました(´・_・… - Yahoo!知恵袋

「小学生向けの四則演算の計算練習のプログラムを考える」……? 回答のJavaコードが長くてよくわからにゃい。ぽりもふぃずむって何?

これでおk。最近、三項演算子の:は後置が普通だけど、前置にするとF#のパターンマッチみたいで格好良くね?とか思うので、開き直って前置にしたいです。これはコーディング規約に載せるべきですね(笑) あと三項演算子の、うにゃ、条件演算子の条件は必ずカッコで括る派。

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以降は「+記号」が使えなかったっぽいので、それを直しました。ダメダメすぎて涙。

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for Visual Studio and Development Technologies(C#)

April 2011
|
July 2018

Twitter:@neuecc
GitHub:neuecc
ils@neue.cc