複雑なラムダ式
最近、ASP.NETMVCのソースコードを読んでいるのだが、そこでは複雑なラムダ式が多々でてくる。
今回はその一例としてアクションフィルタの処理(ControllerActionInvokerクラスのInvokeActionMethodWithFiltersメソッド)を自分なりに解釈し、コードを起こしたものを載せておく。
いくつか有用そうなテクニックを盗めたし、
ASP.NET MVCのソースコードは本当に勉強になります。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace LinqSample { public interface IActionParameter { object GetParameter(); } public class GenericParameter<T> : IActionParameter { private T val; public GenericParameter(T val) { this.val = val; } public object GetParameter() { return val; } } public class ActionResult { public object Value { get; set; } } public class ActionContext { public ActionResult CurrentValue { get; set; } public ActionContext() { CurrentValue = new ActionResult() { Value = "" }; } } public static class Action { public static ActionResult ConcatString(Func<ActionResult> action, IActionParameter param) { ActionResult current = action(); var newValue = current.Value.ToString() + param.GetParameter().ToString(); current.Value = newValue; Console.WriteLine(current.Value); return current; } public static ActionResult GetMaxValue(Func<ActionResult> action, IActionParameter param) { int tpResult; if (!int.TryParse(param.GetParameter().ToString(), out tpResult)) throw new ArgumentException("パラメーター不正(param)"); var current = action(); if (!int.TryParse(current.Value.ToString(), out tpResult)) throw new ArgumentException("パラメーター不正(action)"); var newValue = ""; if (Convert.ToInt32(current.Value) >= Convert.ToInt32(param.GetParameter())) newValue = current.Value.ToString(); else newValue = param.GetParameter().ToString(); Console.WriteLine("現在値:{0}、比較値:{1} => {2}を適用", current.Value, param.GetParameter(), newValue); current.Value = newValue; return current; } } public class ActionProcesser { public void DoAction(Func<ActionResult> acumrator, Func<Func<ActionResult>, IActionParameter, Func<ActionResult>> func) { var parameters = new List<IActionParameter>(); parameters.Add(new GenericParameter<int>(10)); parameters.Add(new GenericParameter<int>(20)); parameters.Add(new GenericParameter<int>(30)); parameters.Add(new GenericParameter<int>(40)); parameters.Add(new GenericParameter<int>(50)); parameters.Add(new GenericParameter<int>(60)); parameters.Add(new GenericParameter<int>(70)); parameters.Add(new GenericParameter<int>(80)); parameters.Add(new GenericParameter<int>(90)); parameters.Add(new GenericParameter<int>(100)); /* * 第1引数には、アキュムレーター値を返すFuncデリゲートを指定。 * 第2引数(アキュムレーター関数)には、アキュムレーターと同タイプのFuncを返すデリゲートを指定。 * こうすることで各パラメーターを外出しすることが可能になる。 * * アキュムレーター関数にデリゲートを渡すと即時実行されないようなので、最後に()をつけることでInvokeしている。 */ parameters.Aggregate(acumrator, (a, p) => func(a, p))(); } public void DoConcatString() { /* * アキュムレーターが保持する値をアキュムレーター自身が保持できない(デリゲートだから) * そこで、値保持用のContextクラスを使用することで対応した。 * */ var context = new ActionContext(); Func<ActionResult> acumrator = () => { return context.CurrentValue; }; /* * 実際の処理呼び出し。 * ラムダ式の中で、アキュムレーター関数の実処理を呼んでいる。 * こうすることで実処理のテスト容易になる。 * (これが一番の狙いか?) */ DoAction(acumrator, (a, p) => () => Action.ConcatString(a, p)); } public void DoCheckMaxValue() { var context = new ActionContext(); Func<ActionResult> acumrator = () => { context.CurrentValue.Value = "0"; return context.CurrentValue; }; DoAction(acumrator, (a, p) => () => Action.GetMaxValue(a, p)); } } }
Action.ConcatStringのテストコード。
[TestClass()] public class ActionTest { /// <summary> ///ConcatString のテスト ///</summary> [TestMethod()] public void ConcatStringTest() { Func<ActionResult> action = () => new ActionResult() { Value = "やまだ" }; IActionParameter param = new GenericParameter<string>("たろう"); ActionResult expected = new ActionResult() { Value = "やまだたろう" }; ActionResult actual; actual = LinqSample.Action.ConcatString(action, param); Assert.AreEqual(expected.Value, actual.Value); } /// <summary> ///GetMaxValue のテスト ///</summary> [TestMethod()] public void GetMaxValueTest() { Func<ActionResult> action = () => new ActionResult() { Value = "100" }; IActionParameter param = new GenericParameter<int>(99); ActionResult expected = new ActionResult() { Value = "100" }; ActionResult actual; actual = LinqSample.Action.GetMaxValue(action, param); Assert.AreEqual(expected.Value, actual.Value); } }