2013年4月25日木曜日

Comparison デリゲートを利用した IComparer の実装クラス

訳のわからないタイトル。


事の起こりは、ジェネリックの「SortedDictionary」クラスを使おうと思ったこと。

デフォルトの比較ルールだと、希望する順番にソートされないので、比較関数を置き変えたかった。そのためにはどうするのか?MSDNを引いてみると、



SortedDictionaryのコンストラクタのオーバーロードを確認しても、IComparer<TKey>を指定するものはあっても、Comparison<TKey>を指定できるものはない。えー?このためにクラス一個作んないとダメってことかい?めんどくせ。

List<T>とかのSortメソッドには、Comparison<T>もIComparer<T>もどっちも引き渡せるようになっているのに…。.net 2.0の時代ならいざ知らず…、ちょっと片手落ちじゃないすか?というか、手を入れ損なったのかなぁ。

ま、無いものはしょうがない。とはいえ、クラスを作るにしても似たようなものを何度も作りたくはない。なので、『Comparison<T> デリゲートを利用した IComparer<T> の実装クラス』を作り、汎用的に使いまわせるようにしようと考えた次第。

しかし、ここで悩んだのはそのクラス名。汎用的なIComparer実装クラスなので、「Comparer」クラスにしたかったのだけど、「System.Coolection.Generic.Comparer」クラスが既に存在しているので却下。

悩んだ挙句、「ComparisonComparer<T>」にしました。頭痛が痛いような名前。でもまぁ、名は体を表していることには違いないし、なによりIntellisenceで「Compar...」でリストアップされるので、まぁ、使い勝手は悪くないんじゃないかと思う。

およそ実装はこんな感じ。

class ComparisonComparer<T> : IComparer<T>
{
  private Comparison<T> comparison;

  public ComparisonComparer(Comparison<T> comparison)
  {
    this.comparison = comparison;
  }

  public int Compare(T p1, T p2)
  {
    return comparison(p1, p2);
  }
}

念のため、ジェネリックじゃないIComparerも実装しておいたほうがいいかも。そして、こう使うと。

var dic = new SortedDictionary<string, int>(
    new ComparisonComparer<string>((p1, p2) => string.Compare(p1, p2)));

正直、それほど使い道があるわけじゃない(ざっと見た限り、SortedListやSortedSetではIComparer<T>が必要なので、使い道はありそうだけど、それくらい…)。だけど、いちいちクラス一個作るよりはいいでしょ。

2013年4月22日月曜日

Lambdaの小ネタ / ハッシュ文字列(フィンガープリント)の生成

Lambda の小ネタ / 文字列から特定種類の文字以外を除去したコピーを作る」でも使いましたが、string.Concat(IEnumerable<T>)。これを知ったのはつい最近。が、これが大変使い勝手がよく、昔書いたコードのいろいろを書き直したくなる衝動に駆られます。

たとえば、バイナリデータのMD5ハッシュ文字列(いわゆるフィンガープリント)を生成するのに、こんなコードを書いてました。
static string MD5OldStyle(byte[] tgt)
{
  var sb = new StringBuilder();
  using (var md5 = MD5.Create())
  {
    foreach (var b in md5.ComputeHash(tgt))
    {
      sb.Append(b.ToString("x2"));
    }
    return sb.ToString();
  }
}

MSDNのサンプルもこんなコードになっています。しかし、ここにstring.Concatのメソッドを使うと、
static string MD5NewStyle(byte[] tgt)
{
  using(var md5 = MD5.Create())
  {
    return string.Concat(md5.ComputeHash(tgt).Select(b => b.ToString("x2")));
  }
}

おんなじことがこれでできます。なんて素敵な!

2013年4月19日金曜日

今週のモーニング

モーニングも楽しみにしている作品が多い。

そこに加えて山崎紗也夏と東村アキコが新連載待機中らしい。そりゃ、楽しみ。

「主に泣いてます」が終わったばっかりのような気がしなくもないけど。

Lambda の小ネタ / 文字列から特定種類の文字以外を除去したコピーを作る

ラムダ式を使ったちょっとした小ネタ。

『文字列から、数字以外を除去したコピーの文字列を作る』処理を書きたい。






.net Framework 4.0以降であれば、入力文字列を「input」、出力文字列を「output」とすると、こんな風に書けます。なにしろstring は 「IEnumerable<char>」ですから!

