??演算子とNullObject
http://d.hatena.ne.jp/akiramei/20040810/p3
※リンク先が間違っていたので修正。
3年前のネタの焼き直し。
C#2.0から加わった??演算子ですが、nullだったらデフォルト値を返させることができます。ふと、NullObjectが使えたら、もう少し活用出来たんではないかと実験君。
using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; /// <summary> /// 仮想メソッドだけNullObject化。 /// 値型は0、参照型はnullを返す。 /// 使い物にならないクラス。 /// </summary> class NullObject { private static readonly Dictionary<string,Type> dict = new Dictionary<string,Type> (); public static T Get<T> () where T : class ,new () { Type t = typeof (T); Type wt; // クラス名にNullを付ける string name = "Null" + t.Name; // 既に作成済みのラップクラスか? if (dict.TryGetValue (name,out wt)) return Activator.CreateInstance (wt) as T; AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly ( new AssemblyName ("Null" + t.Name + "Assembly"),AssemblyBuilderAccess.Run); ModuleBuilder md = ab.DefineDynamicModule ( "Null" + t.Name + "Module"); TypeBuilder tb = md.DefineType ("Null" + t.Name,TypeAttributes.Class,t); tb.DefineDefaultConstructor (MethodAttributes.Public); foreach (MethodInfo mi in t.GetMethods ( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) { // 仮想関数のみオーバーライドする if (mi.IsVirtual) { // パラメータの型 ParameterInfo pis = mi.GetParameters (); Type types = new Type[pis.Length]; for (int i = 0; i < pis.Length; ++i) types[i] = pis[i].ParameterType; // メソッドのシグネチャはオーバーライド対象と同じ MethodBuilder mb = tb.DefineMethod (mi.Name,mi.Attributes, mi.CallingConvention,mi.ReturnParameter.ParameterType,types); ILGenerator il = mb.GetILGenerator (); Type rt = mi.ReturnParameter.ParameterType; if (rt.IsValueType) { if (rt != typeof(void)) il.Emit (OpCodes.Ldc_I4_0); } else if (rt != typeof (void)) il.Emit (OpCodes.Ldnull); il.Emit (OpCodes.Ret); // オーバーライド tb.DefineMethodOverride (mb,mi); } } wt = tb.CreateType (); dict[name] = wt; return Activator.CreateInstance (wt) as T; } } // テスト用クラス public class Foo { // 戻り値が値型 public virtual int Add (int a,int b) { return a + b; } // 戻り値がvoid public virtual void Bar() { Console.WriteLine("Bar"); } // 戻り値が参照型 public virtual string Baz() { return "Baz"; } } class Program { static void Main (string[] args) { // NullObject Foo nul = NullObject.Get<Foo> (); Console.WriteLine ("Begin Add"); Foo foo = new Foo (); Console.WriteLine ( (foo ?? nul).Add (10, 20)); foo = null; Console.WriteLine ( (foo ?? nul).Add (10, 20)); Console.WriteLine ("End Add"); Console.WriteLine ("Begin Bar"); foo = new Foo (); (foo ?? nul).Bar (); foo = null; (foo ?? nul).Bar (); Console.WriteLine ("End Bar"); Console.WriteLine ("Begin Baz"); foo = new Foo (); Console.WriteLine ( (foo ?? nul).Baz ()); foo = null; Console.WriteLine ( (foo ?? nul).Baz ()); Console.WriteLine ("End Baz"); } } /* 結果 Begin Add 30 0 End Add Begin Bar Bar End Bar Begin Baz Baz End Baz */
こんな感じ。NullObjectクラスが実践では使い物にならないので単なるネタどまりですが。せめてデフォルトコンストラクタが無いクラスやインタフェースに対応出来れば、まだ使い道があったのですが・・・(^^;