Windows Phone 7でJSONを扱う方法について(+ Bing APIの使い方)
- 2011-03-31
C#と親和性の高いデータ形式はXMLです。何と言ってもLinq to Xmlが強力です。また、SOAPも悪くない、というのもVisual Studioの自動生成が効くので何も考えずともホイホイ使えます。ではJSONは、というと、これは割と扱いづらいところがあるのが正直なところ。しかしWindows Phone 7においては、JSONを選択すべきでしょう。なにせ、モバイル機器。ネットワークがとても貧弱。データは小さいに越したことはない。XMLとJSONとでは、雲泥の差です。
WPFではJsonReaderWriterFactory(と、内部にそれを用いたDynamicJson)、SilverlightではSystem.Jsonなどが用意されていますが、WP7には一切ありません。じゃあどうするかといえば、シリアライザを使います。WP7ではDataContractJsonSerializerが標準で用意されている(WPF, SLにもあります)ので、それを使ってデシリアライズしてJSONをオブジェクトに変換するのが基本戦略となります。
外部ライブラリ、Json.NETを使うという手も勿論ありますが。
BingからのJSONの取得
何はともあれ、サンプル題材のJSONを拾ってきましょう。Webからの取得というと、最近はいつもTwitterのPublic Timelineでマンネリ飽き飽きなので、別のものを。WP7なので、Bing APIを使いましょう!Bing APIはIDを取得しないと使えないのでサンプル的にどうよ、というところもありますが、IDの取得は簡単(ほんとワンクリックです)だしWP7と親和性の高いAPIでもあるので、これを機に、試しに取ってみるのも良いのではと思います。画像検索、翻訳など色々種類があるのですが、今回はWeb検索(sources=web)にします。
// 標準WP7テンプレのMainPage.xaml.csにベタ書き
const string AppId = ""; // AppIdは登録してください
Uri CreateQuery(params string[] words)
{
// countなどは変数で置き換えれるようにするといいのではと思います、ここでは固定決め打ちですが
var query =
"?Appid=" + AppId
+ "&query=" + Uri.EscapeUriString(string.Join(" ", words))
+ "&sources=web"
+ "&version=2.0"
+ "&Market=ja-jp"
+ "&web.count=20"
+ "&web.offset=0";
return new Uri("http://api.search.live.net/json.aspx" + query);
}
public MainPage()
{
InitializeComponent();
var wc = new WebClient();
Observable.FromEvent<DownloadStringCompletedEventHandler, DownloadStringCompletedEventArgs>(
h => h.Invoke, h => wc.DownloadStringCompleted += h, h => wc.DownloadStringCompleted -= h)
.ObserveOnDispatcher()
.Subscribe(e =>
{
var json = e.EventArgs.Result; // ダウンロード結果(json文字列)
MessageBox.Show(json);
});
wc.DownloadStringAsync(CreateQuery("地震"));
}
json.aspxにクエリ文字列をつけてGETするだけなので割とお手軽。クエリ文字列がゴチャゴチャして分かりづらいのですが、基本的に弄るのはqueryとweb.countぐらいかな、と思います。BingのReferenceは、生成元のクラス構造がまんま掲示されているだけで、恐ろしく分かりづらいので、適当にサンプルから当たりをつける感じで。
非同期通信の実行はReactive Extensions(Rx)で行います。Windows Phone 7では標準で入っているのでSystem.ObservableとMicrosoft.Phone.Reactiveを参照に加えてください。非同期通信を生でやるなんてありえませんから!Rx利用を推奨します。
得られるJSONは下記のものです。
{
"SearchResponse": {
"Version": "2.0",
"Query": {
"SearchTerms": "地震"
},
"Web": {
"Total": 88,
"Offset": 0,
"Results": [
{
"Title": "地震情報 - Yahoo!天気情報",
"Description": "Yahoo!天気情報は、市区町村の天気予報、世界の天気...",
"Url": "http://typhoon.yahoo.co.jp/weather/jp/earthquake/",
"DisplayUrl": "typhoon.yahoo.co.jp/weather/jp/earthquake",
"DateTime": "2011-03-29T19:11:00Z"
},
{
"Title": "地震情報 :: ウェザーニュース",
"Description": "最新の地震の震度、震源地、震度分布を速報で届けます...",
"Url": "http://weathernews.jp/quake/",
"DisplayUrl": "weathernews.jp/quake",
"DateTime": "2011-03-28T09:10:00Z"
},
// 配列上なので幾つも...
]
}
}
}
JSONに関してはJSON ViewerをVisual StudioのVisualizerに組み込むとかなり快適にプレビュー出来るようになります。が、カスタムVisualizerはWP7では実行出来ないのでテキストで見て、スタンドアロンのものにコピペってのを実行ですね、しょんぼり。
DataContractJsonSerializer
では、JSONをオブジェクトに変換しましょう。基本的には、1:1に対応するクラスを作るだけ。必要に応じて System.Runtime.Serializationの参照を加えDataContract, DataMember属性なども加えればよし。
public class BingWebRoot
{
public SearchResponse SearchResponse { get; set; }
}
public class SearchResponse
{
public string Version { get; set; }
public Query Query { get; set; }
public Web Web { get; set; }
}
public class Query
{
public string SearchTerms { get; set; }
}
public class Web
{
public int Total { get; set; }
public int Offset { get; set; }
public Results[] Results { get; set; }
}
public class Results
{
public string Title { get; set; }
public string Description { get; set; }
public string Url { get; set; }
public string DisplayUrl { get; set; }
public string DateTime { get; set; }
}
JSONは、JavaScriptのオブジェクトとほぼ同一の記述ですが、ようするに{}になっている部分はクラスで、[]になっている部分は配列で、置き換えていけばいい、ということで。難しくはないのですが、面倒くさいには大変面倒くさい。なお、JSONの構造を全部記述する必要はなく、必要なものだけでも構いません、例えばVersionやQueryはいらないから省くとか、全然アリです。
そして、 System.ServiceModel.Web を参照設定に加え、DataContractJsonSerializerを使います。
var wc = new WebClient();
Observable.FromEvent<OpenReadCompletedEventHandler, OpenReadCompletedEventArgs>(
h => h.Invoke, h => wc.OpenReadCompleted += h, h => wc.OpenReadCompleted -= h)
.ObserveOnDispatcher()
.Subscribe(e =>
{
using (var stream = e.EventArgs.Result)
{
var serializer = new DataContractJsonSerializer(typeof(BingWebRoot));
var result = (BingWebRoot)serializer.ReadObject(stream);
MessageBox.Show(result.SearchResponse.Web.Results[0].Title);
}
});
wc.OpenReadAsync(CreateQuery("地震"));
デシリアライズはReadObject、シリアライズはWriteObjectで行います。基本はstreamを渡すだけでオブジェクトの出来上がり。
with Reactive Extensions
ですが、まあ、Resultsが欲しいだけなのにSearchResponse.Web.Resultsは長げーよ、とか、DateTimeがstringでイヤだー、とか色々あります。そういう場合はJSONとのマッピング用のクラスとは別に、アプリケーション側で使うクラスを別に立ててやればいいんぢゃないかしら。
public class SearchResults
{
public string Title { get; set; }
public string Url { get; set; }
public DateTime DateTime { get; set; }
public override string ToString()
{
return DateTime + " : " + Title + " : " + Url;
}
}
TitleとUrlとDateTimeしかいらない!という具合で。これを、今度はWebRequestを使って書くと
var req = WebRequest.Create(CreateQuery("ほむほむ"));
Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)()
.Select(r =>
{
using (var stream = r.GetResponseStream())
{
var serializer = new DataContractJsonSerializer(typeof(BingWebRoot));
return (BingWebRoot)serializer.ReadObject(stream);
}
})
.SelectMany(x => x.SearchResponse.Web.Results)
.Select(x => new SearchResults { DateTime = DateTime.Parse(x.DateTime), Title = x.Title, Url = x.Url })
.ObserveOnDispatcher()
.Subscribe(x =>
{
// 加工は全部終わってるのでここで色々自由に処理
Debug.WriteLine(x);
});
となります。最初のSelectは非同期の結果、次のSelectManyではResults[]、つまり普通の配列を平坦化して、以降は普通のLinqのようなコレクション処理をしています。
非同期リクエストとオブジェクトのコレクション処理が、完全にシームレスに溶け込んでいます。これが、RxがLinqとして存ることの真価の一つです。記述が統一され、かつ限りなくシンプルになる。Rxは非同期が、イベントが、時間が、簡単に扱えます。でも、本当の真価は単独で使うというだけでなく、それらが全てPush型シーケンスに乗っていることで、統合することが可能だというところにあります。
でも、むしろ分かりにくい?ふむむ……。慣れの問題、などというと全く説得力がなくてアレですが、しかし、慣れです。記述がシンプルになり、柔軟性と再利用性が増していることには間違いないわけで、後は一度全て忘れてLINQの世界に飛び込んでしまえばいいと思うんだ。
Linqは各処理の単位が細分化されている(Selectは射影、Whereはフィルタ)ことも特徴ですが、これは思考の再利用可能性を促します。非同期->オブジェクト配列=SelectManyなど、単純な定型パターンに落とし込めます。C#はもとより強力なIntelliSenseにより、ブロックを組み立てるかの如きなプログラミングを可能にしていますが、Linqでは、それが更に先鋭化されていると見れます。
まとめ
これも現在製作中のWP7アプリからの一部です。最近Bing API利用に切り替えたので。無駄に汎用化して作りこみつつきりがないので適度なところできりあげつつ。ユニットテスト作ってあったので移行自体は幸いすんなりいった。良かった良かった。テスト大事。
Bing APIの前は諸事情あってGoogleからのスクレイピングでした。スクレイピングはグレーだろうということで代替案をずっと探していて、何とかBingに落ち着きました。最初はどうにも使い物にならない、と思ったのですが、検索パラメータを色々変えて、ある程度望む結果が出るようにはなったかな、と。Bingは結構癖があって、調整大変ですね。その話は後日、WP7アプリが完成したときにでも……。
コード的にはスクレイピングのほうも割と凝ってたんですけどねー、バッサリとゴミ箱行き。復活することは、ないかな。もったいないけどしょうがない。いつかそのうち紹介する日は、来るかも来ないかも。
そんなわけで延々と足踏みしていて実装は相変わらず一歩も進んでませんが(!) 順調に制作は進行中なので乞うご期待。いやほんと。
Widows Phone 7でアプリケーション名やバージョン番号をバインドする方法
- 2011-03-28
アプリケーション名の表示や、about画面に表示したいであろうバージョン番号、どうします?直書きのstringやリソースから?それもいいのですけれど、せっかくプロジェクトのプロパティ(AssemblyInfo.cs)で、アプリケーション名やバージョン番号を設定しているわけだから、そこから利用できたほうがいいですよね。
というわけで、これらの情報はアセンブリから取得しましょう。
public class AssemblyInfoData
{
public static readonly AssemblyInfoData ExecutingAssembly = new AssemblyInfoData(Assembly.GetExecutingAssembly());
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 AssemblyInfoData(Assembly assembly)
{
var assemblyName = new AssemblyName(assembly.FullName);
FileName = assemblyName.Name;
Version = assemblyName.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);
}
}
FileName, Versionはnew AssemblyNameに渡してから(WPFだとGetNameで直に取れるのですが、Silverlightだとセキュリティ違反で例外が飛ぶためこうする必要がある)、それ以外の値はカスタム属性から取得できます。また、任意のAssemblyの情報をコンストラクタに投げて取得出来るようになっていますが、どうせ必要なのは実行アセンブリの情報だけでしょ?ってことで、public staticなフィールドにExecutingAssemblyのデータを公開するようにしています。なので、コードからは、 AssemblyInfoData.ExecutingAssembly.Version とアクセスするだけで、簡単に取得できます。
でも、コードから欲しいということはほとんどなくて、UIに表示するためだけに欲しいのですよね、こういう情報は。バインディングしましょう!まず、こんなクラスを用意します。
public class AssemblyInfoDataBindingHelper
{
public AssemblyInfoData Value { get { return AssemblyInfoData.ExecutingAssembly; } }
}
何故これが必要かというと、WPFの場合は{x:static}でstatic変数もバインド出来るのですが、Silverlight/WP7ではバインド出来ないためです。いやあ、カッコ悪いですね、{x:static}欲しいですね、まあ、ないものはしょうがない。
次にApp.xamlのApplication.Resourcesの中に
<Application.Resources>
<!-- Applicationのところで xmlns:local="ネームスペース" を宣言しておく-->
<local:AssemblyInfoDataBindingHelper x:Key="AssemblyInfoData"/>
</Application.Resources>
と書いてリソースを登録。準備はこれで完了で、あとはバインドするだけ。
<TextBlock x:Name="ApplicationTitle" Text="{Binding Value.Title, Source={StaticResource AssemblyInfoData}}" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="{Binding Value.Version, Source={StaticResource AssemblyInfoData}}" Style="{StaticResource PhoneTextTitle1Style}" />
と、以上です。これで下のような感じに
表示されました。ApplicationTitleにTitleは分かりますがPageTitleにVersionは丸っきりイミフ。ボタンは、何となく寂しいから置いただけで意味はないです気にしないで。
AssemblyInfoDataクラスのコードは完全に独立して使い回しが効くので、コピペってどうぞご自由にお使いください。煮るなり焼くなり……、パブリックドメインで。
まとめ
といったのは、一応、今製作中のWP7アプリの一部です。順調に制作は遅れまくり。うむむ。今月中といきたかったのだけど、まーだずれ込みそう。その前は二月中のつもりだったのだけど、Chaining Assertionが思いの外引っ張りすぎて手を付けてる余裕がなかった。とにかく、4月中頃までには、マーケットプレイスで公開したいなあ。あとソースコードも公開します(というか既に製作中のがこっそり公開されてます)。Reactive Extensionsの実践例として、ただたんに非同期で使うというだけじゃなく、こういうケースで使える、コードはこうなる。というサンプルとして役立てればいいな、という思いで書いてますので、適当に待っていてください。
人に見せるためのコード、というのを念頭に置きすぎていて、同じ場所のコードの修正ばかり繰り返していてアプリ全体としては一歩も製作が進まないという超鈍足状態に陥ってますが(コード書きの遅さに定評のある私です(キリッ)、でも、書きなおす度に確実によくなっていく実感はあるので、最終的にそこそこ見せれるコードになるのではないかと思っています。少なくとも、部分的には面白い内容になるはずです。
DynamicAccessor - Chaining Assertion ver.1.4.0.0
- 2011-03-17
テストブームはまだ続いています。さて、テスト可能性の高い設計は良いのですが、本来あるべきである設計を歪めて(単純なところでいえば、virtualである必要でないものをvirtualにするとか、privateであるべきものをprotectedにするとか、無駄なinterfaceとか、不自然な引数の取り方とか)テスト可能性を確保するのは、私は嫌だなー。などと思っていましたが、しかし、モックについてはMolesを使うことで、最高の形で解決しました。
次に何を考えるべきかな、と浮かんだのはprivateのテスト。privateのテストは考えないという流儀もあるし、それは尤もだと思いますが、publicのものをテストするにも、ちょっと確認とりたかったり値を弄ってやりたかったりなど、触れると楽な場合もいっぱいあるので、出来るにこしたことはありません。MSTestにはAccessorの自動生成で完全なタイプセーフとIntelliSenseの保証をしてくれて、それはそれで大変素敵。なのですが、もう少し軽くテスト出来る機構を用意しました。MSTestへの依存はないので、NUnitでも他のテストフレームワークでも使えます。
privateへのアクセスはリフレクションが常套手段ですが、C#4.0ならdynamicがあるよね。というわけで、dynamicで包んだアクセサを用意しました。dynamicにしたからってprivateのものは呼び出せないので、DynamicObjectで包んでリフレクション経由になるようにしています。
AsDynamic()
こんな感じで使えます。
// こんなprivateばっかなクラスがあったとして
public class PrivateMock
{
private string privateField = "homu";
private string PrivateProperty
{
get { return privateField + privateField; }
set { privateField = value; }
}
private string PrivateMethod(int count)
{
return string.Join("", Enumerable.Repeat(privateField, count));
}
}
// AsDynamic()をつけるだけでPrivateプロパティが呼べる
var actual = new PrivateMock().AsDynamic().PrivateProperty;
Assert.AreEqual("homuhomu", actual);
// dynamicは拡張メソッドが呼べないのでIsを使う場合はキャストしてくださいな。
(new PrivateMock().AsDynamic().PrivateMethod(3) as string).Is("homuhomuhomu");
// 勿論setも出来ます(インデクサもいけます。ジェネリックメソッドも若干の制限付きですが呼べます)
var mock = new PrivateMock().AsDynamic();
mock.PrivateProperty = "mogumogu";
(mock.privateField as string).Is("mogumogu");
オブジェクトへの拡張メソッドにより、全てのオブジェクトに対しAsDynamic()が使える状態です。IntelliSense汚染なので通常だとあまり許容できることではないのですが、UnitTestなのでOKだろう、と。AsDynamic()後はDynamicObjectとして、全ての呼び出しがリフレクション経由となり、public/privateのメソッド/プロパティ/フィールド/インデクサに自由にアクセス可能となっています。見た目は普通と全く一緒で大変自然なのがdynamicの利点。
dynamicの状態では拡張メソッドの呼び出しは不可能なので、IsによるAssertionを行う場合は、キャストして型を適用してやる必要があります。メンドクセーという場合はAssert.AreEqualなど、本来用意されているものはobjectが対象なので、そのまんま使えます。どちらでも好き好きでどうぞ。
ダイナミックとジェネリックとメソッド呼び出し
実装内部の話。DynamicObjectでTryInvokeMemberです。んで、最初はすんごく簡単に実装出来ると思ったんですよ!dynamicでリフレクション包むだけね、はいはい、余裕余裕、と。が、実際に書きだすとどうも引っかかる。オーバーロードが。ジェネリックが。型推論が。ふつーに呼んでるとうまくオーバーロードを解決してくれなくて、AmbiguousMatchException(あいまいな一致)を投げてくれます。なので、手動でマッチさせる必要があります。
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
var csharpBinder = binder.GetType().GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder");
if (csharpBinder == null) throw new ArgumentException("is not generic csharp code");
var typeArgs = (csharpBinder.GetProperty("TypeArguments").GetValue(binder, null) as IList<Type>).ToArray();
var method = MatchMethod(binder.Name, args, typeArgs);
result = method.Invoke(target, args);
return true;
}
private Type AssignableBoundType(Type left, Type right)
{
return (left == null || right == null) ? null
: left.IsAssignableFrom(right) ? left
: right.IsAssignableFrom(left) ? right
: null;
}
private MethodInfo MatchMethod(string methodName, object[] args, Type[] typeArgs)
{
// name match
var nameMatched = typeof(T).GetMethods(TransparentFlags)
.Where(mi => mi.Name == methodName)
.ToArray();
if (!nameMatched.Any()) throw new ArgumentException(string.Format("\"{0}\" not found : Type <{1}>", methodName, typeof(T).Name));
// type inference
var typedMethods = nameMatched
.Select(mi =>
{
var genericArguments = mi.GetGenericArguments();
if (!typeArgs.Any() && !genericArguments.Any()) // non generic method
{
return new
{
MethodInfo = mi,
TypeParameters = default(Dictionary<Type, Type>)
};
}
else if (!typeArgs.Any())
{
var parameterGenericTypes = mi.GetParameters()
.Select(pi => pi.ParameterType)
.Zip(args.Select(o => o.GetType()), Tuple.Create)
.GroupBy(a => a.Item1, a => a.Item2)
.Where(g => g.Key.IsGenericParameter)
.Select(g => new { g.Key, Type = g.Aggregate(AssignableBoundType) })
.Where(a => a.Type != null);
var typeParams = genericArguments
.GroupJoin(parameterGenericTypes, x => x, x => x.Key, (_, Args) => Args)
.ToArray();
if (!typeParams.All(xs => xs.Any())) return null; // types short
return new
{
MethodInfo = mi,
TypeParameters = typeParams
.Select(xs => xs.First())
.ToDictionary(a => a.Key, a => a.Type)
};
}
else
{
if (genericArguments.Length != typeArgs.Length) return null;
return new
{
MethodInfo = mi,
TypeParameters = genericArguments
.Zip(typeArgs, Tuple.Create)
.ToDictionary(t => t.Item1, t => t.Item2)
};
}
})
.Where(a => a != null)
.Where(a => a.MethodInfo
.GetParameters()
.Select(pi => pi.ParameterType)
.SequenceEqual(args.Select(o => o.GetType()), new EqualsComparer<Type>((x, y) =>
(x.IsGenericParameter)
? a.TypeParameters[x].IsAssignableFrom(y)
: x.Equals(y)))
)
.ToArray();
if (!typedMethods.Any()) throw new ArgumentException(string.Format("\"{0}\" not match arguments : Type <{1}>", methodName, typeof(T).Name));
// nongeneric
var nongeneric = typedMethods.Where(a => a.TypeParameters == null).ToArray();
if (nongeneric.Length == 1) return nongeneric[0].MethodInfo;
// generic--
var lessGeneric = typedMethods
.Where(a => !a.MethodInfo.GetParameters().All(pi => pi.ParameterType.IsGenericParameter))
.ToArray();
// generic
var generic = (typedMethods.Length == 1)
? typedMethods[0]
: (lessGeneric.Length == 1 ? lessGeneric[0] : null);
if (generic != null) return generic.MethodInfo.MakeGenericMethod(generic.TypeParameters.Select(kvp => kvp.Value).ToArray());
// ambiguous
throw new ArgumentException(string.Format("\"{0}\" ambiguous arguments : Type <{1}>", methodName, typeof(T).Name));
}
private class EqualsComparer<TX> : IEqualityComparer<TX>
{
private readonly Func<TX, TX, bool> equals;
public EqualsComparer(Func<TX, TX, bool> equals)
{
this.equals = equals;
}
public bool Equals(TX x, TX y)
{
return equals(x, y);
}
public int GetHashCode(TX obj)
{
return 0;
}
}
泥臭い。LINQ的には普段あまり使わないGroupJoinや、SequenceEqualでのIEqualityComparerとか、ここぞとばかりに色々仕込んで実に楽しげになりました。何とも酷いゴリ押し。速度とかどうなのこれ、ただリフレクションを使っただけじゃないよね、というのは、UnitTestですから。だから、許容される。そうでなければ、やれない。
名前からマッチ->型の当てはめ->実引数からマッチ->非ジェネリックメソッドを優先->引数が全てジェネリックでなければ優先->最終的にメソッドが一つにまで絞り込めなければエラー。コンパイラの行う、正確なオーバーロードの解決法はC#言語仕様書の7.5.3に書いてあります。従っていません。というか、outとかrefとか非対応だし、ジェネリックに関しても持ってる情報が足りなすぎてマッチしたくてもできない。特に痛いのはコード上での型が吹っ飛んでいて、GetTypeによる具象型しか取得できないこと。そのせいで、引数の複数の同一のTは、ほぼ同じ型同士でないとダメになってしまっていて(コード上で宣言しているインターフェイスの情報が取れないのでどうしょもない)。その他、入れ子なジェネリックの場合もコケます(対応させるの面倒くさい)。
でも、ふつーの9割がたなシチュエーションでは動作するはずです。
ちなみに、ジェネリックの型引数は通常、DynamicObjectのBinderでは取得出来ません。Binderの実際の型(の持つインターフェイス)であるICSharpInvokeOrInvokeMemberBinderのTypeArgumentsが持っているのですが、ICSharpInvokeOrInvokeMemberBinderがinternalのため、外側から手出しは出来ないのです。どうするかって、もうここまで来たら何も躊躇うことなくリフレクションです本当にありがとうございました。GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder");とか、負けたにも程がある。
そんなわけで、ふつーに作ると存外面倒くさいっぽいです。いや、こんな泥臭くなるのは何かおかしい気はかなりしなくもないんですが、うーん。まあ、泥臭くてもライブラリ側で吸収出来るなら、それはそれでいいかな、と。結果だけを見れば。ライブラリが泥臭さを担保するかわりに、ユーザーはAsDynamic()だけで綺麗に呼び出せる。それはとっても嬉しいなって。
余談
そんなDynamicAccessorのテスト作るのにIsとかAssertEx.Throws、あって良かった。大変助かった。ExpectedExceptionAttributeなんて使ってられない。どっぐふーどどっぐふーど。個人的にはMSTestは凄い好きというか、テストツール選ぶのにあたって優先度が一番高い項目はIDE統合(デバッグ含むというかデバッグ最重要)なので、統合できてないものはその時点でアウトです(どれも、一手間加えれば統合出来るのでしょうけれど)。その上で、Chaining Assertionを使えばMSTestの色々な不満が一気に解消出来て、最高に幸せだなあ、と、自画自賛。
まとめ
当初のIsだけでサクッと軽量なテスト~とかってノリは何処に行ったんでしょうか。ぐぬぬ。それでも、最大限のシンプルさは保ち続けている、と、思いたい。内部がどうあれ、外からはAsDynamic()が足されただけだし、それ自体もシンプルそのものですよね、ね?コンセプトはまだ守れてると、思いたい。
ところで、dynamic使ってます?ぶっちけ全然使ってません。結局のところvarが最高に便利なわけで、dynamicは、例えば以前書いたDynamicJsonであったり、これのようなリフレクションであったりと、通常のC#とは違う場所との糊なわけで、そうそう出番のあるものでもない、ですねん。C#4.0の言語的な追加の最たるものはdynamicなわけですが、普段はそんな使わない代物なわけだと、言語的にはC#4.0はあんま変わらなかったねー、という印象で。LL的な視点から、C#にはdynamicで動的言語でもあるんだって?という意見をたまに見ますが、純C#上ではぶっちゃけほとんど使わないのでそんなでもない。
じゃあなくてもいいか、というと、んー、まあ、このように、たまにある分には便利だし、言語的にもスムースに入り込んでいるので、良いのではないか、むしろ良いのではないか、とは思います。あんま使わないけどたまには思い出してあげると大変可愛い。
そういえば私はメタ構文変数としてhoge, huga, hageの順にhogehogeと使ってるのですが、Twiterでhomuにする。というのを見て、なんかいいな、とか思ってしまったので、当分はhomu, mogu, mamiの順に使おうかと思っている昨今。こーいうのは半年後ぐらいに、あちゃーという気持ちになるのが常なのですがー。
Rx + MolesによるC#での次世代非同期モックテスト考察
- 2011-03-10
最近、妙にテストブームです。Chaining Assertionを作ったからですね。ライブラリドリブンデベロップメント。とりあえずでも何か作って公開すると、その分野への情報収集熱に火がつくよね。そしてテスト厨へ。さて、ユニットテストで次に考えるべきは、モックの活用。C#でモックといえばMoqが評価高い。メソッドチェーンとExpression Treeを活かしたモック生成は、なるほど、良さそうです。読み方も可愛いしね。もっきゅ。もっきゅ。
というわけでスルーして(えー)Molesを使いましょう。Microsoft Research謹製のモックフレームワークです。PexとのセットはMSDN Subscriptionが必要ですが、MolesのみならばFreeです。VS Galleryに置かれているので、VSの拡張機能マネージャーからでも検索に引っかかります。
Moles。Pex and Molesとして、つまりPex(パラメータ自動生成テスト)のオマケですよねー、と考えていたりしたりした私ですが(実際、Pexがこの種のモックシステムを必要とする、という要請があって出来た副産物のよう)、これがオマケだなんてとんでもない!アセンブリ解析+DLL自動生成+ILジャックという、吹っ飛んだ発想による出鱈目すぎる魔法の力でモック生成してしまうMolesは、他のモックフレームワークとは根源的に違いすぎる。
Molesとは何か。既存のクラスの静的/インスタンスメソッドやプロパティの動作を、自由に置き換えるもの。既存のクラスとは、自分の作ったものは勿論、.NET Frameworkのクラスライブラリも例外ではありません。Console.WriteLineやDateTime.Now、File.ReadAllTextなども、そのままに乗っ取ることが可能です。PublicもPrivateも、どちらでも乗っ取れます。
しかも使うのは簡単。往々に強力なものは扱いも難しくなってしまうものですが、常識はずれに強力な魔法が働いている場合は、逆に非常に簡単になります。対象となるメソッドにラムダ式を代入する。それだけ。moqなどよりも遥かに簡単。
Molesを使う
日本語での紹介はMoles - .NETのモック・スタブフレームワーク - Jamzzの日々に、また、MSRのページのDocumentationにLevel分けされた沢山のドキュメントが用意されているので(素晴らしい!Rxも見習うべし!)、そちらに目を通せば大体分かると思われます。
とりあえず使ってみましょう。Molesをインストールしたら、テストプロジェクトを作って、参照設定を右クリックし、Add Moles Assembly for mscorlibを選択。
するとmscorlib.molesというファイル(中身はただのXML)が追加されます。そして、とりあえずビルドするとMicrosoft.Moles.Framework, mscorlib.Behavior, mscorlib.Molesが参照設定に追加されます。つまり、mscorlibが解析され、モッククラスが自動生成されました!mscorlib以外のものも生成したい場合は、参照設定の対象dll上で右クリックし、Add Moles Assemblyを選べば、.molesが追加されます。なお、解析対象が更新されてHoge.Molesも更新したい、という場合はリビルドすれば更新されます(逆に言えばリビルドしないと更新されないため、コンパイルは通るものの実行時エラーになります)。また、もし追加したことによって何かエラーが出る場合(VS2010 SP1で私の環境ではSystem.dllでエラーが発生する)は、.molesの対象アセンブリの属性にReflectionOnly="true"も記載すると回避できることもあります。
では簡単な例を。
// mscorlibに含まれる型の場合のみ、Molesで乗っ取りたい型を定義しておく必要があります
// 定義なしで実行すると、この型定義してね、って例外メッセージが出るので
// それが出たらコピペってAssemblyInfo.csの下にでも書いておけばいいんぢゃないかな
[assembly: MoledType(typeof(System.DateTime))]
[TestClass]
public class UnitTest1
{
// 現在時刻を元に"午前"か"午後"かを返すメソッド
public static string ImaDocchi()
{
return (DateTime.Now.Hour < 12) ? "午前" : "午後";
}
// HostType("Moles")属性を付与する必要がある
[TestMethod, HostType("Moles")]
public void TestMethod1()
{
// ラムダ式で置き換えたいメソッドを定義する
// プリフィックスMが自動生成されているクラス、
// サフィックスGetはプロパティのgetの意味
MDateTime.NowGet = () => new DateTime(2000, 1, 1, 5, 0, 0);
ImaDocchi().Is("午前");
MDateTime.NowGet = () => new DateTime(2000, 1, 1, 15, 0, 0);
ImaDocchi().Is("午後");
}
}
お約束ごと(属性付与)が若干ありますが、エラーメッセージで親切に教えてくれるので、そう手間もなくMoles化出来ます。モック定義自体は何よりも簡単で、見たとおり、デリゲートで置き換えるだけです。非常に直感的。(IsはChaining Assertion利用のものでAssert.AreEqualです、この場合)
システム時刻に依存したメソッドのテストは、単体テストの書き方として、よく例に上がります。そのままじゃテスト出来ないのでリファクタリング対象行き。メソッドの引数に時刻を渡すようにするか、時刻取得を含んだインターフェイスを定義して、それを渡すとか、ともかく、共通するのは、外部から時刻を操れるようにすることでテスト可能性を確保する。ということ。
Molesを使えば、そもそもDateTime.Now自体をジャックして任意の値を返すように定義出来てしまいます。これは単純な例でしかないので、いくら出来てもそんなことやらねーよ、かもですね。はい。それが良い設計かどうかは別としても、Molesの存在を前提とすると、テスト可能にするための設計方法にも、かなりの変化が生じるのは間違いないでしょう。時に、テスト可能性のために歪んだ設計となることも、Molesで乗っ取れるのだと思えば、自然な設計が導出できるはず。
イベントのモック化
続けてイベントの乗っ取りも画策してみましょう。イベントの乗っ取りは、正直なところ少し面倒です。
// こんな非同期でダウンロードして結果を表示するメソッドがあるとして
public static void ShowGoogle()
{
var client = new WebClient();
client.DownloadStringCompleted += (sender, e) =>
{
Console.WriteLine(e.Result);
};
client.DownloadStringAsync(new Uri("http://google.co.jp/"));
}
[TestMethod, HostType("Moles")]
public void WebClientTest()
{
// 外から発火出来るように外部にデリゲートを用意
DownloadStringCompletedEventHandler handler = (s, e) => { };
// AddHandlerとRemoveHandlerを乗っ取って↑のものに差し替えてしまう
MWebClient.AllInstances.DownloadStringCompletedAddDownloadStringCompletedEventHandler =
(wc, h) => handler += h;
MWebClient.AllInstances.DownloadStringCompletedRemoveDownloadStringCompletedEventHandler =
(wc, h) => handler -= h;
// DownloadStringAsyncをトリガに用意したデリゲートを実行
MWebClient.AllInstances.DownloadStringAsyncUri = (wc, uri) =>
{
// DownloadStringCompletedEventArgsはコンストラクタがinternalなので↓じゃダメ
// handler(wc, new DownloadStringCompletedEventArgs("google!modoki"));
// というわけで、モックインスタンス作ってしまってそれを渡せばいいぢゃない
var mockArgs = new MDownloadStringCompletedEventArgs()
{
ResultGet = () => "google!modoki"
};
handler(wc, mockArgs);
};
// 出力はConsole.WriteLineなので、それを乗っ取って、結果にたいしてアサート
MConsole.WriteLineString = s => s.Is("google!modoki");
ShowGoogle(); // 準備が終わったので、実行(本来非同期だけど、全て同期的処理に置き換えられてます)
}
ちょっと複雑です。テストしたい処理はDownloadStringCompletedの中ですが、外からこれを発火する手段は、ない。この例だとAddHandlerだけ乗っ取って、直に発火させてもいいのですが、(非同期だけじゃなく)他のイベントの場合でも応用が効くように、正攻法(?)でいきましょう。イベントの発火を自分でコントロール出来るように、まずはAddとRemoveに対し、外部デリゲートに通すよう差し替えます。なお、インスタンスメソッドを乗っ取る場合は.AllInstancesの下にあるインスタンスメソッドを、静的メソッドと同じようにラムダ式で直に書き換えるだけです。非常に簡単。なお、第一引数は必ず、そのインスタンス自身となっていることには注意。
あとは、トリガとなるメソッドがあればそれを(この場合はDownloadStringAsync)通して、そうでない場合(例えばただのボタンクリックとか)なら直にイベントを乗っ取ったデリゲートを発火してやれば完了。で、ここでEventArgsがコンストラクタがprivateなせいで生成出来なかったりというケースも少なくないのですが、それはモックインスタンスを作って、そいつを渡してやるだけで簡単に回避できます。
少し手順が多いですが、「出来る」ということと、まあ流れ自体は分かるしそれぞれは置き換えるだけで複雑じゃない。ということは分かるのではと思います。でも、それでも面倒くさいですよ。ええ、どう見ても面倒くさいです。しかし、このことはReactive Extensionsを使えば解決出来ます。んが、その前にもう一つ別の例を。
APMのモック化
非同期繋がりで、APM(Asynchronous Programming Model, BeginXxx-EndXxx)のモック化もやってみましょう。
// こんな非同期でダウンロードして結果を表示するメソッドがあるとして
public static void ShowBing()
{
var req = WebRequest.Create("http://bing.co.jp/");
req.BeginGetResponse(ar =>
{
var res = req.EndGetResponse(ar);
using (var stream = res.GetResponseStream())
using (var sr = new StreamReader(stream))
{
var result = sr.ReadToEnd();
Console.WriteLine(result);
}
}, null);
}
[TestMethod, HostType("Moles")]
public void WebRequestTest()
{
// Beginでコールバックを呼ぶ、EndでWebResponseを返す
MHttpWebRequest.AllInstances.BeginGetResponseAsyncCallbackObject =
(req, ac, obj) => { ac(null); return null; };
MHttpWebRequest.AllInstances.EndGetResponseIAsyncResult = (req, ar) =>
{
return new MHttpWebResponse
{
GetResponseStream = () => new MemoryStream(Encoding.UTF8.GetBytes("bing!modoki"))
};
};
MConsole.WriteLineString = s => s.Is("bing!modoki");
ShowBing(); // 実行
}
イベントよりは少し簡単ですが、BeginとEndの絡み具合は混乱してしまいます。また、HttpWebResponseのダミーを作るのも面倒。
Reactive Extensions
見てきたように、イベントもAPMも、モック化は面倒です。そこで出てくるのがReactive Extensions。RxならばIObservableとして一つにまとまるので、その一点をモック化してしまえばそれだけですむ、しかもダミーのIObservableを生成するのは非常に簡単!というわけで、例を見ましょう。モック化、の前に非同期のRx化と、そのテストを。
// こっち本体
public class Tweet
{
public string Name { get; set; }
public string Text { get; set; }
// 実際やるなら静的メソッドじゃなくて、API操作はまとめて別のクラスで、と思いますが、まあとりあえずこれで
public static IObservable<Tweet> FromPublicTL()
{
var req = WebRequest.Create("http://twitter.com/statuses/public_timeline.xml");
return Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)()
.Select(r =>
{
// StreamはSilverlightでも同期で書けるので、同期で取得しちゃいます
using (var stream = r.GetResponseStream())
using (var sr = new StreamReader(stream))
{
return sr.ReadToEnd();
}
})
.SelectMany(s => XElement.Parse(s).Elements()) // 配列上のものをバラして
.Select(x => new Tweet // Tweetに変換
{
Text = x.Element("text").Value,
Name = x.Element("user").Element("screen_name").Value
});
}
}
// こっちがTest
[TestMethod]
[Timeout(3000)] // Timeoutはテスト全体のオプションで設定してもいいね
public void FromPublicTL()
{
var tl = Tweet.FromPublicTL().ToEnumerable().ToArray();
// 20件あって、NameとかTextが
// 全部空じゃなければ正常にParse出来てるんじゃないの、的な(適当)
tl.Length.Is(20);
tl.All(t => t.Name != "" && t.Text != "").Is(true);
}
Twitterのpublic_timeline.xml、つまり認証のかかってない世界中のパブリックなツイートが20件(オプション無しの場合)XMLで取れるAPIを叩いています。RxのFromAsyncPatternを使い、リクエストは非同期。非同期のテストは通常難しい、のですが、Rxの場合はFirstやToEnumerableで簡単にブロックして同期的なものに変換出来るため、それで結果を取って、何食わぬ顔でアサートしちゃえます。
Rxは非同期が簡単にテスト出来てメデタシメデタシ。これはこれで良いのですが、ところでパブリックじゃなくて認証入るものを取るときはどうするの?ストリーミングAPI(ツイートだけじゃなくFavoriteなど色々な形式のXMLが届く)を試したいけど、誰かがFavoriteつけるまで待機とか、テストに不定な時間がかかるものはどうするの?などなどで、本物のウェブ上のデータをテストで毎回取ってくるのは大変です。また、誤ったデータが流れてきた/サーバーが応答不能状態な場合などの例外処理のテストは、通常では出来ないですね?
そこで、モック。ウェブからじゃなくてモックがダミーのデータを返せばいいわけだ。そして改めてFromPublicTLメソッドを見ると「データ取得」と「データパース」の二つを行っている。なので、ここはその二つに分けて、後者の「データパース」がモックでテスト出来るようにしてやりましょう。
public class Tweet
{
public string Name { get; set; }
public string Text { get; set; }
private static IObservable<String> GetRawPublicTL()
{
var req = WebRequest.Create("http://twitter.com/statuses/public_timeline.xml");
return Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)()
.Select(r =>
{
using (var stream = r.GetResponseStream())
using (var sr = new StreamReader(stream))
{
return sr.ReadToEnd();
}
});
}
public static IObservable<Tweet> FromPublicTL()
{
return GetRawPublicTL()
.SelectMany(s => XElement.Parse(s).Elements())
.Select(x => new Tweet
{
Text = x.Element("text").Value,
Name = x.Element("user").Element("screen_name").Value
});
}
}
リファクタリングというほど大仰なものでもなく、メソッドチェーンのうちのネットワークアクセス部分をprivateメソッドとして切り出しただけです。Rxは、メソッドチェーンの一つ一つが独立しているので、切った貼ったが簡単なのもメリット。では、このprivateメソッドをMolesで差し替えてしまおう!
[TestMethod, HostType("Moles")]
public void FromPublicTLMock()
{
// これは省略した文字列ですが、実際は取得したXMLをファイルに置いて、それを読み込むといいかも
var statuses = @"
<statuses>
<status>
<text>Hello</text>
<user>
<screen_name>neuecc</screen_name>
</user>
</status>
<status>
<text>Moles</text>
<user>
<screen_name>xbox99</screen_name>
</user>
</status>
</statuses>
";
// 本来ネットワーク取得のものを、たった一行でただのシーケンスに置き換える
MTweet.GetRawPublicTL = () => Observable.Return<string>(statuses);
var tl = Tweet.FromPublicTL().ToEnumerable().ToArray();
tl.Length.Is(2);
tl[0].Is(t => t.Name == "neuecc" && t.Text == "Hello");
tl[1].Is(t => t.Name == "xbox99" && t.Text == "Moles");
}
これだけです。データ用意は別として、モックへの差し替えはたった一行書いただけ。既存のコードに一切手を加えず、こんなにも簡単にモックへの置き換えが可能だなんて、わけがわからないよ。
理由として、Rxの持つ非同期もイベントも普通のシーケンスも、全て等しく同じ基盤に乗っている、という性質が生きています。この性質は時に分かりづらさを生むこともありますが、しかしそれ故に絶大な柔軟性も持っていて、その結果、本来非同期処理のものをただのシーケンスに置き換えることを可能にしています。非同期が、イベントがテストしづらいならMolesでただのシーケンスに差し替えてしまえばいい。別段「テストのため」の設計を意識しなくても、Rxで書くということ、それだけで自然にテスト可能な状態になっています。
なんて、さらっと流してしまっているわけですが、この事に気づいた瞬間にこれはヤバい!と悶えました。いや、凄いよ、凄過ぎるよRx + Moles。
Moq vs Moles、あるいは検証のやり方
Molesは非常に強力ですが、ではMoqと、どう使いわけよう?もしくは、全て代替出来てしまう?Molesは純粋な置き換えのみなので、呼び出しの検証はありません。モックとスタブの用語の違い、を言うならば、Molesの提供するものはモックではなくスタブ。自動生成クラスにつくプリフィックスのSは勿論Stubですが、MはMockではなく、Moleを指します(じゃあMoleって何よ、っていうと、何なんでしょうね……)
さて、使い分けとかいうほどのものでもないので、基本はMolesのみでいいんじゃないかなあー。もし呼び出しを保証したければ、こういうふうに書ける。
// IDisposableのDisposeは1回しか呼ばれないとしたい場合を検証する
// インターフェイスの場合はMHogeではなくSHogeなことに注意
var callCount = 0;
var mock = new SIDisposable()
{
Dispose = () => { callCount += 1; }
};
(mock as IDisposable).Dispose(); // mockを使った処理があるとする...
callCount.Is(1); // 1回のみ
フレームワークに用意されていないから一手間なのは事実ですが、Molesの持つシンプルさを失ってまで足したいほどでもなく、好きなようなチェックを自前で書けるのだから、それでいいかな。むしろこのほうが大抵スッキリ。といったようなことは、MolesのマニュアルのComparison to Existing Frameworksに書かれています。Molesの提供するシンプルさが、私は好きです。
フレームワークは最大限のシンプルさを保って、機能は他の機構に回すというのはChaining Assertionも一緒ですよ←比較するとはなんておこがましい
もう一つ、もっと具体的なもので行きましょうか。LinqのCount()はICollectionの場合は全部列挙せず、Countプロパティのほうを使ってくれる(詳細は過去記事:LinqとCountの効率をどうぞ)ことのテスト。
var countCalled = false;
var enumeratorCalled = false;
var mock = new SICollection01<int>
{
CountGet = () => { countCalled = true; return 100; },
GetEnumerator = () => { enumeratorCalled = true; return null; }
};
// 呼んでるのはLinqのCount()のほうね
mock.Count().Is(100);
countCalled.Is(true);
enumeratorCalled.Is(false);
CountGetで100返せば、それだけでいい気もしますが、念のため+意図を表明するということで。
そういえばですが、Chaining AssertionのIsは、散々DisったAssertThatに存外近かったりします。 Assert.That(actual, Is(expected)) と書くものを、 actual.Is(expected) と書けるようになった、ですから(但しこれはAreEqualsの場合であって、Shuold.Be.GreaterThanとかやり始めたらぶん殴る)。
Silverlight? Windows Phone 7?
Silverlightのテスト環境は貧弱です。当然それに連なってWP7のテスト環境も貧弱です。というかMSTestが使えない!というだけじゃなく、Molesも動かせませんし。どうする?そこは、「リンクとして追加」でSilverlight/WP7のファイルをWPFのプロジェクトにでも移して、そのWPFのコードをテストするという手段が取れなくもないです。非同期周りはRxが吸収出来るし、互換性は、元来クラス群が貧弱なSLのほうが第一ターゲットなので、まあまあ大丈夫なはず。ViewModelはともかくとして、Modelのテストなら行けるはずです。
非同期のテストは難しいって?うん、Rxを使えば簡単なんだ。大丈夫。
まとめ
次世代というか、もう現世代なんですよ。今まで理想論に過ぎなかったものを、急速に現実のものとしてくれています。徒手空拳では難しい領域はいっぱいあった。でも、今、手元にはRxとMolesがある。この二つを手に、もう一度領域を見てみたらどうだろう?晴れた景色が広がっているはずです。
それにしてもRxの素晴らしさがMolesで更に輝くことといったらない。
今回はRxと組み合わせた例を中心に説明しましたが、Molesは単体でも文句なく素晴らしい。Moqも悪くないけれど、選ぶならMolesです。とにかく抜群に使いやすい。機能が極まっていることと、APIのシンプルさは両立するんだって。自動生成を活かしきった事例ですねー。VSとのシームレスな一体化といい、文句のつけようがない。ついこないだまで軽視していた私が言うのもアレですが、これがそんなに知られていない(少なくともググッて引っかかる記事はid:jamzzさんの記事だけだ)のは勿体無い話。Moles、是非試してみてください。
C#(.NET)のテストフレームワーク4種の比較雑感
- 2011-03-03
for MSTestをやめて、NUnitとMBUnitとxUnit.NETにも対応しました。MSTestに限定していたのは、単純に他のを入れて試すの面倒くせー、というだけの話であり、そういう態度はいけないよね、と思ったので全部入れました。NUnitはDocumentだけは読んでかなり参考にしてたのですが、他のは全くはぢめて。MSTest以外はみんな野心的に開発進んでるんですね。比べると機能面では一番見劣りするMSTest。
というわけで、対応させるために各種フレームワークを入れる&多少触ったので、それらの紹介/感想などを書きたいと思います。C#上というか.NET上、ですね。の前に、更新事項が幾つかあるのでそれを。まず、CollectionAssertに等値比較するラムダ式を受けるオーバーロードを追加しました。
var lower = new[] { "a", "b", "c" };
var upper = new[] { "A", "B", "C" };
// IEqualityComparer<T>かFunc<T,T,bool>を渡して、コレクションの任意の演算子で等値比較
lower.Is(upper, StringComparer.InvariantCultureIgnoreCase);
lower.Is(upper, (x, y) => x.ToUpper() == y.ToUpper());
// Linq to ObjectsのSequenceEqualを使えば今までも出来なくもなかったのですが!
lower.SequenceEqual(upper, StringComparer.InvariantCultureIgnoreCase).Is(true);
SequenceEqual使えばいいぢゃん(キリッ って思ってました。Isのオーバーロードがかなり嵩んでいて、オーバーロードのIntelliSense汚染が始まってる。IntelliSense = ヘルプ。であるべきなのですが、数が多かったり似たようなオーバーロードが多いと、混乱を招いてしまい良くないわけです。だいたいがして、以前にAllを拒んでいたのにSequenceEqualはアリなのかよ、という。一応弁解すると、CollectionAssert自体に比較子を受けるオーバーロードがあるので、IsがCollectionAssertを示すのなら、それを使える口を用意すること自体は悪くないかな、と。あと、SequenceEqualはIEqualityComparerなのが使いづらい。ラムダ式を渡せる口がないので、こちらに用意して回避したかったというのもあります。AnonymousComparer - lambda compare selector for Linqを使えばラムダ式渡せますが。
それにしても、本来のものはIComparerを受けるんですよ、ジェネリックじゃないほうの。今時ノンジェネリックを強要とか、しかもIComparer<T>とIComparerは別物なのでIComparer<T>として定義すると使えないという。ありえない!そもそも何故にIComparerなのか、という話ではある。大小比較じゃないので、等値として0しか判定に使ってませんもの。それならIEqualityComparer<T>だろ、と。GetHashCodeの定義が(不要なのに)面倒だから、そのせいかなー。 そう考えると分からなくもないので、Func<T, T, bool>を用意しておきました。ラムダ式は最高よね。
それと、今回、コレクションでのIsの動作を変更しました。今までは欠陥があって、例えばlower.Is(upper)とすると、オーバーロードがかち合ってCollectionAssertではなくAreEqualsのほうで動いてしまってました。これは、大変望ましくない。オーバーロードだけだと解決しようがないので、内部で動的にIEnumerbaleかを判定した上でCollectionAssertを使うようにしてやりました。
例外のテスト
他のテストフレームワークを色々見たのですが、例外は属性じゃなくてラムダ式でのテストをサポートするのも少なくない。というかMSTestだけですが、それがないのは。属性だと、範囲が広すぎる(メソッドをテストしたいのに、コンストラクタがテスト範囲に含まれ、コンストラクタが例外を吐いてしまったらメソッドのテストにならない)問題があるので、ラムダ式で書けるほうがいい、という流れのようで。その他に1例外テスト1メソッドが強制されて面倒くさいー、引数のチェックぐらい、1メソッドに全部突っ込んでおきたい、というモノグサな私的にもラムダ式のほうが好み。というわけで、移植移植。
// Throes<T>で発生すべき例外を指定する
AssertEx.Throws<ArgumentNullException>(() => "foo".StartsWith(null));
// 戻り値も取得可能で、その場合は発生した例外が入ってます
var ex = AssertEx.Throws<InvalidOperationException>(() =>
{
throw new InvalidOperationException("foobar operation");
});
ex.Message.Is(s => s.Contains("foobar")); // それにより追加のアサーションが可能
// 例外が起きないことを表明するテスト
AssertEx.DoesNotThrow(() =>
{
// code
});
これを入れるのに伴い、拡張メソッド置き場のクラス名をAssertExに変更しました。それに加え、partial classにしたので、独自に何かメソッド置きたい場合に、AssertExに追加することが可能です。.csファイル一つをポン置きでライブラリと言い張るからこそ出来るやり口……。割とどうでもいい。
テストフレームワーク雑感
Assertちょっと触った程度でしかないので、本当に雑感ですが、一応は各フレームワークを触ったので感想など。
非常に何とかUnitっぽい仕上がり。おお、これこれ、みたいな。ある種のスタンダード。他のテストフレームワークにあるあの機能が欲しいなあ、みたいなものもしっかり取り入れてる感で、全く不足なく。情報も豊富、周辺環境も充実。
不満は、Assert.That。これJavaのJUnit発祥なのかしらね。Hamcrest?まあしかしともかく、酷い。英文のようで読みやすいとか入力補完ですらすらとか、ないない。これが本当に読みやすい?書きやすい?ありえないでしょ……。Is.All.GreaterThanとか、ただのシンタックス遊び。ラムダ式のないJava(or .NET2.0)ならともかく、現在のC#でそれやる意味はどこにもない。
かなり独自な方向に走っている印象。Gallioというプラットフォーム中立なテストシステムを立てて、その上にNUnitやMSTest、そしてGallioの前身でありテストフレームワークのMbUnitなどが乗っかる。という、壮大なお話。IronRubyでRSpec、など独自にテストシステムを立てられるほどに需要がなさそうな、でもあると絶対嬉しいと思う人いるよね、といったものを一手に吸収出来るかもです(実際RSpecは乗っかってる模様)。そんな壮大な話を出すだけあって、テストランナーとしてのGallioの出来はかなり良いように見えます。
MbUnit自体は可もなく不可もなく。属性周りとかは独特なのかなあ、単純なアサート関数しか使っていないので、その辺は分かりません。
ちなみに、今回紹介するテストフレームワークの中で、唯一NuGet非対応。対応に関しては議論されたようですが、どうもGallioのプラグインを中心とする依存関係が、現在のNuGetだと上手く対応させられないそうで。将来のNuGetでも対応するような仕組みへの変更は今のところ考えてない、とNuGet側から返答を貰っているみたいなので、当面はMbUnitのNuGet入りはなさそうです、残念。まあ、若干大掛かりなGallioのインストール込みで考えたほうが嬉しいことも多く、NuGet経由での必要性は薄いから、それはそれでしょうがないかな、といったところ。
非常に独特で、旧来のテストの記述法を徹底的に見直して新しく作り直されています。とっつきは悪いのですが(如何せん、他と比べて全然互換がない)良く考えられていて、素晴らしいと思います。
例えば、CollectionAssertはなく、Assert.Equalが万能に、Objectの場合はEqualsで、Collectionの場合は列挙しての値比較で行ってくれます。つまり、この辺はChaining AssertionのIsと同じ。旧来のしがらみに囚われず、Assert関数はどういう形であることがベストなのか、ということを突き詰めて考えると、そうなる。と思う。
ただ、非常に厳密な比較を行うので、型が違う(IEnumerable<T>とT[]とか)とFailになります。Chaining AssertionのIsは、ゆるふわに列挙後の値比較だけで判定します。どちらが良いのかは、正しいテスト、ということを考えればxUnit.NETのほうなのでしょう。私は、その辺は、とにかく「書き味」優先で振りました。型比較の厳密さは例外テストのThrowsメソッドにも現れていて、MSTestやMbUnitは派生型も含め一致すればSuccessとしますが、xUnit.NETは厳密に一致しないとFailになります。Chaining AssertionのThrowsは厳密一致のほうを採用しました。
正しいテストを書くために、テストフレームワークはかくあるべき、という強い意志でもって開発されている感じ。これは、相当に良いと思います。MSTest以外のものを使うなら、私はこれを選びたい。付属のテストランナーは貧弱ですが、Gallioを使うことで克服出来ます。
- MSTest
唯一TestCaseがない。これがたまらなく不便なんです!かわりに強力なデータソース読み込みが可能になっているようですけれど、強力な分、セットアップに手間がかかってダルいという。他は、いたって普通。ふつーのAssert関数群とふつーの属性でのテスト設定。このノーマルっぷりは標準搭載らしいかもです。最大の欠点はウィザードで自動生成されるテンプレコードがどうしょうもなくクソなこと。あのノイズだらけのゴミは何とかすべし。
最大のメリットは完全なVisual Studio統合。サクッとデバッグ実行。何て素晴らしい。標準搭載で準備一切不要なのも嬉しい。昨今のテストの重要度を考えると、Express EditionにもMSTest入ってるべきですねえ。ちょっと弱いAssert関数群とTestCaseがないことは、Chaining Assertionを使えば補完されるので、全体的に割と問題なくなって素敵テストフレームワークに早変わり。TestCaseに関してはモドきなので若干弱いけれど。
番外編。僕と契約してPexをより強固にしようよ!Code Contracts + Pexは後ろにMicrosoft Researchがいる.NETならではの強烈さだなあ、と。とりあえずVS2010 後の世代のプログラミング (2) Pex « kazuk は null に触れてしまったの素晴らしい導入記事を読んで試せばいいと思うんだ。そういえば、Pex開発者はMbUnitのプロジェクト創設者(学生時代にMbUnit作って、その後MSRに行ったそうで、凄いね...)だそうですよ。
比較しての相違点
他のテストフレームワークや拡張補助ライブラリと根源的に異なるのは、CollectionAssertに対する考え方です。Linq to Objectsに投げればそれが一番でしょ?という。末尾に.Isなのは、それが一番Linq to Objectsを適用して返した場合に書きやすいから。Linqはインフラ。これを活用しないなんて勿体無い。ドキュメントにそれを書くことで、公的に推奨している、ということを押し出している、つもりです。
まとめ
xUnit.NETはかなり素晴らしいんじゃないかと思います。IDE完全統合という点で、私はMSTestを選んでしまいますが、MSTest以外から使うものを選択するのならば、xUnit.NETにしたいところ。周辺環境がまるで整ってない感はありますが、その辺はGallioを使えば吸収出来るっぽいので、セットで、みたいなところかしらん。
テストは別に同一言語である必要はない、ので、CLRの特色を活かせば、IronRubyでRSpecというのは魅力的な道に見えます。今回は試せていないのですが、いつか試してみたい。F#を用いてのテストフレームワークも良さそう。F#は、特にテストのような小さい単位ではF# Interactiveに送りながら書けるのが強烈なアドバンテージになるよね。ユニットテストぐらいの小さな単位なら、デバッガよりもむしろこちらのほうが小回り効いて書きやすそう。
私的にはChaining Assertionはそれらと比べても、全く引けを取らない書きやすさがあると思っています。C#はクラス定義などを除けば、コード本体自体の書き味はライトウェイトなのですよ、ね、ね!
それにしてもAssertThat一族の毒はどうにかならないのかねえ……。AssertThatがない、という点だけでxUnit.NET一択ですよほんと。F#などでも、この形式を踏襲しているのを見ると大変モニョる。こういうのがビヘイビアドリブンなんですかね?ただのシンタックス遊びなだけで、そーいうの違くない?って。でもAssertで詳細なデータが出ない?Expression Treeを解析して詳細なデータを出しゃあいいわけで。どっかの言語の仕組みを持ってこないで、C#の持つ特色、強みであるExpression Treeを活かす方向で動けないものなのか。
別にJavaだからどうとか言うつもりではないです。大切なのはあらゆる言語を見つめて、良いものを取り入れることでしょう?例えば、Groovyの素晴らしいPower AssertをC#でやろうとするプロジェクト expressiontocode など。C#に取り込もうとするなら、こちらのやり方でしょう。時系列を考えればそれは違う、という話もあるでしょうけど、現在の、これからの姿勢として。本当に良いものは何なのかを、考えよう。それを実現するための力がC#には備わっているのだから。
Chaining Assertion ver 1.1.0.1
- 2011-02-28
リリースから、意外にも色々と反響を頂き、ありがとうございました。そんなわけで、意見を考慮した結果、メソッド増えました。当初のスタンスは、シンプルにIsのみでやる、というところだったので、現在結果的に4x2つある、という状況は理想的には望ましくないのですが、現実的にはしょうがないかな、といった感です。以下が、Chaining Assertionの全拡張メソッド。
Is
IsNot
IsNull
IsNotNull
IsInstanceOf
IsNotInstanceOf
IsSameReferenceAs
IsNotSameReferenceAs
IsNotはまんまで、AreNotEqualです。IsSameReferenceAsはAreSameで、参照比較。メソッド名長いね!ただ、私はAreSameという名前は曖昧で何がSameなのか(値がSameとも取れるじゃないか!)よく分からず嫌いだったので、Referenceをメソッド名に入れるのは確定でした。その上で、IsReferenceEqualsにするかで悩みましたが、ここはAreSameであることを想起させるためにも、Sameを名前に含めることにしました。
拡張のジレンマ
最も多く使う部分のみに絞って最少のものを提供する。という思想がありました。だからNotもSameも頻度的には下がるから不要で、それらが使いたい場合はAssert.AreSameといったものを使えばいい、と。従来のものを完全に置き換えるものじゃなく、要点を絞って、処方を出す。
でもまあ、Isのみにこだわりすぎるのって、逆に良くないよね、と。事実妥協してIsNull/IsNotNullは入れていたわけだし、もう少し妥協したっていいのではないかと。と、たがが外れた結果、増量しすぎた……。これだけ増えると、Isだけで済むんだよ!簡単だよ!強力だよ!という文句に説得力に欠けていってしまう。
なので、このまま拡張し続ける気はありません。メソッドが増えると、学習コストの増加とIntelliSense汚染が出てきてしまうので、便利メソッドを増やすのは、ないかな、と。特にStringやCollection周りには、Isのような汎用的なものではなく、もっと特化的なものがあれば「便利」なのは違いないのですが、でも、それ本当に便利かな、って。ラムダ式を使えば十分代替出来る、Linq to Objectsを使えば十分代替出来る。なら、それでいいよね、と。
例えば seq.All(pred).Is(true) が失敗する時、これだとどの値がfalseになったのか分からない。判定をLinq to ObjectsのAllに任せているから。これがもし、 seq.IsAll(pred) といったような、専用メソッドが用意されていれば、細かいエラーメッセージを出すことが出来て便利なのは間違いない。
でもAllだけ?他のは?Allだけ特別扱いする理由もない。じゃあ全部のLinq to Objectsを実装するか、といったらありえない!実装の手間・俺々実装によるバグ混入の可能性、そしてIntelliSense爆発。学習コストの増大(まあ、IsXxxでXxxをLinq to Objectsのものと同一にする、という原則を貫けば増大はしませんが)。
// ソート済みであるかどうかのチェック(NUnitのIsOrderedにあたるもの)を、Linq to Objectsで
var array = new[] { 1, 5, 10, 100 };
array.Is(array.OrderBy(x => x));
Linq to Objectsの汎用性の高さ!既存の仕組みに乗っかれる場合は、全力で乗っかりたい。DynamicJsonもそうで、扱いづらいJsonReaderWriterFactoryにdynamicの皮を被せているだけにすぎない、という。
まとめ
Assert.Hogeよりも圧倒的に書きやすく美しく短くなるというほかに、圧倒的に読みやすくなります。読みやすさはメンテナンス性の向上に繋がる。一生懸命テストを書いたはいいものの、面倒くさくてメンテ放置になって、ゴミの山になってしまった。という経験が、私はある(えー)。そんなわけで、書きやすさは何より大事だし、読みやすさまで手に入るなら僥倖だし、かつ、学習コストも最小限。という売り文句なので、是非試してみてもらえればと思います。
// 今回追加された細々としたもの
// Not Assertion
"foobar".IsNot("fooooooo"); // Assert.AreNotEqual
new[] { "a", "z", "x" }.IsNot("a", "x", "z"); /// CollectionAssert.AreNotEqual
// ReferenceEqual Assertion
var tuple = Tuple.Create("foo");
tuple.IsSameReferenceAs(tuple); // Assert.AreSame
tuple.IsNotSameReferenceAs(Tuple.Create("foo")); // Assert.AreNotSame
// Type Assertion
"foobar".IsInstanceOf<string>(); // Assert.IsInstanceOfType
(999).IsNotInstanceOf<double>(); // Assert.IsNotInstanceOfType
ようするところ、AssertThatを更に凝縮したような感じです。また、 Assert.That(array, Is.All.GreaterThan(0)) なんてのは array.All(x => x >= 0).Is(true) のほうがずっと良くない?って。思うわけです(但し、Assert.Thatはエラーメッセージがずっと親切)。なお、Is.All.GreaterThanは、一見IntelliSenseが効いて書きやすそうに見えるけれど、無駄な連鎖によりイライラさせられるだけで別に全然書きやすくない。連鎖において、一つ一つの処理に重みがないものは、どうかなあ。ただのシンタックス遊びだよ、こんなの(ラムダ式のないJavaでの苦肉の策だというのなら分かる)。
それと、基本的にオブジェクトへの拡張メソッドというのは、影響範囲が広すぎて禁忌に近いわけですが、ユニットテストという範囲が限定されている状況なので、許容できるのではないかな、と思っています。しかし8つはなぁ、多すぎかなあ……。今も、これで良かったのか相当悩んでます。InstanceOfは不要だったかも、typeof書きたくなかったというだけじゃ理由としてアレだし、メソッド増やすほどの価値はあったのか、多分、ないよね、うぐぐ。でも、これでほぼ全てがチェーンで書けるようになりました。それはそれでヨシと思うことにします。
メソッドチェーン形式のテスト記述ライブラリ
- 2011-02-24
昨日の今日で特に更新はないのですが、せっかく画像作ったので再解説。命名は見たとおりに、メソッドチェーンで切らさずアサーションが書ける!ということから付けました。テストを、限りなくシンプルに素早く迷いなく書けるようにしたかった。その答えが、メソッドチェーンで最後に値を取ることです。
// 全てIs一つでメソッドチェーンで流るまま!
Math.Pow(5, 2).Is(25);
"foobar".Is(s => s.StartsWith("foo") && s.EndsWith("bar"));
Enumerable.Range(1, 5).Is(1, 2, 3, 4, 5);
Assert.AreEqualの最大の問題は、どっちがactualでどっちがexpectedだか悩んでしまうこと。一秒でも引っかかったら「気持よくない」のです。テストはリズム。リズムを崩す要素は極限まで潰さなければならない。Chaining Assertionならば、引数として与えるのはexpectedだけなので、全く悩む必要がなくなる。些細なことですが、しかし、大きなことです。
また、この手のメソッドチェーン式のものでよく見られるのが「流れるようなインターフェイス」を名乗って、自然言語のようにチェーンで書けるようにする、などというものがありますが、滑稽です。EqualTo().Within().And()だの.Should().Not.Be.Null()だの、馬鹿げてる。ラムダ式なら一発だし、そちらのほうが遥かに分かりやすい。DSLは分かりやすく書きやすくすることを目指すものであって、形式主義に陥ることじゃない。自然言語風流れるようなインターフェイスは二度とDSLを名乗るべきではない。
もう一つの問題は、無駄に沢山あるアサート関数。覚えるのは面倒。特に、コレクション関連。ぶっちゃけ全然扱いやすくなく、そして、私達にはずっとずっと扱いやすいLinq to Objectsがあるじゃないか。というわけで、コレクションのテストをしたい時は、Linq to Objectsで結果まで絞ってIsで書くという手法を推奨します。
new[]{1, 3, 7, 8}.Contains(8).Is(true);
new[]{1, 3, 7, 8}.Count(i => i % 2 != 0).Is(3);
new[]{1, 3, 7, 8}.Any().Is(true);
new[]{1, 3, 7, 8}.All(i => i < 5).Is(false);
ね、このほうがずっと書きやすいし柔軟に書ける。
非同期のテスト
非同期のテストは難しい。結果が返ってくるのが非同期なのでテストを抜けてしまうので。ではどうするか、答えは、Rx使えば余裕です。例として以下のコードは今こそこそと作ってるWP7アプリ用のテストです。
[TestMethod]
public void Search()
{
// SearchLyricはWebRequestのBeginGetResponseで非同期に問い合わせるもの
// 結果はIObservableを返す非同期なものなので、ToEnumerableして同期的に待機する
var song = new Song { Artist = "吉幾三", Title = "俺ら東京さ行ぐだ" };
var array = song.SearchLyric().ToEnumerable().ToArray();
array.Count().Is(1);
array.First().Title.Is("俺ら東京さ行ぐだ 吉幾三");
array.First().Url.Is("http://music.goo.ne.jp/lyric/LYRUTND1127/index.html");
}
FirstとかToEnumerableで、非同期をサクッとブロックして同期的に待機してしまえば簡単に値を確保できてしまいます。とまあ、そんなわけで非同期処理は全部Rxで行うよう統一すると、こういうところで物凄く楽になるわけですね、素晴らしい。だからもう非同期プログラミングにRx無しとか全方位でありえないわけです。
といっても、Rxなんて使ってないし!という場合は、こんなものが。例は恣意的すぎますが
[TestMethod]
public void SpinUntilTest()
{
int number = 0;
// 非同期処理をしてるとする
ThreadPool.QueueUserWorkItem(_ =>
{
Thread.Sleep(3000); // 重たい処理をしてるとする
number = 1000;
});
// 指定条件が成立するまで待機
SpinWait.SpinUntil(() => number != 0, 10000); // Timeout = 10秒
number.Is(1000);
}
Pxチームの記事 SpinWait.SpinUntil for unit testing で見たのですが、SpinWait.SpinUntilが結構使えそうです。Thread.Sleepでタイムアウトいっぱいまで待つとか、手動でManualResetEventを設定する、などなどに比べると遥かにサクッと書けて良さそう。ていうかSpinWait.SpinUntilなんて初めて知りましたよ、本当にhidden gems!
まとめ
テストのないコードはレガシーコード。と、名著が言ってるので
Chaining Assertionで苦痛を和らげて、素敵なテスト生活を送りましょう。
XboxInfoTwit - ver.2.3.0.3
- 2011-02-24
Xbox.comの認証周りが変わってログイン出来なくなってしまってたのですが、それを修正しました。朝っぱらに急ぎで対応したので、あまりテストしてません。マズいところあったら後で直します。
Chaining Assertion for MSTest
- 2011-02-23
MSTest用の拡張メソッド集をCodePlexと、そしてNuGet(idはChainingAssertionです)にリリースしました。ライブラリといってもたった数百行(うち300行が自動生成)の、csファイル一つです。NuGet経由でも.csが一個配置されるだけという軽量軽快さ。中心となるのはIsというTへの拡張メソッドで、これはAssert.EqualとAssert.IsTrue(pred)とCollectionAsert.AreEqualを、一つのメソッドだけで表現します。単純なものですが、それだけなのに驚くほどテストが書きやすくなります。まずは例を。
// 全てのオブジェクトに.Isという拡張メソッドが追加されていて、3つのオーバーロードがあります
// Assert.AreEqualと等しい
Math.Pow(5, 2).Is(25);
// 条件式によるAssert.IsTrueをラムダ式で
"foobar".Is(s => s.StartsWith("foo") && s.EndsWith("bar"));
// コレクションの等値比較は可変長引数でダイレクトに書ける
Enumerable.Range(1, 5).Is(1, 2, 3, 4, 5);
以前どこかで見たような?はい。ベースはテストを簡単にするほんの少しの拡張メソッドで書いたものです。それを元に若干ブラッシュアップしています。ラムダ式によるAssert時のエラーメッセージが、非常に分かりやすくなりました。
前はFuncだったのですが、Expressionで受けるようにしたので、情報が取れるようになったらからです。それと渡ってくる値に関しても表示。これで、情報が足りなくてイライラってのがなくなって、かなり書きやすくなりました。
ところでnullに関してはIsNullとIsNotNullというメソッドに分けました。あまり増やしたくはないので、入れるか悩んだんですけどね。x => x == null でもいいのだし。
TestCase/Run
以前書いた時もNUnit風のTestCaseの再現を試みていたのですが、今回は抜本的に作り替えてリベンジしました。再現度(それっぽい度)は相当上がっています。といっても、目的は再現度を上げることじゃあないですね、大丈夫です、これにより、遥かに使いやすく実用的になりました。
[TestClass]
public class UnitTest
{
public TestContext TestContext { get; set; }
[TestMethod]
[TestCase(1, 2, 3)]
[TestCase(10, 20, 30)]
[TestCase(100, 200, 300)]
public void TestMethod2()
{
TestContext.Run((int x, int y, int z) =>
{
(x + y).Is(z);
(x + y + z).Is(i => i < 1000);
});
}
}
TestContextへの拡張メソッドにRunというのが仕込んであって、それとTestCase属性が連動して、パラメータ指定のテストが行えるようになっています。TestCaseSource属性もありますよ?
[TestMethod]
[TestCaseSource("toaruSource")]
public void TestTestCaseSource()
{
TestContext.Run((int x, int y, string z) =>
{
string.Concat(x, y).Is(z);
});
}
public static object[] toaruSource = new[]
{
new object[] {1, 1, "11"},
new object[] {5, 3, "53"},
new object[] {9, 4, "94"}
};
何でTestContextへの拡張メソッドという形をとっているかというと、TestContextから実行中クラス名とメソッド名が簡単に取れるから、です。この二つさえ分かれば、あとはリフレクション+LINQでやり放題ですからねー。クラス名が取れるTestContext.FullyQualifiedTestClassNameは、どうやらVS2010からの追加なようで、ナイス追加。VS2008からTest周りは何も進化しなくてNUnitと比べるとアレもコレも、な感じなわけですが、地味に改良はされていたんですね。いや、誰が嬉しいんだよって話ですが、私は大変嬉しかったです。
テストケースとして分けられないので、非常にオマケ的ではあるんですが、大変お手軽なので無いよりは嬉しいかな、と。
まとめ
Assert.HogeHogeの楽しくなさは異常。本当にダルい。かといって、英語的表現を目指した流れるようなインターフェイスは爆笑ものの滑稽さ。ああいうのをDSLと言うのは止めましょうぜってぐらいの。まあ、そんなわけで、これを使うと結構快適に書けます。Isの一つ一つは馬鹿みたいに単純なたった一行のメソッドなんですが、それだけなのに、全然違うんですよね。
というわけでまあ、楽しいテスト生活を!あと、最近は自動化テストなPexや、それと絡むCodeContractsを触っているので、それらについて近いうちに書けたらと思っています。
Rx FromEvent再訪(と、コードスニペット)
- 2011-02-18
最近の私ときたらFromAsyncでキャッキャウフフしすぎだが、Asyncの前にEventを忘れているのではないか、と。というわけで、FromEventについて、「また」見直してみましょう!延々と最初の一歩からひたすら足踏みで前進していないせいな気はその通りで、いい加減飽きた次に進めということだけれど、まあそれはそのうち。私的にはFromEventはとうに既知の話だと思い込んで進めているのであまり話に出していなかっただけなのですが、初期に出したっきりで、特にここ数カ月で(WP7出たりAsync CTP出たり昇格したり)でRxが注目を浴びるのが多くなってはじめましてな場合は、そんな昔の記事なんて知らないよねですよねー(Blogの形式は過去記事へのポインタの無さが辛い)。なわけなので定期再び。
「Rxの原理再解説」や「時間軸という抽象で見るRx」、というネタをやりたいのですが、長くなるのと絵書いたり動画撮ったり色々準備がという感じで中々書き進められていないので、先にFromEvent再訪を。
4オーバーロード
FromEventはイベントをReactive Sequenceに変換するもの。これはオーバーロードが4つあります。
// ただのEventHandlerを登録する場合はハンドラの+-を書くだけ
// 例はWPFのWindowクラスのActivatedイベント
Observable.FromEvent(h => this.Activated += h, h => this.Activated -= h);
使う機会は少ないかな? 実際はEventHandler/EventArgsだけのものなどは少ないわけで。
// WPFのButtonなどり
Observable.FromEvent<RoutedEventArgs>(button1, "Click");
これはサンプルなどで最も目にすることが多いかもで、文字列でイベントを登録するもの。記述は短くなるのですが、動作的にはイベント登録時にリフレクションで取ってくることになるので、あまり推奨はしない。じゃあどうすればいいか、というと
// 第一引数conversionはRoutedEventHandlerに変換するためのもの、とにかく記述量大すぎ!
Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
h => h.Invoke, h => button1.Click += h, h => button1.Click -= h);
ハンドラの+-を自前で書くわけですが、EventArgsと一対一の俺々EventHandlerへの変換関数も必要になっています。これはnew RoutedEventHandler() などとしなくても、 Invoke と書くだけで良いようです。最後のオーバーロードは
// EventHandler<T>利用のものって本当に少ないんですよね、こちらを標準にして欲しかった
Observable.FromEvent<TouchEventArgs>(h => button1.TouchDown += h, h => button1.TouchDown -= h);
EventHandler<T>のものはスッキリ書けます。
コードスニペット
conversionが必要なFromEvent面倒くさい。それにしても面倒くさい。WPFのINotifyPropertyChangedほどじゃないけれど、やはり面倒くさい。ジェネリックじゃない俺々EventHandlerどもは爆発しろ!デリゲートはEventHandler<T>とFuncとActionがあれば他は原則不要(ref付きが必要とか、そういう特殊なのが欲しい時に初めて自前定義すればよろし)。と、嘆いてもしょうがない。何とかしなければ。以前はT4でガガガガッと自動生成してしまう方法を紹介しましたが、少し大仰な感があります。もう少しライトウェイトに、今度は、コードスニペットでいきましょう。
// 普通に使うもの
Observable.FromEvent<$EventHandler$, $EventArgs$>(h => h.Invoke, h => $event$ += h, h => $event$ -= h)
// 拡張メソッドとして定義する場合のもの
public static IObservable<IEvent<$EventArgs$>> $eventName$AsObservable(this $TargetType$ target)
{
return Observable.FromEvent<$EventHandler$, $EventArgs$>(
h => h.Invoke, h => target.$eventName$ += h, h => target.$eventName$ -= h);
}
この二つです。二つ目の拡張メソッドのものは、ええと、大体の場合は長ったらしくて面倒なので拡張メソッドに退避させるわけですが、それを書きやすくするためのものです。利用時はこんな形。
class Program
{
static void Main(string[] args)
{
var c = new ObservableCollection<int>();
var obs1 = Observable.FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(h => h.Invoke, h => c.CollectionChanged += h, h => c.CollectionChanged -= h);
var obs2 = c.CollectionChangedAsObservable();
}
}
public static class EventExtensions
{
public static IObservable<IEvent<NotifyCollectionChangedEventArgs>> CollectionChangedAsObservable<T>(this ObservableCollection<T> target)
{
return Observable.FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
h => h.Invoke, h => target.CollectionChanged += h, h => target.CollectionChanged -= h);
}
}
rxevent -> TabTab か、 rxeventdef -> TabTab というだけで、この面倒くさい(長い!)定義が簡単に書けます。おっと、スニペットファイルはXMLなのでこれだけじゃ動きませんね。ベタ張りだと長いので、ダウンロードはneuecc / RxSnippet / source – Bitbucketの二つからどうぞ。そうしたら、ツール->コードスニペットマネージャーで追加してやってください。
今回、スニペットはSnippet Designerで作成しました。Visual Studioと統合されているので非常に書きやすくてGood。コンパイルがしっかり通る、万全な雛形をコード上で作ったら右クリックしてExport As Snippet。スニペットエディタ上に移ったら、置換用変数を選択してMake Replacement。それだけで出来上がり。あとはプロパティのDescriptionとShortcutを書くだけ。楽すぎる。もうスニペットを手書きとか馬鹿らしくてやってられません。これだけ楽だと、ちょっとした面倒事を片っ端からスニペット化していけるというものですねん。
アタッチ、デタッチのタイミングを考えよう
Rxで意外と困るのが、いつアタッチされているのか、デタッチされているのか、よくわからなかったりします。慣れると分かってくるのですが、最初は存外厳しい。そういう時は悩まずに、デバッガを使おう!
こういったように、アタッチのラムダ式、デタッチのラムダ式にそれぞれ縄を張れば、いつ呼ばれるのか一目瞭然です。悩むよりも手を動かしたほうがずっと早い。
ところでRxのいいところは、イベントのデタッチが恐ろしく簡単になったことです。そのお陰で今まであまりやらなかった、そもそも考えすらしなかった、アタッチとデタッチを繰り返すようなイベント処理の書き方が発想として自然に浮かび上がるようになった。少し簡単に書けるようになった、という程度じゃあそんな意味がない。Rxのように極限まで簡単に書けるようになると、スタイルが一変して突然変異が発生する。ある意味これもまた、パラダイムシフトです。オブジェクト指向から関数型へ?いえいえ、C#から、Linqへ。
まとめ
2/11にRx v1.0.2856.0 releaseとしてアップデートが来てました。大量更新ですよ大量更新!正式入りしたから更新ペースがゆったりになるかと思いきや、その逆で加速しやがった!ちなみに破壊的変更も例によって平然とかけてきて、Drainというメソッドが消滅しました(笑) 何の躊躇いもないですね、すげー。
代わりに、Christmas Releaseの時に消滅してWP7と互換がなくなった!と騒いだPruneとReplayは復活しました(なんだってー)。というわけで、再びWP7との互換は十分保たれたという形ですね、ヨカッタヨカッタ。そんなわけで、常に見張ってないと分からないエキサイティングさが魅力のRx、是非是非使っていきましょう。
実践 F# 関数型プログラミング入門
- 2011-02-14
共著者の一人であるいげ太さんから献本のお誘いを受け、実践F#を献本頂きました。発売前に頂いたのですが、もう発売日をとっくに過ぎている事実!ど、同時期に書評が並ぶよりもずらしたほうがいいから、分散したんだよ(違います単純に遅れただけです、げふんげふん)
NuGetの辺りでも出しましたが、F#スクリプトは活用し始めています。いいですね、F#。普通に実用に投下できてしまいます、今すぐで、C#とかち合わない領域で。勿論、それだけで留めておくのはモッタイナイところですが、とりあえずの一歩として。実用で使いながら徐々に適用領域を増やせるという、なだらかな学習曲線を描けるって最高ぢゃないですか。
F#を学ぶ動機
最近流行りだから教養的に覚えておきたいとか、イベントがファーストクラスとか非同期ワークフローが良さそうなので知っておきたいとか、私はそんな動機ドリブンのつもりでしたが、そういう動機だと実に弱いんですね!そんな動機から発したもので完走出来たものは今まで一つもありません(おっと、積み本の山が……)。もっと具体的に甘受できる現金なメリットがないと駄目なんだ。そんな情けない人間は私だけではない、はず、はず。
というわけで、実際、動機付けが一番難しいのではないかと思います、「実践」するには。F#の場合「それC#で」という誘惑から逃れるのは難しく、正面から向かわないとでしょう。この図式と対比させられるJava-Scala間では、「それJavaで」とは口が裂けても言えなくて(Java……ダメな子)、学ぶことがそのままJVMの資産を活かしてアプリケーションを書けるというモチベーションに繋がりますが、C#は割とよく出来る子だから。そんなわけかないかですが、本書では、冒頭1章でF#手厚く説明されています。言語の歴史を振り返って、パラダイムを見て、F#とはどういう流れから生まれてきた言語なのか。丁寧だとは思います。
並列計算。マルチパラダイム。うーん、それだけだと請求力に欠けるよね、何故ならF#が関数型ベースのマルチパラダイム言語であるように、C#はオブジェクト指向型ベースのマルチパラダイム言語だから。
などとやらしくgdgdとしててもまあ何も始まらない。いいからコード書こうぜ!といった流れで2章で環境導入の解説(この解説は非常に役立ちでした、F# Interactiveのディレクティブ一覧や、__SOURCE_DIRECTORY__でパスが取れるとか、F#のソースコードの所在とか)で、あとは書く!と。なんとも雑念に満ちたまま読み始めたわけですが、読み始めるとグイグイ引きこまれました。なんというか、学ぶのに楽しい言語なんですよね、F#。
それと、Visual Studioに統合されたF# Interactiveがとんでもなく便利で。こいつは凄い。私、今までREPLって別にどうでもいいものと思っていたのですよ。コマンドプロンプトみたいな画面で一行一行打っていくの。REPLは動作が確認しやすくてイイとかいう話を耳にしては、なにそれ、そもそもメチャクチャ打ちづらいぢゃん、イラネーヨって。でもVSに統合されたF# Interactiveは、IDEのエディタで書くこと(シンタックスハイライト, 補完, リアルタイムエラー通知)とREPLの軽快さが合体していて、最強すぎる。しかもその軽快さで書いたコードはスクリプトとして単独ファイルで実行可能、だと……!F#スクリプト(fsx)素晴らしい。C#で心の底から欲しいと思っていたものが、ここにあったんだ……!
と、読み始めてたら、普通に楽しい言語だし、並列や言語解析といった大変なところに入らなくても実用的だしで、かなりはまってます。F#いいよF#。始める前に考えてた動機だとかなんて幻でしかなく、始めたら自然に心の奥から沸き上がってくるものこそ継続されるものだと、何だか感じ入ってしまったり。
パイプライン演算子
F#といったらパイプライン演算子。パイプラインは文化。と、いうわけかで、実際、私がよく目にしているF#のコードというのは基本|>で繋ぐ、という形であり、それが実にイイなー。などという憧憬はあるのでF#を書くとなったらとりあえずまずはパイプライン演算子の学習から入ったりなどしたりする。
このパイプライン演算子、書くだけならスッと頭に入るけれど、どうしてそう動くのかが今一つしっくりこなかった、こともありました。関数の定義自体は物凄くシンプルでたった一行で。
// |>は中置換なのとinlineなので正確には一緒ではないですが、その辺は本を参照ください!
let pipe x f = f x
おー、すんごくシンプル。シンプルすぎて逆にさっぱり分からない。型も良くわからない。困ったときはじっくり人間型推論に取り組んでみますと、まず、変数名はpipeであり、二引数を持つから関数。
pipe : x? -> f? -> return?
まだ型は分からないので?としておきます。右辺を見るとf x。つまりfは一引数を持つので関数。
x? -> (f_arg? -> f_ret?) -> return?
fの第一引数はxの型であり、fの戻り値が関数全体の戻り値の型となるので
x? -> (x? -> return?) -> return?
これ以上は型を当てはめることが出来ず、また、特に矛盾なくジェネリクスで構成できそうなので
'a -> ('a -> 'b) -> 'b
となる('aがC#でいう<T>みたいなものということで)。なるるほど、あまりに短いスパッとした定義なので面食らいますが、分かってしまえばその短さ故に、これしかないかしらん、という当たり前のものとして頭に入る、といいんですがそこまではいきませんが、まあ使うときは感覚的にこう書けるー、程度でいいので大丈夫だ問題ない。
このパイプライン演算子をC#で定義すると
public static TR Pipe<T, TR>(this T obj, Func<T, TR> func)
{
return func(obj);
}
比較するとちょっと冗長すぎはします。とはいえ、この拡張メソッドは、これはこれでかなり有益で、例えばEncodingのGetBytesなどを流しこんだりがスムーズに出来ます。例えばbyte[]の辺りは変換後に別の関数を実行して更に別の、という形で入れ子になりがちで、かといって変数名を付ける必要性も薄くて今一つ綺麗に書けなくて困るところなのですが、パイプライン演算子(モドき)さえあれば、
// ハッシュ値計算
var md5 = "hogehogehugahuga"
.Pipe(Encoding.UTF8.GetBytes)
.Pipe(MD5.Create().ComputeHash)
.Pipe(BitConverter.ToString);
// B6-06-FC-CF-DC-99-6D-55-95-B8-B6-75-DB-EE-C8-AE
Console.WriteLine(md5); // Pipe(Console.WriteLine)でもいいですね
気持ちよく、また入れ子がないため分かりやすく書けます。そのためC#でも最近は結構使ってます。ただ、Tへの拡張メソッドという影響範囲の大きさは、相当な背徳を背負います。というかまあ、共同作業だと、使えないですね、やり過ぎ度が高くなりすぎてしまって。パイプはC#のカルチャーでは、ない。うぐぐ。F#なら
"hogehogehugahuga"
|> Encoding.UTF8.GetBytes
|> MD5.Create().ComputeHash
|> BitConverter.ToString
|> printfn "%s"
このようになりますね。「|>」という記号選びが実に絶妙。ちゃんと視覚的に意味の通じる記号となっているし、縦に並べた際の見た目が美しいのが素敵。美しいは分かりやすいに繋がる。
"hogehogehugahuga"
|> (Encoding.UTF8.GetBytes >> MD5.Create().ComputeHash >> BitConverter.ToString)
|> printfn "%s"
翌々眺めると、関数が並んでるなら合成(>>演算子)も有りですね!びゅーてぃほー。
LINQ
関数型言語といったら高階関数でもりもりコレクション処理であり、そしてそれはLinq to Objectsであり。F#ではSeq関数群をパイプライン演算子を使って組み上げていきます。
[10; 15; 30; 45;]
|> Seq.filter (fun x -> x % 2 = 0)
|> Seq.map (fun x -> x * x)
|> Seq.iter (printfn "%i")
filterはWhere、mapはSelect、iterは(Linq標準演算子にないけど)ForEachといったところでしょうか。
new[] { 10, 15, 30, 45 }
.Where(x => x % 2 == 0)
.Select(x => x * x)
.ForEach(Console.WriteLine); // 自前で拡張メソッド定義して用意する
比べると、|>はドットのような、メソッドチェーンのような位置付けで対比させられようです。違いは、パイプライン演算子のほうが自由。例えば、拡張メソッドとして事前にかっきりと定義しなくてもチェーン出来る。だから、F#にSeq.iterなどがもしなかったとしても、
[10; 15; 30; 45;]
|> (fun xs -> seq {for x in xs do if x % 2 = 0 then yield x * x })
|> (fun xs -> for x in xs do printfn "%i" x)
その場でサクサクッと書いたものを繋げられたり、yieldもその場で書けたり(まあこれは内包表記に近くC#だと別にクエリ式でもいいし、といった感じで意味はあまりないですが)実に素敵。しかし、自由には代償が必要で。何かといえば、補完に若干弱い。シーケンスの連鎖では、基本的には次に繰り出したいメソッドはSeqなわけで、一々Seq.などと打つまでもなくドットだけでIntelliSenseをポップアップさせて繋げていくほうが楽だし、ラムダ式の記法に関してもfunキーワードが不要な分スッキリする。
この辺は良し悪しではなくカルチャーの違いというか立ち位置の差というか。C#のほうがオブジェクト指向ライクなシンタックスになるし、F#のほうが関数型ライクなシンタックスだという。同じ処理を同じようにマルチパラダイムとして消化していても、優劣じゃない差異ってのがある。これは、私は面白いところだなーと思っていて。どちらのやり方も味わい深い。
最後に
C#単独で見た時よりも.NETの世界が更に広く見えるようになったと思います。あ、こんな世界は広かったんだって。今後は、ぽちぽちとF# Scriptを書きつつ、FParsecにも手を出したいなあといったところですね。
大して書けはしませんが、それなりに書き始めて使い出せている、歩き始められているのは間違いなく本書のお陰です。「こう書くと良いんだよ」という、誘導がうまい感じなのでするっと入れました。どうしてもC#風に考えてしまって、それがF#にそぐわなくてうまくいかなくて躓いたりするわけですが、そこで、ここはこうだよ、って教えてくれるので。ちょっとした疑問、何でオーバーロードで作らないの?とかの答えは、載っています。
それと最後にクドいけれど、Visual Studio統合のF# Interactiveは本当に凄い。C# 5.0のCompiler as a ServiceはこれをC#にも持ってきてくれることになる、のかなあ。
Linq to ObjectsとLinq to Xmlを.NET 2.0環境で使う方法
- 2011-02-09
LinqのないC#なんて信じられない。カレールゥのないカレーライスみたいなものです。しかし.NET Framework 2.0ときたら……。幸いなことに、開発はVisual Studio 2008以降で、ターゲットフレームワークを2.0に、とすることでvarやラムダ式は使うことが可能です。拡張メソッドも小細工をすることで利用可能になります。といったことは、C# 3.0 による .NET 2.0 アプリケーション開発 - XNA で LINQ を使おう - NyaRuRuの日記に書いてありますねん。あと足りないのはLinqを実現するEnumerableクラス。その辺のことはNyaRuRuさんの日記の追記のほうにもありますが、LINQBridgeを使うことで拡張メソッドのための小細工なども含めて、全部面倒見てくれます。つまり、Linq to Objectsを.NET 2.0で使いたければLINQBridge使えばいい。以上。
というだけで終わるのもアレなので、Linq to Objectsが使えるなら、Linq to Xmlも使いたいよね?Linq to Xmlの強力さを一度味わったら二度とXmlDocumentも使いたくないし生のXmlReader/Writerも使いたくないし。でも残念なことにLINQBridgeはto Objectsだけ。となれば自前再実装、は無理なので、そこは.NET Frameworkのオープンソース実装のMonoからソースコードをお借りすればいいんじゃなイカ?
Monoのソースコードはmono/mono - GitHubで管理されています。私はGitじゃなくてMercurial派なのでGitは入れてないのでPullは出来ない、ので、普通にDownloadsからzipで落としました。クラスライブラリはmcs\class以下にあります。まずはEnumerableから行きましょう。
Linq to Objects
新規にクラスライブラリプロジェクトを立てて(プロジェクト名は何が良いでしょうかねえ、私はMono.Linqとしました)、ターゲットフレームワークを2.0に変更。そしてSystem.Core/System.Linqをフォルダごとドラッグアンドドロップでソリューションエクスプローラに突っ込む。そしてコンパイル!
ふむ。華麗に673件のエラーが出てますね。どうやらExpressionsがないそうで。ふーむ、つまりQueryable関連ですねえ。Enumerableだけだと関係ないので削除してもいいんですが、せっかくなのでIQueryableも使えるようにしましょう!System.Core/System.Linq.ExpressionsもEnumerableと同じようにフォルダごとコピー。更にコンパイル!
するとまだまだ346件エラー。FuncがないとかExtensionAttributeがないとか。.NET 3.0で追加された拡張メソッド用の属性とかFuncがないわけですね。というわけで、それらも持ってきてやります。ExtensionAttributeはmcs/class/System.Core/System.Runtime.CompilerServicesにあります。Enumerableだけの場合はExtensionAttributeだけでいいのですが、Queryableも使う場合は他のクラスも必要になるので、ここもフォルダごとコピーしましょう。
もう一つの、FuncとActionはSystem.Core/SystemにFuncs.csとActions.csとして定義されているので、これらも持ってきます。なお、FuncとActionは#ifディレクティブにより.NET4以下の場合は4引数までのものしか使えないようになっていますが、.NET4からの16引数までのものも使いたければ、#ifディレクティブを削除すればOK。
これでコンパイルするとエラーはたった4つになります!ってまだエラーあるんですか、あるんですねえ。HashSetがないとか。HashSetで、ああ、集合演算系が使ってるもんねえ、とティンと来たら話は早い。こいつはSystem.Core/System.Collections.Generic/HashSet.csにあります。みんなSystem.Core下にあるので捜すの楽でいいですね。
コンパイルしたらエラーが増えた!HashSet.csのエラーですね。CollectionDebuggerViewとMonoTODOという属性が無いそうだ。よくは分かりませんが、名前からして大したことはなさそうだしたったの5つなので、削除してしまっても問題なく動きます。ので削除してしまいましょう。と言いたいんですが、せっかくなのでこの二つの属性も拾ってきます。この二つはSystem.Coreにはないし、正直見たこともない属性なので何処にあるのか検討付きません。というわけで、まあ検索すれば一発です。
System.Data.Linq/src/DbLinq/MonoTODOAttribute.cs、って随分変なとこにありますね、とにかくこれと、corlib/System.Collections.Generic/CollectionDebuggerView.csを持ってくる。
これで完成。コンパイル通る。動く。ターゲットフレームワーク2.0でもLinq!何も問題もなくLinq!ラムダもvarも拡張メソッドもある!うー、わっほい!C# 3.0で.NET Framework 2.0という奇妙な感覚が非常に素敵です。
Linq to Xml
ではLinq to Xmlも用意しましょう。といっても、やることは同じようにmonoのコードから拝借するだけです。mcs/class/System.Xml.Linq下にあるSystem.Xml.Linq, System.Xml.Schema, System.Xml.XPathをフォルダごとコピー。そしてコンパイルすると!例によってエラー。
XNameが見つからないそうで。んー、あれ、XNameは普通にLinq to Xmlの一部では?と、いうわけでSystem.Xml.Linq/XName.csを見に行くと、あー、#if NET_2_0で.NET2.0環境下では全部消えてる!しょうがないので、ここではソースコードを直に編集して#ifディレクトブを除去しちゃいます。
コンパイルは通りましたが警告ががが。CLSCompliantがついてないってさ。というわけで、Properties/AssemblyInfo.csにCLSCompliantを付けてやります。
[assembly: System.CLSCompliant(true)]
これで完成。Linq to Xmlが使えるようになりました!マジで!マジで。
ライセンス
ライセンスは大事。FAQ: Licensing - Monoで確認するところ、クラスライブラリはMIT X11 Licenseになるようです。かなり緩めのライセンスなので比較的自由に扱えるのではないかと思いますが、詳細はFAQならびにMIT X11 Licenseの条項を個々人でご確認ください。
まとめ
Linqがあれば.NET 2.0でも大丈夫。もう何も怖くない。まあ、実際.NET 2.0のプロジェクトを今からやるかといえば、これは最終手段であって、まずやることは全力で反対して.NET 4を採用させることでしょう。既存のプロジェクトに対する改修でLinqを突っ込むのは、うーん、そんなこと許されるんですか!許されるなら平気でやります!大抵は許されない気がしますが!
さて、.NET 4の人でもこれを用意する利点はあります。学習用に。シームレスにLinqの中へデバッグ実行で突入出来ます。挙動の理解にこれより最適なものはないでしょう。ソースコードを眺めるもよし、ですしね。それと、これを機にMonoに触れる、機会はWindowsな私だとあまりないのですが、ソースコードに触れてみるのも結構幸せ感です。mono独自のクラス(Mono.Xxx)も色々あって面白そう。
余談ですが、Windows Phone 7やSilverlightであのクラスがない!という状況もMonoの手を借りることで何とかなるケースも。(何とかならないケースは、依存がいっぱいで沢山ソースを持ってこなければならない場合。さすがにそう大量となるとどうかな、と)
.NETコードへデバッグ実行でステップインする方法
デバッグ実行といえば、Microsoftもソースコードを公開しています。.NET Framework Librariesで公開されてます。.NET 4をDownloadすれば、その中にあります。やたら階層が深くて迷ってしまいますが、EnumerableとQueryableは Source.Net\4.0\DEVDIV_TFS\Dev10\Releases\RTMRel\ndp\fx\src\Core\System\Linq にあります。Symbolをモニョッとすれば、Visual Studio内でもステップインでデバッグ出来ますねえ。というわけで、その解説もついでなので。
まず、オプション->デバッグ->全般で「マイコードのみ設定を有効にする」のチェックを外します。そして、「ソースサーバーサポートを有効にする」のチェックを入れます。この二つだけ(多分)。ちなみに、「.NET Frameworkソースのステッピングを有効にする」というなんともそれっぽいオプションは罠なので無視しておきましょう。
あとはデバッグ->シンボルでダウンロードした先のフォルダを指定すればOK。私はZ:\RefSrc4\Symbolsになってます。これで、F11でめくるめく.NET Frameworkの無限世界にステップインで帰ってこれなくなります!やり過ぎると普通に鬱陶しくなるので、その他のオプション類とかで適度に抑制しながらやりませう。
Reactive Extensionsを学習するためのリソースまとめ
- 2011-01-26
1年半ほどDevLabsプロジェクトとして動いていたReactive Extensionsですが、ついにDevLabsを卒業し、Data Developer Center入りを果たしました。まずは、おめでとう!そして、これで安心してプロダクトに突っ込む事ができます。どれだけ有望そうに見えても、DevLabsのままではいつ消滅するか分からない。そういう先例(STM.NETがね、この死骸ページの虚しさ)もあった。また、Rxチームの前身はMicrosoft Live LabsでのVoltaというプロジェクトなわけですが、これは打ち切りでした!その魂はRxの可愛い鰻のアイコンで引き継がれ(Voltaからの継続使用)、ついに復活を遂げたという感動ストーリーがあるかないかは、特にない。それはともかくとして、私はこのアイコン好きです。
なお、Data Developer Centerはen-usとja-jpの格差が激しいので、日本語情報が充実するかは不透明というか多分しないというか、せめてja-jpからだと辿れないテクノロジが幾つかあるのだけは何とかして欲しい、RxもそうだしStreamInsightなんかも……。
学習リソースまとめ
Data Developer Centerのページが何だかごちゃごちゃしているので、少し情報を整理します。
ここのTutorials & ArticlesにあるCuring the asynchronous blues with the Reactive Extensions。これがハンズオンラボになっていて、基礎からチュートリアル式に触りながら学べるようになっています。まずは、これをこなすとRxではどのようにプログラミングするのか、どのような問題を解決できるのか、というのが見えるはずです。for .NETとfor JavaScriptがありますが、内容は同じです。両方を見ることで、Rxという、Linqという層を設けることで言語を超えた統一的な思考・統一的な記述が可能になっているという世界を垣間見ることができます。
続いて同ページの左下、Documentation HighlightsにあるDesign Guidelines。このドキュメントは非常に重要で、Rxにおける原理原則注意点実装の詳細が書かれているので、最初から読む必要はないのですが、ある程度Rxに慣れ親しんだら絶対に読むべき代物です。日本マイクロソフトは是非これを和訳してください!
RxチームのBart De Smetによるプレゼンテーション。Rxチームもあちこちでプレゼンやっていて、色々なビデオが残っているのですが、これが一番お薦め。導入から深いところまで過不足なく説明されていて大変分かりやすいし、グッとくるかと思われます。また、Channel 9のRxタグには動画がいっぱいあります。新機能を足すたびに動画で説明していたので英語圏のほうでは分かりやすいのだろうけれど、それ以外にとってはむしろ分かりづらいんだよこんちくしょう!を繰り広げてましたので良く分からない機能があったらとりあえず動画見に行くといいのではないかと思われます。
これは、お薦めしません。ほんと最初期に立てられたWikiで、皆が試行錯誤な段階で例が書かれた感じで、どうもわかってない感漂う今一つな例ばかり。いや、そりゃ初期なのでしょうがないのですが、如何せんそれから以後誰も追加も更新もしない寂れた廃墟なので、これ見て学ぼうとするのは少しシンドイ。(私も含めて)お前ら編集しろよって感じなのですが、どうしてこうなった……。
Rxについての情報交換は公式フォーラムで行われています。Rxチームの人も出てきますし、常連みたいな人が何人か張り付いてコードぺたぺた貼ってくれているので、サンプル集的な意味でもお薦め(前述のWikiよりも遥かに!)。何か使い方の分からないメソッドがあれば、検索に入れてみれば、きっと解説とコードが出てくるでしょう。
Windows Phone 7にはRxが標準搭載されていますので、当然MSDNにもリファレンスがあります。WP7の開発環境が日本語化してくれれば、念願のRxでの日本語IntelliSenseが!なのですが、まだなのですよね、残念。ちなみに、このWP7版は少し前のものがベースになっているので、必ずしも現在の最新版と一致するとは限りません。WP7版が出てから追加されたものは入っていないし、中には削られたものも……。なお、リファレンス自体はインストールディレクトリ Progam Files\Microsoft Cloud Programmability\Reactive Extensions にchmで転がってます。
あまりお薦めしません(笑) 初期は「メソッド探訪XX回」というフォーマットでやろうとしていましたが今はそれは放棄して完全に好き放題書いてます。壊滅的に整理されておらず、非常に分かりづらい。日本語でのちょっと突っ込んだ情報はここしかないというのは悲しいことです。一応、幾つか並べてみれば
Reactive Extensions入門 + メソッド早見解説表
linq.js & Reactive Extensions for JavaScript(RxJS)入門
RxとAsync CTPでの非同期のキャンセル・プログレス処理
Reactive Extensionsとエラーハンドリング
Rxを使って非同期プログラミングを簡単に
Reactive Extensionsの非同期周りの解説と自前実装
メソッド探訪第7回:IEnumerable vs IObservable
Rx(Reactive Extensions)を自前簡易再実装する
といったところでしょうか(全然絞れてないですね、あうあう)。ちょっと非同期にお熱だったので、非同期系に偏りがちな、特に近頃は。重要な○○の解説を出してないので早く書きたい!と思っている事項が、まだかなり沢山残っているので、今年も積極的に更新を続けたいと思っています。あとは私の Twitter:@neuecc で小さいコード書いて貼りつけたり、Rx関連な話題が目についたら反応したりはしてます。たまに。答えられることは答えられますが答えられないことは勿論答えられないので、私がダンマリとしてたら、こいつ分かってねーな、ということで、ぐぬぬぬ。もしくは風呂で寝てます。
その他のリソース
Reactive programmingというパラダイムで見ることが出来るので、他の言語での動きから分かることというのも、いっぱいあります。
F#から。この論文の著者のTomas Petricekは非常に有名な方で、そもそもSupervisor: Don Syme, Microsoft Research Cambridgeですしね。146ページとボリューム十分。ですが、私は「読んでません」よ。俺、F#を学んだらこの論文読むんだ……。とか思ってはやンヶ月。ようやく重い腰が上がってF#はぢめました。やってみるとF#は非常に面白く、更に教養として身につけておく、的なわけじゃなく今すぐ普通に実用的なので、実践 F#読んで一緒に学びましょうー。F#お薦め。
F#はファーストクラスイベントとして、デフォルトでフィルタリング程度なら可能になっているしで、むしろネイティブ対応だぜ的な勢いもありますね。少し触った感じだとmapとfilterぐらいなので、あくまで軽く、程度ではあるようですが。あと非同期ワークフローが実に興味深く有益な機能。
非同期ワークフローといったら、こちらも。C# 5.0に入るかも、なAsync機能のCTP。残念ながら英語版VSにしか入らないので簡単に触れはしないのですが……。Rx自体にもこのAsync CTP対応(GetAwaiterの実装)や、System.Linq.AsyncとしてAsyncEnumerableの実装などをしていて、Async CTPとは、切っても切れない密接さを見せているのですが、機能的にやはり被りつつあるので、どう上手く切り分けるのか、というのが難しいところです。Async CTPはもう少し突っつきたいのですが中々時間取れずな今現在。
ScalaでのReactiveの実装になるようです。Wiki -> Design Guidelines in Japanese は実にためになります。作者はC++でのLinq(酷い形容ですがC#erの戯言なので許して!) であるOvenのかた。Enumerable-Observableみたいなことを感じつつそこはしかしC++もScalaも分からないのでもごもご。
概要編のほかEvent編、Behavior編が。Haskellはよくわからなくても雰囲気は分かる(ぉ
こうして俯瞰してみても、Rxは実用に踏み出しているという点で、一歩抜けてるのではないかと思います。
Rxの入手方法・パッケージ・DLL内容について
対応プラットフォームはいっぱいありますが、Get Reactive Extensions for .NETからRx for All Platformsを選択すれば全部インストールされますんで、それでいいと思われます。又はNuGetに対応しているので、それを使うのも良いでしょう。
画像はNuGetに登録されているRxのもの。多すぎである。NuGetでも多すぎて困るのですが、普通にインストールした場合は、やたら小分けされた大量のDLLをインストールしてくるので、何をどう選べばいいのかさっぱり分かりません。というわけで、それの解説を少し。
System.CoreEx // Scheduler,Disposableなどの必須クラス群
System.Observable // Silverlightのみ
NuGetだとRx-Core。System.ObservableはIObserver/IObservableのインターフェイス定義で、.NET4なら標準入りしているので不要ですがSilverlightでは、こちらの参照も必要になります。
System.Reactive // Observable拡張メソッド
NuGetだとRx-Main。Observableの拡張メソッド群が入っているので、CoreExとReactiveがRxを使う際に必須となる参照と考えるといいです。
System.Reactive.ClientProfile // Stream周りをRxで非同期Readする補助拡張メソッド群
System.Reactive.ExtendedProfile // IHttpHandler使ってモニョモニョなサーバー利用での補助拡張メソッド群
System.Reactive.Testing // テストでのモックを作ったりする時に使いたいクラス群
この3つはオプション。ClientProfileはstreamにAsyncRead/AsyncReadLineというのが入っていて、そのまんまな挙動です。ちなみにAsyncReadLineはbyteをそのまんま切り出しているだけ(つまり日本語で使うとぶっ壊れる)のでまるっきり実用になってません。ふざけんな。というわけで使わないの推奨。
ただ、標準のRxだけだと、この辺のReadは自前で書かなければならなくて少々面倒くさいので、こういったあると便利系のものはあったほうがいい。恐らく、今後拡充されてここに追加されていくのではないかと思われます。私もReactive Extensions用のWebRequest拡張メソッドとか書いちゃってますが、標準でそういうの入ってくれれば手間なくて素敵。ExtendedProfileはよくわからないけどClientProfileと同じく作りは適当な気がする。Testingはあとで調べると思って放置中。でも概念は多分有益だと思うので、そのうちしっかり調べておきたい。
System.Interactive // Enumerable拡張メソッド(EnumerableEx)
こちらはRxとは独立していて、IEnumerableへの拡張メソッド群になっています。気の利いたのが色々入っていて便利。linq.jsにもここからパクッた、じゃなくて名前を統一させて入れたのが幾つかあります。あと、みんなが待望していたForEachがRunという名前で入っていますよ!それだけでもはや必須コンポーネントですね!
なお、突然Ixという表記を見たら、こちらのこと(もしくはIEnumerable全体)を指します。Interactive Extensions。用語としては、Pull-Enumerable-Interactive-IxとPush-Observable-Reactive-Rx となっています。紛らわしくよくわからなくなっても泣かない。
System.Linq.Async // AsyncCTP - Task連携
これもRxとは微妙に独立していて、中身はIAsyncEnumerable。AsyncCTPのTaskをLinq的に扱おうとするものです。Taskだと1個しか返せないので複数返せるように、という。私はイマイチこれの必要性が分からなかったりします。ぶっちゃけIObservableで良くて、で、IObservableのほうが色々融通が利くので。なんかもう出来るから作ったし入れたよ、といったRxチームのノリがここから伺えます。フットワーク軽くて、その姿勢は大好きだけど、混乱します。
最後に、Windows Phone 7では標準搭載されていて、System.ObservableとMicrosoft.Phone.Reactiveを参照することで使えるようになります。また、標準搭載とは別に、上記のような最新版も提供されています。標準搭載と最新版の違いですが、安定度は断然標準搭載版です。MSDNにドキュメントがあるのも良い。そして勿論、標準搭載なので配信する際の容量が嵩まない。では最新版のメリットはというと、勿論、機能面では豊富。また、デスクトップ版などとも完全にAPIの互換が取れます。ただ、バグの混入率は残念ながら結構高いので安定性は若干欠けます。
Rxの使える局面って?
ハンズオンラボやセッションのタイトルがCuring the asynchronous bluesであるように、やはり非同期に対しての適用に強い。クラウド時代のデータプログラミングに非同期は避けられない、それを解決するソリューションとしてのRx。しかしC# 5.0には組み込みAsync入っちゃうしRxJSだって、jQueryに1.5から組み込みでDeferredが入るので、将来的には強い特徴にはならないのですが、未来の前に現実。特にC# 5.0なんていつだよっていう。まあ、jQueryのDeferredよりもずっとセンス良いし(よく知らないで言ってる)、C#5.0 async/awaitよりも遥かに柔軟で強力だという印象を私は持っているので、直接競合するからってoutにはならないと思ってます。
非同期じゃなくイベントのほうは、様々な側面を見せるので一概に言うのは難しい。とりあえず時間を扱うテクノロジとしてのRxは、ベストな選択です。もはや生Timerなんて使ってられません。一例を出すと
// FileSystemWatcherのChangedイベントは一度の変更で複数のイベントが発行される面倒な仕様
var watcher = new FileSystemWatcher("C:\\", "test.txt") { EnableRaisingEvents = true };
// 1秒以内に連続して発生してきたイベントは無視して通すのは最後一つだけにする
Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>(
h => h.Invoke, h => watcher.Changed += h, h => watcher.Changed -= h)
.Throttle(TimeSpan.FromSeconds(1)) // Throttleは指定時間、値の通過がなかった場合に最後の一つを通す
.Subscribe(e => Console.WriteLine(e.EventArgs.FullPath));
FileSystemWatcherはファイルの変更を監視し、変更があった場合にイベントを飛ばしてくれる便利なクラスですが、例えば一度ファイルを保存する(変更がある)だけで、2,3個のイベントが同時に飛んできたりするなど処理が少し面倒だったりします。ググルとみんな困ってるよ。そういう場合にDateTimeを外部変数に保存しておいて比較を取るとかTimerで発火を調整するとか面倒くさいことをしなければなりません、が、RxならThrottleメソッドで一撃。
Rxではイベントの発生が時間軸上に並んでいるので、イベントに対して時間系魔法をかけることが出来ます。Throttle!Sample!Delay!TimeInterval!その他色々。未だかつてないほど時間を扱うのが容易になりました。面倒くさい印象のあったポーリングなども楽勝です。
他にイベントといったらセンサーの感知なども上げられます。Windows Phone 7に標準搭載された理由にもなるかもですが、センサーで飛んでくる大量のイベントのフィルタリング・加工にRxは実に都合が良い。また、物理デバイスがないとテストできない、あってもそもそもテストしにくいセンサー系APIですが、Rxを使うことでイベントのモック生成・差し替えが容易になります。
最後に挙げるイベント系といったら、GUI。ボタンクリックとか。これは、割とダメ。いや、ダメではないのだけど、GUIの基盤であるWPF/Silverlightががっちりとデータバインド中心に組まれているわけで、ここに無理やり割り込んでも、お互いの良さを消してしまう。フレームワークの根幹でサポートしている仕組みにRxは乗り入れられるほどのメリットを提供出来るか?というと、それは苦しいのではないかとも。Rxが標準入りすることで、WPFのフレームワーク根源からのサポートが入れば、また変わってきそう。これは未来の話。素敵な融合を夢みたい。
その他にデータの分配配信とか実行コンテキストの選択とか、メリット・特徴は色々あります。というか、なんでもかんでもが突っ込める基盤になってます。それらが渾然一体となって融合出来るというのがRxの本当の強さなのではないかと思っています。あらゆるソースをOrchestrate and Coordinateする。美しい……。
まとめ
正式になったから、それで何が変わったか、何が変わるかというとまだ不透明ではある。けれど、なにも変わらない気がします、とりあえず当面は。リリースパッケージが分かれるとかいうこともなく、なんか普通にDevLabsから引っ越ししてきました、というだけな雰囲気だし。そして、今まで通りに一月ぐらいの単位で新しいのリリースするサイクルを続けるのではないかなあ。まだ足したりなさそうだし。破壊的変更も、普通にたまにやってくるのではないかなあ。例えばIQbservableなんて、いつ名前が変わってもオカシクない。これは、QueryableObservableの略なのだけど、Observableと対比させるため同程度の長さの名前である必要があって、それでいてQbservableの本質をついた良い代案ってのは、出てこないよねえ。名前は難しい。
正式なテクノロジとして認められた、として、じゃあ今後どうなる?予想でしかないですが、まず.NET 4 SP1に入るか。これは、入らないんじゃないかなあ……。もし入ったとしても、変わらずData Developer Centerのページで最新版の開発と提供が続いていくでしょう。と、現時点でGetAwaiterが入ってたりするなど、C# 5.0 Asyncとの関係性も避けられない話なので、少なくとも.NET 4 SP1に入ったから開発終了には絶対にならないのではない。はず。.NET 5には入るでしょうが。確実に。
個人的にはWP7に搭載されている程度のものは.NET 4 SP1に入って欲しい(WP7のものは結構前のもの、リリース時期考えると当然ですが)ところなのですけれど、WP7のものとシグネチャ合わないのが出てきちゃってるのが、少々難しいかもなー、と、思うところ。一度フレームワーク本体に入れると変更が効かなくなるので、Rxチーム的にはもう少し手元に置いて弄りたいと思ってるような気がします。見ててそう思うというだけで、的外れな可能性はありますよ、あくまで私の予想なので悪しからず。
私としては、これで日本マイクロソフトにも動きが出て翻訳とか出たりしてくれると嬉しいのだけど。Rx Design Guidelinesなどは非常に重要な資料なので……。
ともあれ、使用に当たっての最大のリスク(テクノロジそのものが消滅する)というのがなくなったので、実プロジェクトに突っ込むことも十分検討できる範囲に入ってきました。実際に使ってみた、の記事が読めるようになってくと嬉しいですねえ。私は、ええと、機会があれば……。もうすぐ実際に突っ込める機会があるかもなので、なにか出せればいいか、な。サイの転がり方次第では分からないけれど。
ReactiveOAuth ver.0.2.0.0
- 2011-01-23
ReactiveOAuthを更新しました。今回よりNuGetに対応したのでReactiveOAuth、もしくはReactiveOAuth-WP7で入れられます。あとSilverlightもサポートしました。そして、盛大にバグってたのを修正しました。UrlEncodeをそのまんまの使ったのでRFC2396でRFC3986じゃないから「!」とかが含まれた文章を投稿すると死ぬ、という。あまりにも限りなく初歩ミスで、死ぬほど反省します……。
おまけに、この辺りがマズいのは出した当初から薄々感づいていたのですが、「あとで直す」とか思って延々と今まで放置していたので、もう本当に本当にごめんなさい。リリース用にzip固めるスクリプトも書いた(fsxで)し、ディレクトリ周りも整理したしで、負担なくリリースしてける態勢を整えたので、もう放置しません、今後他のプロジェクトでも。本当に、今回はあまりにも酷かったのでなんともかんともです。
一応、通信部分を以前書いたReactive Extensions用のWebRequest拡張メソッドに載せ替えたりしたりなど、中身も変えたんですが、うーん。解説する気力が折れたぽ。
とりあえずドッグフードな体制を整えるためにXboxInfoほげほげにとっとと積んでしまうかな……。
NuGetパッケージの作り方、或いはXmlエディタとしてのVisual Studio
- 2011-01-22
linq.js 2.2.0.2をリリースし、今回からNuGetでも配信されるようになりました!linq.js、もしくはlinq.js-jQuery、linq.js-Bindingsで入りますので、是非お試しを。ちなみに更新事項はちょっとBugFixとOrderByの微高速化だけです(本格的な変更は次回リリースで)。
さて、そんなわけでNuGetに対応したので、今回はNuGetのパッケージの作り方、公開のしかたについて解説します。やってみると意外と簡単で、かつNuGetいいよNuGet、と実感出来るので、特に公開するようなライブラリなんてないぜ!という場合でも試してみるのがお薦め(参照先としてローカルフォルダも加えられる)。普通に小さなことでも使いたくなります。そういえばでどうでもいいんですが、私は「ぬげっと」と呼んでます。GeForceを「げふぉーす」と呼ぶようなノリで。ぬげっと!
NuGetを使う
NuGetとは何ぞやか、大体のとこで言うと、オンラインからDLLとかライブラリをサクッと検索出来て、依存関係(これのインストールにはアレとソレが必要、など)を解決してくれた上で参照に加えてくれて、ついでにアップデートまで管理してくれるものです。Visual Studioの拡張として提供されているので、インストールはCodePlexからでもいいですが、VSの拡張機能マネージャからNuGetで検索しても出てきます。
インストールすると参照設定の右クリックに「Add Library Package Reference」というのが追加されていて、これを選択すると、トップの画像のようなNuGetの参照ダイアログが出てきます。最初NuGetが喧伝されていたときはPowerShellでのConsoleでしたが、ご覧のようにGUIダイアログもあるので安心。Consoleのほうが柔軟でパワフルな操作が可能なのですが(PowerShellを活かしたパイプやフィルタで一括ダウンロードとか)、普通に参照してーアップデートしてー、程度ならば別にGUIでも全然構いませんし。
.nupkg
NuGetを通してインストール/参照を行うと、プロジェクトのフォルダにpackages.configが生成されています。しかしこれはどうでもいいのでスルー。.slnのあるディレクトリにpackagesというフォルダも生成されていて、実体はこちらにあります。そこにはパッケージ名のフォルダが並んでいて、中には.nupkgという見慣れないものと、libもしくはContentというフォルダがあるのではないでしょうか……?
nupkgが最終的に作らなければならないもので、実態はただのzip。nupkgと同フォルダにあるlib/Contentはnupkgが展開された結果というだけです。というわけで、適当なパッケージをダウンロードして(linq.jsとかどうでしょう!)zipにリネームして解凍するとそこには……!
_relsとか[Content_Types].xmlとか、わけわからないものが転がってます。これらはノイズです。ようするに、System.IO.ZipPackageを使って圧縮してるというだけの話ですねー、恐らくこれらがある必然性はないです。ただたんに、.NET Framework標準ライブラリだけでZipの圧縮展開をしようとすると、こうしかやりようがなかったという、ただそれだけです。だから早くZipライブラリ入れてください(次辺りに入るらしい)。
大事なのは、.nuspecです。
.nuspec
.nuspec(中身はXml)に、バージョン情報やID、依存関係などが記載されています。といったわけで、自分で作らなければならないのは.nuspecです。これにパッケージしたいファイルや配置場所などの定義を記述し、それをNuGet.exeというものに通すと.nupkgが出来上がる、といった流れになっています。
nuspecの記述には、既存のnuspecを見るのが参考になるかもでしょう。但し、既存のnupkgを落として展開した結果のnuspecはNuGet.exeを通された時点で再加工されているものなので(パッケージ用のファイルの場所などの情報は消滅してる←まあ、絶対パスで記述可能だったりするので消滅してないと逆に困るわけですが)、100%そのまんま使える、というわけではないことには少し注意。
XmlエディタとしてのVisual Studio
では、nuspecを書いていく、つまりXmlを書いていくわけですがエディタ何使います?勿論Visual Studioですよね!Visual Studioは最強のXmlエディタ。異論はない。えー、マジXmlを補完無しで書くなんてシンジラレナーイ!小学生までだよねキャハハ。というわけで、補完全開で書きます。補完さえあればリファレンスなくても書けるし!IntelliSense最強説。
そのためにはスキーマが必要なわけですが、ちゃんと用意されています。NuGet Documentationの下の方のReferenceの.nuspec File Schemaにスキーマがリンクされています。CodePlexのソースリポジトリに直リンクというのが色々潔いですな。
さて、適当に新規項目でXmlを作ったら、メニューのXML->スキーマのダイアログを開き、nuspec.xsdを追加してやりましょう。
そして、とりあえずは<とでも打ってやると補完に!--とか!DOCTYPEなどなどに並んでpackageというものが。これを選択すると、一気にxmlns="http..."と名前空間まで補完してくれて!更に更に書き進めれば……。入力補完は効くし、必須要素が足りなければ警告出してくれるしでリファレンスとか何も見なくても書ける。
これなら打ち間違えでエラーなども出ないし、完璧。Xmlなんて普通のテキストエディタで気合で書く、とか思っていた時もありました。もうそんなの無理げ。VSバンザイ。なお、FirefoxのアドオンのGUI定義などに使うXULのSchemaなども当然適用出来る - XUL Schema ので、まあ、補完のないテキストエディタなんて使ってたら死んでしまうです。
なお、毎回毎回、スキーマの追加参照するのは面倒くさいという場合は、VSの標準スキーマ参照ディレクトリにxsdを直に突っ込んでおくと、楽になれます。オプション->テキストエディター->XMLでスキーマ、で場所が設定出来ます(デフォルトは %VsInstallDir%\xml\Schemas のようで)
パッケージング
nuspecのリファレンスは.nuspec File Formatに。IDとかVersionとかしか書かないし、項目も少ないしネストもないので書き方というほど書き方はないです。参考までにlinq.js-jqueryのnuspecは
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>linq.js-jQuery</id>
<version>2.2.0.2</version>
<title>linq.js - jQuery Plugin Version</title>
<authors>neuecc</authors>
<owners>neuecc</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Linq to Objects for JavaScript. This version is plugin integrated with jQuery.</description>
<language>en-US</language>
<licenseUrl>http://linqjs.codeplex.com/license</licenseUrl>
<projectUrl>http://linqjs.codeplex.com/</projectUrl>
<tags>linq javascript jquery</tags>
<dependencies>
<dependency id="jQuery" version="[1.3.1,]"></dependency>
</dependencies>
</metadata>
<files>
<file src="../../jquery.*" target="Content\Scripts" />
</files>
</package>
といった感じ。tagsはスペース区切りで入れておくと検索の時にそのワードで引っかかる。dependenciesは依存関係がある場合に記載。対象バージョンの書き方に関してはSpecifying Version Ranges in .nuspec Filesを見て書くべし。
filesは後述するNuGet.exe(コマンドラインツール)でのパッケージング時に参照するファイルを設定。何も記載しない場合はNuGet.exeの実行時引数で解決されるので、どちらでもお好みで、という感じですが、普通はこちらに書いておいたほうが楽な気はします。
ファイル指定のsrcではワイルドカードとして*が使えます。targetのほうは、nupkgにパッケージングされた時の階層の指定。この階層の指定は非常に重要です。「Content」の下に記載すると、プロジェクト直下に対象を配置します。この例では Scripts 下に「jquery.linq.js, jquery.linq.min.js, jquery.linq-vsdoc.js」が展開されることになっています。Scriptsというフォルダ名はjQueryに合わせてあります。勿論、対象は.csでも.txtでも何でも可。
では、普通のC#でのdllのように直下には.dllとか置いて欲しくないし参照設定にも加えて欲しい、という場合はどうするかというとtargetを「lib」にします。すると自動で参照設定に加えてくれます。この「Content」とか「lib」とかってのは名前で決め打ちされてますので、そーいうものだと思うことにしませう。
残るはパッケージ化。まずNuGetのトップからDownloadsタブ(Downloadボタンじゃなく)を選び、NuGet Command Line Toolをダウンロード。このNuGet.exeに対して引数「p ファイル名」でnuspecを指定してやればnupkgが出来上がります。私はnuspecと同じ階層にexeを置いて、ついでにbatファイルに
nuget p linq.js.nuspec
nuget p linq.js-jquery.nuspec
nuget p linq.js-bindings.nuspec
とか書いたのを置いて3個のパッケージを作ってます。この辺は好き好きで。
以上が基本的な感じです。ただたんに参照設定に加える、ファイルを配置する、以上のことをやりたい場合はインストール時にPowerShellスクリプトを実行、なども出来るので色々柔軟に手を加えられそうです。また、.NET Frameworkのバージョンによって参照させるファイルを変える、といったことはフォルダの構成を変えるだけで対応で可能です。例えば.Net4の場合は lib/Net4 に、Silverlightへは lib/SL4 に、といったような感じ。
といったルールなどはNuGet Creating a Packageを見るといいでしょう。また、バージョンのフォルダ分けがワケワカランという場合は既存のnupkgを展開してフォルダ構成を見てしまうのが手っ取り早いかも。Rx-AllやNewtonSoft.Jsonなどなど。
ローカル参照としてのNuGet
nupkgは別にオフィシャルのサーバーだけではなく、個人で立てたサーバーも参照出来ます。また、それだけでなく、ただたんにフォルダを指定するだけでもOKです。
作ったnupkgはこれでテスト可能です。また、頻繁に参照に加えるものはわざわざOnlineに繋げて取ってくるの重い!という場合では一度落としたnupkgをローカルに配置してしまうのも悪くないかもです。テストというだけじゃなく、これは普通に使えますね?今まで参照設定の共通化というとテンプレート作って、程度しかありませんでしたが、これならばいい具合に自由度の効いたものが出来そうです。社内/俺々フレームワーク置き場として活用できそう。
なお、現在は、ローカル参照のパッケージは、GUIのパッケージマネージャだとバージョンが上がってもUpdatesに現れなくてアップデート出来ません。Consoleならば現れるので、ふつーにバグのよう。で、報告されていましたし修正もされていた(今リリースされているのには反映されてないもよう)ので、次のリリースでは直ってるんじゃないかと思われます。
NuGet gallery
せっかく作ったパッケージはOnlineに乗せたいよね!NuGet galleryでパッケージの閲覧・登録・管理が出来ます。よーし、じゃあパパSign Inしちゃうぞー、Registerして、と。やってもいつまでたってもInvalid Passwordと言われてしまいます。あれれ……。
現在は管理者の承認が必要なようで David Ebbo: Introducing the NuGet gallery Registerしたら、Twitterの@davidebbo宛てにapproveして!と言わないとダメぽ。私は「Hi. I registered nuget.org, id is "neuecc" . plaease approve my account.」と、スペルミスしてる適当不躾な@を飛ばしたところ数時間後にSign In出来るようになりました。いつまで認証制なのかは不明ですが、いまんところそんな感じなようです。
まとめ
オンラインで簡単にDLLをインストール出来て便利!というのは勿論ありますが、ローカルで使ってみても存外便利なものです。ぬげっといいよぬげっと。NuPackからNuGetに名前が変わったときは、事情は分かる(NuPackは名前が被ってたらしい)けど、NuGetはないだろ、いくらなんでも。と、思ってたんですが、今は何かもうすっかり馴染んだ気がします。ぬげっと。ぬぱっけーじ。ぬすぺっく。
とりあえず私は今後作るのは勿論、今まで出してきたものも、順次対応させてNuGet galleryに登録していくのでよろしくお願いしま。勿論linq.jsもよろしくお願いしま。今回の2.2.0.1は表には何も更新されてない感じですが、裏側の体制を整えてました。
F#スクリプト(fsx)により、linq.jsからAjaxMinのdllを通し圧縮化と、ついでにjQueryプラグインを生成したり、これまたF#スクリプトでリリース用のZip圧縮をワンクリックで一発で出来るようにしたり。今まで手動でやっていた(そしてミスしまくってた!リリースから10分で撤回して上げなおしとか今まで何度やってきたことか)部分を完全自動化したので、もうミスはありません。そして、自動化されたことによりリリースはミス出すし面倒なので、もう少し色々やってからにするかー、とズルズル後回しにする心理がなくなりました。多分。きっと。NuGet対応したことだしで、当分はアクティブにアップデートしていきます!
そんなこんなでF#スクリプトはぢめました。素晴らしすぎる。あとVS2010とシームレスに完全統合されたF# Interactiveがヤバい。超凄い。こんなイイものがあったなんて……。というわけでF#書きたい欲とF#について色々書きたい欲が、ので次回は実践F#書評です、多分。いや、次々回かも。とりあえず近日中には。とにかくF#は絶対触るべきですね!
おまけ
と、いうわけで、生成を自動化します。F#スクリプトでdllのアセンブリ情報を読み込んでnuspecとnupkgを生成するものを書きました。
#r "System.Xml.Linq"
open System
open System.IO
open System.Diagnostics
open System.Reflection
open System.Xml.Linq
// 同ディレクトリにNuGet.exeを置いておくこと
// mainにはnuspecへの情報登録に利用するdllを、othersにはその他のものを;区切りで
// パスはこのスクリプトからの相対パス
let main = "bin/Release/ClassLibrary4.dll"
let others = ["bin/Release/System.CoreEx.dll"; "bin/Release/System.Interactive.dll"]
let pass p = Path.Combine(__SOURCE_DIRECTORY__, p)
let xn s = XName.Get(s)
// Load Assembly
type AssemblyInfo =
{ Id:string; Version:string; Description:string; Company:string }
let getAttr<'a> (asm:Assembly) =
asm.GetCustomAttributes(typeof<'a>, true) |> Seq.head :?> 'a
let info =
let asm = Assembly.LoadFrom(pass main)
let name = asm.GetName()
{ Id = name.Name;
Version = name.Version.ToString();
Description = (getAttr<AssemblyDescriptionAttribute> asm).Description;
Company = (getAttr<AssemblyCompanyAttribute> asm).Company }
let filename = info.Id + "." + info.Version + ".nuspec"
// Build .nuspec
let nuspec =
let file src = XElement(xn "file", XAttribute(xn "src", src), XAttribute(xn "target", "lib"))
let delBlank = function "" -> "_" | x -> x
XElement(xn "package",
XElement(xn "metadata",
XElement(xn "id", info.Id),
XElement(xn "version", info.Version),
XElement(xn "authors", delBlank info.Company),
XElement(xn "description", delBlank info.Description)),
XElement(xn "files",
file main,
others |> Seq.map file))
nuspec.Save(pass filename)
// output .nupkg
new ProcessStartInfo(
FileName = pass "NuGet.exe",
Arguments = "p " + filename,
RedirectStandardOutput = true,
UseShellExecute = false,
WorkingDirectory = __SOURCE_DIRECTORY__)
|> Process.Start
|> fun p -> Console.WriteLine(p.StandardOutput.ReadToEnd())
DLLからVersionとかDescriptionとか取れてしまう、.NETのアセンブリがサクッと読み込めるF#いいわー。これだけだと情報は最低限なので、tagとかも入れたければ下の方のXElementを生成している部分に直書きで挟んでやればヨシ。スクリプトの軽快さは良いですね。なので設定というか読み込むファイルも先頭のほうで普通に直書きで指定しちゃっております。
そのまま使ってもいいんですが、ビルド後に実行するコマンドラインに指定してやれば一切の手間暇なく常にフレッシュ。おお、素敵。