var output = string.Concat(input.Where(c => Char.IsDigit(c)));

シンプルでいいでしょ。

2013年4月18日木曜日

ありがちな。

誰が作ったものか知りませんが、お気に入り。いやいや、あくまでパロディですよ。
でも、戒めとしても。ね。


ネタがないなぁ



そんなときの必殺技。ねこ写真!!

2013年4月11日木曜日

Sandcastleの日本語パッチを見つけました!

いままでいろいろ探してみても、結構古めのバージョンのものしかなく、仕方なく自分で最低限必要なところだけを翻訳して「オレオレパッチ」を作って使ってました。

SHFBの「1.9.6.0」対応バージョンがこちらです。

https://code.google.com/p/sandcastle-help-file-builder-japanese-help-file-pack/

作者さん(tuedaさん)のブログはこちら。

tuedaの日記 - 「SandCastleすごくいい!!」

中身を見てみると、全部のプレゼンテーションタイプの、翻訳可能な個所すべてが日本語化されてます!すばらしい!これはいい!

さっそく使わせてもらおうと、まずはSHFBをバージョンアップさせようとサイトに行ったら、「1.9.7.0」が出てました…。…とりあえず問題なく使えました。

ちなみに、僕は「VS2005」のプレゼンテーションタイプが一番好みです。シンプルで見やすい。

『なんでこれコンパイル通るんだっけ?』

あらかじめ言っておくと、この話にオチはありません。なんかすいません。

深く考えもせず、こんな感じのコードを書いていました。

public void SetValidChars(CharType type, string option)
{
  var validChars = new List<char>();

  if (type == CharType.AllAscii)
    validChars.AddRange(Enumerable.Range(0x20, 0x7f - 0x20)
                                  .Select(n => Convert.ToChar(n)));
  else if (type == CharType.NumericOnly)
    validChars.AddRange("0123456789");
  else if (type == CharType.Optional)
    validChars.AddRange(option);

  // この後、validCharsを使ってごにょごにょ
}

コンパイルかけて、UTも通して、さてと、とコードを眺めていたら、文字を眺めていてゲシュタルト崩壊起こしたときのように、なんだか急に不安になってきて、『…なんでこのコードコンパイル通るんだっけ?』。なんでList<char>.AddRange()にstringを引き渡せるんだっけ?

念のため「List<T>.AddRange」のオーバーロードを確認してみようとググってみたら、パラメータに「IEnumerable<T>」を受け付けるモノのみ。ふーん。やっぱそうだよね。

じゃあ、stringクラスのほうで「IEnumerable<char>」への暗黙の変換とかができるのかな?と思い、まずはstringクラスの型の構文をググって調べてみた。



ふーん。「IEnumerable」と「IEnumerable<string>」なのか。それだと「IEnumerable<char>」には勝手に変換できないよな。じゃあ、stringクラスに「IEnumerable<char>」に暗黙的に変換できるImplicitなオペレータがあるんだろう。と思って探してみたけど、…ない。…うん。そんなの見た覚えない。

もうわからん。なんか僕が知らない型変換の仕組みでもあるのか?と軽くパニックになりかけたところで、

『ん?stringクラスがIEnumerable<string>を実装?』。いったい何を列挙すんだよ、それ?

よくよくググった結果を見てみると、「.net Framework 2.0」と書いてある。まさかなー。と思いながら「.net Framework 4.5」に切り替えてみると、

public sealed class String : IComparable, 
 ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, 
 IEnumerable, IEquatable<string>

わぁ。やっぱり「IEnumerable<char>」じゃねーか。じゃぁ、「2.0」バージョンでの説明が間違ってたんだよね。と、ちょっとほっとした。

…一応フィードバックを書いといたよ。

2013年4月10日水曜日

今週のイブニング

引き続きのアタリっぷり。イブニングたのしー。

『CAPTAINアリス』はもうすぐ終わりそう。さすがです。面白いです。

『ジャポニカの歩き方』は今号が最終回?

「ラオ編」は終わるだろうなー、と思ってたけど、大使が別の国に赴任になって、カラドもそれについていって、それでもって赴任先で新たなかわいいおねーさんがでてきていろいろ。とかそんな展開じゃないの?ないか。

まぁ、きっちりと終わらせてくれたので、満足です。

