ホッケーを愛する日本全国の人々へ:COVERAGE(カバリッジ)~最終ステップ「5 on 5」 - livedoor Blog(ブログ)
必読。
一度で理解できないとあきらめず、理解できるまで何度でも読もう。アイス・インラインの区別、人数の差異は関係ない内容。
難しい・わからない・理解できない。それなら、理解できるまで考えればいいだけだから。
あぁ~、プレーオフもないし暇すぎる。来期な~。いいんだけど。いい年こいて精神論でホッケーするのはバカらしいと思う今日この頃。
ホッケーを愛する日本全国の人々へ:COVERAGE(カバリッジ)~最終ステップ「5 on 5」 - livedoor Blog(ブログ)
必読。
一度で理解できないとあきらめず、理解できるまで何度でも読もう。アイス・インラインの区別、人数の差異は関係ない内容。
難しい・わからない・理解できない。それなら、理解できるまで考えればいいだけだから。
あぁ~、プレーオフもないし暇すぎる。来期な~。いいんだけど。いい年こいて精神論でホッケーするのはバカらしいと思う今日この頃。
PostSharp - Bringing AOP to .NET
AOPフレームワークと言われるんだって。
ボスの大嫌いなRemotingのProxy(.NET Framework 2.0 コア機能解説 ~ 第 1 回 .NET リモーティング ~)でのフックや(A basic proxy for intercepting method calls (Part –1) - Mehfuz's WebLog)、Invokerを必ず呼び出すASP.NET MVCでのFilterAttribute達のどっちかが一般的なのかと思ってたけど、世の中スゴイ事思いつく人たちがいるモンで、ビルド後にILを書き換えてフックポイントを追加してしまえばいいじゃないかという発想で作られてます。まさにEmitにを超える黒魔術。
ドキュメントに書かれてる実装パターン。
int MyMethod(object arg0, int arg1) { OnEntry(); try { // Original method body. OnSuccess(); return returnValue; } catch ( Exception e ) { OnException(); } finally { OnExit(); } }
Reflectorなんかでビルド後のアセンブリを見ると上記のパターンにそったものが出力されてるのが確認出来ます。
確かに~、確かに~。
ドキュメントを見つつ、どんなことが出来るのかチラッとコード書いてみたんです。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using PostSharp.Laos; namespace TestPostSharp { class Program { [ThirdAspect] private static int _intFldValue; static void Main(string[] args) { Console.WriteLine("Start program."); PrintMessage("Hello, World! - first"); PrintMessage("Hello, World! - second"); PrintMessage("Hello, World! - third"); var res = Invocation(100,"まじか!",DateTime.Now); Console.WriteLine("res = {0}",res); _intFldValue = 100; Console.WriteLine("Get field {0}", _intFldValue); Console.WriteLine("End program."); Console.ReadKey(); } [FirstAspect] static void PrintMessage(string message) { Console.WriteLine(message); } [SecondAspect] static int Invocation(int p1, string p2, DateTime dateTime) { Console.WriteLine("何かか実行されるようだ"); Console.WriteLine("{0} - {1} - {2}",p1,p2,dateTime); return p1; } } [Serializable] public class FirstAspectAttribute:OnMethodBoundaryAspect { public override void OnEntry(MethodExecutionEventArgs eventArgs) { Console.WriteLine(eventArgs.Method.Name + " - OnEntry"); base.OnEntry(eventArgs); } public override void OnExit(MethodExecutionEventArgs eventArgs) { Console.WriteLine(eventArgs.Method.Name + " - OnExit"); base.OnExit(eventArgs); } public override void OnSuccess(MethodExecutionEventArgs eventArgs) { Console.WriteLine(eventArgs.Method.Name + " - OnSuccess"); base.OnSuccess(eventArgs); } public override void OnException(MethodExecutionEventArgs eventArgs) { Console.WriteLine(eventArgs.Method.Name + " - OnException"); base.OnException(eventArgs); } public override void RuntimeInitialize(System.Reflection.MethodBase method) { Console.WriteLine(method.Name + " - RuntimeInitialize"); base.RuntimeInitialize(method); } public override void CompileTimeInitialize(System.Reflection.MethodBase method) { Console.WriteLine(method.Name + " - CompileTimeInitialize"); base.CompileTimeInitialize(method); } public override bool CompileTimeValidate(System.Reflection.MethodBase method) { Console.WriteLine(method.Name + " - CompileTimeValidate"); return base.CompileTimeValidate(method); } } [Serializable] public class SecondAspect : OnMethodInvocationAspect { public override void OnInvocation(MethodInvocationEventArgs eventArgs) { Console.WriteLine("Calling {0}", eventArgs.Method.Name); var args = eventArgs.GetArgumentArray(); if (args[0].GetType() == typeof(int)) args[0] = (int) args[0] * 2; eventArgs.Proceed(args); var res = eventArgs.ReturnValue; eventArgs.ReturnValue = (int) res + 100; } } [Serializable] public class ThirdAspect : OnFieldAccessAspect { public override void OnGetValue(FieldAccessEventArgs eventArgs) { Console.WriteLine("Getter {0} = {1}", eventArgs.FieldInfo.Name, eventArgs.StoredFieldValue); base.OnGetValue(eventArgs); } public override void OnSetValue(FieldAccessEventArgs eventArgs) { Console.WriteLine("Setter {0} = {1} -> {2}", eventArgs.FieldInfo.Name, eventArgs.StoredFieldValue, eventArgs.ExposedFieldValue); var value = eventArgs.ExposedFieldValue; eventArgs.ExposedFieldValue = (int) value*2; base.OnSetValue(eventArgs); } } }
最初から用意されてる便利クラス。
それぞれ、だいたい名前の通り。引数や戻り値を書き換えたりも出来るよ!IOn~のインターフェースも用意されてるから、用意されてるのが気に入らないなら最初から実装してしまうのも可能。OnExceptionAspectについてはその他のクラスのOnExceptionでも取れるから単体で使う場面はそんなに無かったりするのかな~。どうなんでしょう。
属性クラスとしてAspectクラスを実装して、クラスやメソッド、フィールドに指定してビルドするのがオーソドックスな使い方だと思うけど、更に[assembly:自作Aspectクラス(~)]でBCLにゴッソリ指定出来たりするのが恐ろしい。
めっぽう気になってしかたがないのがCompositionAspect。これってDIなんですかね?
フック出来るタイミングや、シリアライズのカスタマイズやら、プラグイン(ビルド後の処理時にだと思われるけどよく分かってない)、msbuild実行されないASP.NETの対応なんかもあって、イロイロ遊べそうな感じデス!
前にも、この方法について考えたことがあって、その時はストラテジパターンを使ってDelegateでのコマンド振り分けでの実装をしてたんですが、少し前に違う方法を実装してるのを見て悔い改めたんです。
MVCによく似合う方法は、属性ベースで対象となるアクションを振り分ける(判定する)方法ですよね。
AcceptVerbsでHTTP Method毎に処理を振り分ける事ができるのを上手く利用して、Submitの値毎に処理を振り分けるためにActionMethodSelectorAttributeを派生したSubmitCommandAttributeというのを定義していました。
using System; using System.Reflection; using System.Web.Mvc; namespace MvcApplication2.Controllers { [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class SubmitCommandAttribute : ActionMethodSelectorAttribute { private string _submitName; private string _submitValue; private static readonly AcceptVerbsAttribute _innerAttribute =
new AcceptVerbsAttribute(HttpVerbs.Post); public SubmitCommandAttribute(string name) : this(name, string.Empty) { } public SubmitCommandAttribute(string name, string value) { _submitName = name; _submitValue = value; } public override bool IsValidForRequest(ControllerContext controllerContext,
MethodInfo methodInfo) { if (!_innerAttribute.IsValidForRequest(controllerContext, methodInfo)) return false; // Form Value var submitted = controllerContext.RequestContext
.HttpContext
.Request.Form[_submitName]; return string.IsNullOrEmpty(_submitValue) ? !string.IsNullOrEmpty(submitted) : string.Equals(submitted, _submitValue,
StringComparison.InvariantCultureIgnoreCase); } } }
これだけなんですけども...。HttpPostのコードを参考に内部にAcceptVerbsAttributeを保持してPOST時のみをIsValidにしてますが、DeleteとPutも必要なら要修正。
using System.Web.Mvc; namespace MvcApplication2.Controllers { public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { return View(); } [ActionName("Different")] [SubmitCommand("DoSave")] public ActionResult DifferentSave() { TempData["message"] = "saved! - defferent"; return View("Index"); } [ActionName("Different")] [SubmitCommand("DoDelete")] public ActionResult DifferentDelete() { TempData["message"] = "deleted! - defferent"; return View("Index"); } [ActionName("Same")] [SubmitCommand("DoSubmit","保存")] public ActionResult SameSave() { TempData["message"] = "saved! - same"; return View("Index"); } [ActionName("Same")] [SubmitCommand("DoSubmit","削除")] public ActionResult SameDelete() { TempData["message"] = "deleted! - same"; return View("Index"); } } }
コントローラでこんな感じにアクションを定義しておいて、Viewを以下のようにしておきます。
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Index</title> </head> <body> <h1><%= TempData["message"] ?? "Click some button" %></h1> <h2>異なるnameのsubmit</h2> <% using (Html.BeginForm("Different", "Home")) { %> <input type="submit" name="DoSave" value="保存" /><br /> <input type="submit" name="DoDelete" value="削除" /> <% } %> <h2>同一nameでValueの異なるsubmit</h2> <% using (Html.BeginForm("Same","Home")) { %> <input type="submit" name="DoSubmit" value="保存" /><br /> <input type="submit" name="DoSubmit" value="削除" /> <% } %> </body> </html>
動かしてみると、最初に↓。ボタンが4種類出てきます。上2つはnameとvalueの異なるsubmitで、下2つがnameが同じでvalueが違うsubmit。
上から順にボタンを押したのが↓。
ちゃんとTempDataの値が違うのが確認できますね(ページ上部のH1)。
で、ココまで書いときながら、前にどこで見たのかを検索して探し出してみてショック。
ASP.NET MVC – Multiple buttons in the same form - David Findley's Blog
まんま、同じになるという大失態。やっぱり年始からこんなコードを書いてて先が思いやられる...。
いけてるINotifyPropertyChangedの実装は、結構遅かった - かずきのBlog@Hatena
正月から興味深いエントリですね。
遅くなるのは毎回のCompileと引数に渡してるExpression生成ですかね。Compileはキャッシュしてしまえば毎回生成する必要は無いですが、そもそも以下のようにすることでCompileそのものが不要に。
public static void Raise2<TResult>(this PropertyChangedEventHandler _this, Expression<Func<TResult>> propertyName) { // ハンドラに何も登録されていない場合は何もしない if (_this == null) return; // ラムダ式のBodyを取得する。MemberExpressionじゃなかったら駄目 var memberEx = propertyName.Body as MemberExpression; if (memberEx == null) throw new ArgumentException(); // () => NameのNameの部分の左側に暗黙的に存在しているオブジェクトを取得する式をゲット var senderExpression = memberEx.Expression as ConstantExpression; // ConstraintExpressionじゃないと駄目 if (senderExpression == null) throw new ArgumentException(); var sender = senderExpression.Value; // 下準備が出来たので、イベント発行!! _this(sender, new PropertyChangedEventArgs(memberEx.Member.Name)); }
こうすると、実行時間は...。
ノーマル:5ms
イケテル:3700ms
カイリョ:84ms
※平均値じゃないですが。
これを更に高速化しようとイロイロと試してみたんですが、どうやらExpression<Func<T>>の生成に時間を取られる模様。
Raiseを呼び出さずにローカル変数として
Expression<Func<string>> expr = () => Name;
と、しただけで実行時間はほぼ同じくらいかかるし。全く素敵じゃなくなるけど、↓こんな感じにしておくとちゃんと早い。
public class KairyoEmp : INotifyPropertyChanged { private string _name; private static Expression<Func<string>> _expr; public string Name { get { return _name; } set { _name = value; if (_expr==null) _expr = () => Name; PropertyChanged.Raise2(_expr); } } public event PropertyChangedEventHandler PropertyChanged; }
ノーマル:5ms
カイリョ:10ms
うむ。意味のないコードになってしまいました。年始からこれじゃ先が思いやられる...。
買っちゃった。TOUR大好きなのに乗り換え。
衝動買いしちゃった。いつもの軟骨が邪魔をするかと思いきや、今のところ特に痛いところもなく。
まぁ、ねじ山が最初からつぶれてたり(スズキ君にネジもらったから問題なし。ありがとう!)、紐止めの部分がいきなり外れたりして、ドキドキしたけど新しい靴はいいもんだね。
モデルチェンジで安くなってたからトップモデルを安く買えたのは良かったけど、ウィールが最悪。雨で濡れた上を滑ってるような気になるくらいのグリップの無さ。マイクロベアリングの良さを生かすにはウィール交換が高くつきすぎるからなんかヤダしな~。とりあえず、ノーマルのベアリングとウィールで乗り切る事にしようと思うところです。
履き心地に慣れる前に戸塚が無くなるのは痛いけど、保土ヶ谷でまったりホッケーでならそうかな。ところで、ずいぶん更新してなかったのね。ブログ。さーせん。
internalクラスのテストってどう書くのがセオリー?まさかReflectionってことはないよね?今まで知らなくてズルッコしてpublicにしてたんだけど、まさかこんな簡単な方法があったとは。
Easy way to use TDD with internal classes - Sean McAlinden's Blog
常識なんすか!?
namespace Samples { internal class TestInternal { public string Test() { return "TestInternal method"; } } internal static class TestInternalStatic { public static string Test() { return "TestInternalStatic method"; } } }
普通にこんなclassを作ると、Testクラスで↓こうなるもんね。
でも、AssemblyInfo.csに↓この行を追加。
[assembly: InternalsVisibleTo("Samples.Tests")]
※テストプロジェクトのアセンブリ名。今回はSamples.Testsっていうのを作ったのでこの名前。
コンパイルエラーも出ないし、テストも普通に通る。
なんかもう今までのコードを全部書き直したくなってきた...。
ちょっと試しにCacheDependencyを書いてたんだけど。
using System; using System.Web.Caching; using System.Timers; namespace Sample
{ public class TimerCacheDependency:CacheDependency { private readonly Timer _timer; public TimerCacheDependency(DateTime expireTime) { _timer = new Timer((expireTime - DateTime.Now).TotalMilliseconds); _timer.Elapsed += (sender, e) => NotifyDependencyChanged(this, EventArgs.Empty); _timer.Start(); } protected override void DependencyDispose() { if (_timer == null) return; _timer.Dispose(); } } }
絶対時間でExpireさせるCacheDependency。 Timer使っていいのかな。なんかキャッシュに同時に1万とかデータ入れると1万のTimerだよね...。
Comparing the Timer Classes in the .NET Framework Class Library
特になにかリソースを消費しまくるってわけではない?
Pro ASP.NET 3.5 in C# 2008 - Google ブックス
こっちはSystem.Threading.Timer使ってるな...。ぶふ~。
https://github.com/takepara/MvcVpl ↑こちらにいろいろ置いときました。 参加してくださった方々の温かい対応に感謝感謝です。