??演算子と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クラスが実践では使い物にならないので単なるネタどまりですが。せめてデフォルトコンストラクタが無いクラスやインタフェースに対応出来れば、まだ使い道があったのですが・・・(^^;