『とろける鉄工所』も今号がラスト。毎号楽しみに読んでおりました。

それから『もやしもん』は新刊が出ておりますな。未購入ですがまだ限定版手に入るかな?


2013年4月4日木曜日

ラムダ式を利用したリファクタリングの例 その4 / 後編

前編の続き。Exifファイルのタグ一覧をコンソール上に表示するプログラム。前編最後のコードはこうでした。
private static void Main(string[] args)
{
  using (Bitmap bmp = new Bitmap(args[0]))
  {
    foreach (PropertyItem pr in bmp.PropertyItems)
    {
      Console.Write("ID=0x{0:X}({1}), Len={2}, Type={3}({4})",
        pr.Id,
        PropertyItemReader.GetExifTagId(pr.Id),
        pr.Len,
        pr.Type,
        PropertyItemReader.GetExifType(pr.Type));

      if (pr.Len > 0 && pr.Type == 2)
      {
        Console.WriteLine(", Value={0}", 
                      PropertyItemReader.ReadStringValue(pr));
      }
      else if (pr.Len > 0 && new short[] {3,4,5,9,10}.Contains(pr.Type))
      {
        Console.WriteLine(", Value={0}", string.Join(",",
                      PropertyItemReader.ReadValues(pr).Cast<object>()));
      }
      else
      {
        Console.WriteLine("");
      }
    }
  }
}

ここで、if ~ else if ~ else の構文が条件も各々の処理も「PropertyItem」をもとにしていることに気がつき、せめてもう少し見栄えが良くなるように、条件と処理を管理するこんなシンプルなクラスを作ってみました。

パラメータに対する条件と処理のペアを複数個登録しておき、実行時には最初に条件に合致した処理を実行する。というクラスです。switch ~ case や if ~ else if ~ else をオブジェクトにしたような存在。
public class SwitchAction<T>
{
  private List<Tuple<Func<T, bool>, Action<T>>> switches;
  private Action<T> actionDefault = _ => { };

  public SwitchAction()
  {
    switches = new List<Tuple<Func<T, bool>, Action<T>>>();
  }

  public SwitchAction(Action<T> actionDefault) : this()
  {
    this.actionDefault = actionDefault;
  }

  public void AddCase(Func<T, bool> condition, Action<T> action)
  {
    switches.Add(Tuple.Create(condition, action));
  }

  public void Action(T target)
  {
    var sw = switches.FirstOrDefault(s => s.Item1(target));
    if (sw != null)
      sw.Item2(target);
    else
      actionDefault(target);
  }
}

このクラスを使うと、こう書きかえることができます。
private static void Main(string[] args)
{
  using (Bitmap bmp = new Bitmap(args[0]))
  {
    var sw = new SwitchAction<PropertyItem>(_ => Console.WriteLine());

    sw.AddCase(p => p.Len > 0 && p.Type == 2,
      p => Console.WriteLine(", Value={0}", 
        PropertyItemReader.ReadStringValue(p)));
    sw.AddCase(p => p.Len > 0 && new short[] {3,4,5,9,10}.Contains(p.Type),
      p => Console.WriteLine(", Value={0}",
        string.Join(",", PropertyItemReader.ReadValues(p).Cast<object>())));

    foreach (PropertyItem property in bmp.PropertyItems)
    {
      Console.Write("ID=0x{0:X}({1}), Len={2}, Type={3}({4})",
        property.Id,
        PropertyItemReader.GetExifTagId(property.Id),
        property.Len,
        property.Type,
        PropertyItemReader.GetExifType(property.Type));

      sw.Action(property);
    }
  }
}

んん~?な~んか違うなぁ。どの条件の時も処理はすべて「Console.WriteLine」だ。だから、コンソールに出力する文字列だけを取り出す処理にするように変えよう。なので、条件と処理を管理するクラスを、さらに戻り値を返す物を別に作ろう。
public class SwitchFunc<T, TResult>
{
  private List<Tuple<Func<T, bool>, Func<T, TResult>>> switches;
  private Func<T, TResult> funcDefault = _ => default(TResult);

  public SwitchFunc()
  {
    switches = new List<Tuple<Func<T, bool>, Func<T, TResult>>>();
  }

  public SwitchFunc(TResult resultDefault) : this()
  {
    this.funcDefault = _ => resultDefault;
  }

