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」のプレゼンテーションタイプが一番好みです。シンプルで見やすい。
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」のプレゼンテーションタイプが一番好みです。シンプルで見やすい。
『なんでこれコンパイル通るんだっけ?』
あらかじめ言っておくと、この話にオチはありません。なんかすいません。
深く考えもせず、こんな感じのコードを書いていました。
コンパイルかけて、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」に切り替えてみると、
わぁ。やっぱり「IEnumerable<char>」じゃねーか。じゃぁ、「2.0」バージョンでの説明が間違ってたんだよね。と、ちょっとほっとした。
…一応フィードバックを書いといたよ。
深く考えもせず、こんな感じのコードを書いていました。
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」バージョンでの説明が間違ってたんだよね。と、ちょっとほっとした。
…一応フィードバックを書いといたよ。
ラベル:
C#
2013年4月10日水曜日
2013年4月4日木曜日
ラムダ式を利用したリファクタリングの例 その4 / 後編
前編の続き。Exifファイルのタグ一覧をコンソール上に表示するプログラム。前編最後のコードはこうでした。
ここで、if ~ else if ~ else の構文が条件も各々の処理も「PropertyItem」をもとにしていることに気がつき、せめてもう少し見栄えが良くなるように、条件と処理を管理するこんなシンプルなクラスを作ってみました。
パラメータに対する条件と処理のペアを複数個登録しておき、実行時には最初に条件に合致した処理を実行する。というクラスです。switch ~ case や if ~ else if ~ else をオブジェクトにしたような存在。
このクラスを使うと、こう書きかえることができます。
んん~?な~んか違うなぁ。どの条件の時も処理はすべて「Console.WriteLine」だ。だから、コンソールに出力する文字列だけを取り出す処理にするように変えよう。なので、条件と処理を管理するクラスを、さらに戻り値を返す物を別に作ろう。
これを使うと、foreach ループの中身が一文であらわされます。なので、Array.ForEachを使う形に変えてみました。こうなりました。ブロックがusing の一つだけになりました。
…悪くはないけど、わざわざ別にクラスを作ったメリットが感じられないコードになった…。気がする。これなら三項演算子を重ねてみても変わらないんじゃないかなぁ?
ってことで書き変えてみたのがこれ。
あー。動くけど、もはや何がしたいのか誰にもわからんコードになったなぁ。三項演算子を使うにしても、せめて「WriteLine」の{5}に当たる処理は、インラインにするには複雑すぎるので、外に追い出そう。ということで、引数チェックや例外処理を含む、最終的なコードはこうなりました。
結局のところ、2つクラス(SwitchActionクラスとSwitchFuncクラス)を作ったけど、使わなかった。まぁ、どっかで使うこともあるかもしれないし、無いかもしれないし。
そして最終的なコードはちょっと微妙なにおいが残ったなぁ。それでもいろいろ書いてみた中では一番ましのような気がするけれど。うーん…。
という、ちょっと歯切れの悪い終わり方でした。あ~あ。
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クラス)を作ったけど、使わなかった。まぁ、どっかで使うこともあるかもしれないし、無いかもしれないし。
そして最終的なコードはちょっと微妙なにおいが残ったなぁ。それでもいろいろ書いてみた中では一番ましのような気がするけれど。うーん…。
という、ちょっと歯切れの悪い終わり方でした。あ~あ。
ラベル:
C#
2013年4月3日水曜日
ラムダ式を利用したリファクタリングの例 その4 / 前編
デジカメとかで撮影した画像ファイルの、Exifタグの中身を参照する、こんなコードがありました。コンソールアプリで第一引数にファイルパスをとる形です、Mainメソッドのみ書き出します。
この中の「PropertyItemReader」というのは、別途作ったstaticなクラスで、PropertyItemからExif-Tagが文字列ならその値を文字列で取り出したり、数値型の配列ならArrayで取り出したり。または、Tag-IDやTag-Typeを表示名に変換したりといった機能を持ちます。(とりあえずここでは、そいつのコードは割愛します。)
で、コードをぼーっと眺めてみて、なんだかかっこ悪い。if文があまり意味もなくネストしてるし、配列をカンマ区切りの文字列にするために、StringBuilderを使っているけど、string.Joinを使えばも少しシンプルになりそう。それと、数値型の判断をしている条件文がなんだか冗長。
と、気がついたところを直してみました。こうなりました。なお、これ以降説明用に、引数チェックと例外処理は省略します。
だいぶいい感じになったかな。でも、なんだかまだちょっと不満。何でだろう。
どうやら、if ~ else if ~ elseのブロックが、それぞれがシンプルな処理なだけに、どうも気に入らない。ような気がしてきた。こいつをどうにかすることを考えよう。
が、今日はここまで。寝ないとまずいってば。続きは次のエントリで。
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のブロックが、それぞれがシンプルな処理なだけに、どうも気に入らない。ような気がしてきた。こいつをどうにかすることを考えよう。
が、今日はここまで。寝ないとまずいってば。続きは次のエントリで。
ラベル:
C#
2013年4月2日火曜日
登録:
投稿 (Atom)