  public SwitchFunc(Func<T, TResult> funcDefault) : this()
  {
    this.funcDefault = funcDefault;
  }

  public void AddCase(Func<T, bool> condition, Func<T, TResult> func)
  {
    switches.Add(Tuple.Create(condition, func));
  }

  public TResult Func(T target)
  {
    var sw = switches.FirstOrDefault(s => s.Item1(target));
    if (sw != null)
      return sw.Item2(target);
    else
      return funcDefault(target);
  }
}

これを使うと、foreach ループの中身が一文であらわされます。なので、Array.ForEachを使う形に変えてみました。こうなりました。ブロックがusing の一つだけになりました。
private static void Main(string[] args)
{
  using (Bitmap bmp = new Bitmap(args[0]))
  {
    var sw = new SwitchFunc<PropertyItem, string>(string.Empty);

    sw.AddCase(p => p.Len > 0 && p.Type == 2,
      p => ", Value=" +
        PropertyItemReader.ReadStringValue(p));
    sw.AddCase(p => p.Len > 0 && new short[] {3,4,5,9,10}.Contains(p.Type),
      p => ", Value=" +
        string.Join(",", PropertyItemReader.ReadValues(p).Cast<object>()));

    Array.ForEach(bmp.PropertyItems, p =>
      Console.WriteLine("ID=0x{0:X}({1}), Len={2}, Type={3}({4}){5}",
        p.Id,
        PropertyItemReader.GetExifTagId(p.Id),
        p.Len,
        p.Type,
        PropertyItemReader.GetExifType(p.Type),
        sw.Func(p)));
  }
}

…悪くはないけど、わざわざ別にクラスを作ったメリットが感じられないコードになった…。気がする。これなら三項演算子を重ねてみても変わらないんじゃないかなぁ?

ってことで書き変えてみたのがこれ。
private static void Main(string[] args)
{
  using (Bitmap bmp = new Bitmap(args[0]))
  {
    Array.ForEach(bmp.PropertyItems, p =>
      Console.WriteLine("ID=0x{0:X}({1}), Len={2}, Type={3}({4}){5}",
        p.Id,
        PropertyItemReader.GetExifTagId(p.Id),
        p.Len,
        p.Type,
        PropertyItemReader.GetExifType(p.Type),
        p.Len > 0 && p.Type == 2 ? 
            ", Value=" + PropertyItemReader.ReadStringValue(p) :
          p.Len > 0 && new short[] {3,4,5,9,10}.Contains(p.Type) ?
            ", Value=" + string.Join(",", 
              PropertyItemReader.ReadValues(p).Cast<object>()) : ""));
  }
}

あー。動くけど、もはや何がしたいのか誰にもわからんコードになったなぁ。三項演算子を使うにしても、せめて「WriteLine」の{5}に当たる処理は、インラインにするには複雑すぎるので、外に追い出そう。ということで、引数チェックや例外処理を含む、最終的なコードはこうなりました。
private static void Main(string[] args)
{
  if (args.Length < 1)
  {
    Console.WriteLine("Less Arguments.");
    return;
  }
  try
  {
    using (Bitmap bmp = new Bitmap(args[0]))
    {
      Func<PropertyItem, string> getvalues = p =>
        p.Len > 0 && p.Type == 2 ?
          ", Value=" + PropertyItemReader.ReadStringValue(p) :
        p.Len > 0 && new short[] {3,4,5,9,10}.Contains(p.Type) ?
          ", Value=" + string.Join(",", 
            PropertyItemReader.ReadValues(p).Cast<object>()) : "";

      Array.ForEach(bmp.PropertyItems, p =>
        Console.WriteLine("ID=0x{0:X}({1}), Len={2}, Type={3}({4}){5}",
          p.Id,
          PropertyItemReader.GetExifTagId(p.Id),
          p.Len,
          p.Type,
          PropertyItemReader.GetExifType(p.Type),
          getvalues(p)));
    }
  }
  catch (Exception e)
  {
    Console.WriteLine(e.Message);
  }
}

結局のところ、2つクラス(SwitchActionクラスとSwitchFuncクラス)を作ったけど、使わなかった。まぁ、どっかで使うこともあるかもしれないし、無いかもしれないし。

そして最終的なコードはちょっと微妙なにおいが残ったなぁ。それでもいろいろ書いてみた中では一番ましのような気がするけれど。うーん…。

という、ちょっと歯切れの悪い終わり方でした。あ~あ。

2013年4月3日水曜日

ラムダ式を利用したリファクタリングの例 その4 / 前編

デジカメとかで撮影した画像ファイルの、Exifタグの中身を参照する、こんなコードがありました。コンソールアプリで第一引数にファイルパスをとる形です、Mainメソッドのみ書き出します。

private static void Main(string[] args)
{
  if (args.Length < 1)
  {
    Console.WriteLine("Less Arguments.");
    return;
  }
  try
  {
    using (Bitmap bmp = new Bitmap(args[0]))
    {
      foreach (PropertyItem pr in bmp.PropertyItems)
      {
        Console.Write("ID=0x{0:X}({1}), Len={2}, Type={3}({4})",
          pr.Id,
          PropertyItemReader.GetExifTagId(pr.Id),
          pr.Len,
          pr.Type,
          PropertyItemReader.GetExifType(pr.Type));

        if (pr.Len > 0)
        {
          if (pr.Type == 2)
          {
            Console.WriteLine(", Value={0}", 
                              PropertyItemReader.ReadStringValue(pr));
          }
          else if (pr.Type == 3 || pr.Type == 4 || pr.Type == 5 ||
                   pr.Type == 9 || pr.Type == 10)
          {
            StringBuilder sb = new StringBuilder();
            foreach (object o in PropertyItemReader.ReadValues(pr))
            {
              sb.Append(o.ToString());
              sb.Append(",");
            }
            sb.Remove(sb.Length - 1, 1);
            Console.WriteLine(", Value={0}", sb.ToString());
          }
          else
          {
            Console.WriteLine("");
          }
        }
        else
        {
          Console.WriteLine("");
        }
      }
    }
  }
  catch (Exception e)
  {
    Console.WriteLine(e.Message);
  }
}
ちなみに、実行するとこんな結果を吐き出します。



この中の「PropertyItemReader」というのは、別途作ったstaticなクラスで、PropertyItemからExif-Tagが文字列ならその値を文字列で取り出したり、数値型の配列ならArrayで取り出したり。または、Tag-IDやTag-Typeを表示名に変換したりといった機能を持ちます。(とりあえずここでは、そいつのコードは割愛します。)

で、コードをぼーっと眺めてみて、なんだかかっこ悪い。if文があまり意味もなくネストしてるし、配列をカンマ区切りの文字列にするために、StringBuilderを使っているけど、string.Joinを使えばも少しシンプルになりそう。それと、数値型の判断をしている条件文がなんだか冗長。

と、気がついたところを直してみました。こうなりました。なお、これ以降説明用に、引数チェックと例外処理は省略します。

private static void Main(string[] args)
{
  using (Bitmap bmp = new Bitmap(args[0]))
  {
    foreach (PropertyItem pr in bmp.PropertyItems)
    {
      Console.Write("ID=0x{0:X}({1}), Len={2}, Type={3}({4})",
        pr.Id,
        PropertyItemReader.GetExifTagId(pr.Id),
        pr.Len,
        pr.Type,
        PropertyItemReader.GetExifType(pr.Type));

      if (pr.Len > 0 && pr.Type == 2)
      {
        Console.WriteLine(", Value={0}", 
                      PropertyItemReader.ReadStringValue(pr));
      }
      else if (pr.Len > 0 && new short[] {3,4,5,9,10}.Contains(pr.Type))
      {
        Console.WriteLine(", Value={0}", string.Join(",",
                      PropertyItemReader.ReadValues(pr).Cast<object>()));
      }
      else
      {
        Console.WriteLine("");
      }
    }
  }
}

だいぶいい感じになったかな。でも、なんだかまだちょっと不満。何でだろう。

どうやら、if ~ else if ~ elseのブロックが、それぞれがシンプルな処理なだけに、どうも気に入らない。ような気がしてきた。こいつをどうにかすることを考えよう。

が、今日はここまで。寝ないとまずいってば。続きは次のエントリで。

2013年4月2日火曜日

放置のままあっという間に一週間…

開設から一カ月も経たないうちに…。反省…。

いや、ネタを考えたりいろいろ試したりしてるんですが、公開できる形にまとまらなくて…。

そーいうこともありますよね?ね…?