読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

C#3.0の拡張メソッドでMix-in的な何か。それは写輪眼でコピー忍者なカカシ先生。

プログラミング C#3.0

元ネタ

空のインターフェースに拡張メソッド(Extension Methods)を足すと
それって Mix-in クラスだ(module とみなせる)よね、ということだったんだな。

C# 3.0 で拡張メソッドによる Mix-in 的ななにか - いげ太のブログ


確かにこれは、Mix-in的な何かっぽいですね。C#でもMix-inぽいものができるなんてオモローです。
これってつまり、Mix-inクラスっぽい「拡張メソッドを持った空のインターフェイス」を足したり引いたりすることで、
動的に複数の実装を足したり引いたりできちゃうってことですよね。まぁ、引くケースってのはあまりないように思うけど。


つまり、この仕組みをお子様に分かりやすいように説明をするならば、それは「写輪眼でコピー忍者なカカシ先生」です。
はてさて・・・、NARUTOをご存知ない方には、まったく意味がわからないと思いますが、このまま続行します。
見所は、カカシ先生にMix-inされる写輪眼インターフェイスとそのエクステでしょうか。ではどうぞ。


霧隠れの抜け忍、桃地再不斬を作成

まず、カカシ先生に忍術をコピーされちゃう忍者、霧隠れの抜け忍、桃地再不斬を作成します。
なんだか、非常になつかしいですwww

    /// <summary>
    /// 霧隠れの抜け忍、桃地再不斬
    /// </summary>
    public class MomochiZabuza : IZabuza {}

    /// <summary>
    /// 桃地再不斬が使える忍術
    /// </summary>
    public interface IZabuza :  IMizubunsinNoJyutsu,
                                IKirigakureNoJyutsu,
                                ISuitonSuiryudanNoJyutsu,
                                ISuitonDaibakufuNoJyutsu{}
    /// <summary>
    /// 霧隠れの術
    /// </summary>
    public interface IKirigakureNoJyutsu{}
    
    public static class IKirigakureNoJyutsuExtensions
    {
        public static void KirigakureNoJyutsu(this IKirigakureNoJyutsu self)
        {
            Console.WriteLine("霧隠れの術");
        }
    }

    /// <summary>
    /// 水分身の術
    /// </summary>
    public interface IMizubunsinNoJyutsu {}

    public static class IMizubunsinNoJyutsuExtensions
    {
        public static void MizubunshinNoJyutsu(this IMizubunsinNoJyutsu self)
        {
            Console.WriteLine("水分身の術");
        }
    }

    /// <summary>
    /// 水遁・水龍弾の術
    /// </summary>
    public interface ISuitonSuiryudanNoJyutsu { }
    
    public static class ISuitonSuiryudanNoJyutsuExtensions
    {
        public static void SuitonSuiryudanNoJyutsu(this ISuitonSuiryudanNoJyutsu self)
        {
            Console.WriteLine("水遁・水龍弾の術");
        }
    }

    /// <summary>
    /// 水遁・大瀑布の術
    /// </summary>
    public interface ISuitonDaibakufuNoJyutsu { }

    public static class ISuitonDaibakufuNoJyutsuExtensions
    {
        public static void SuitonDaibakufuNoJyutsu(this ISuitonDaibakufuNoJyutsu self)
        {
            Console.WriteLine("水遁・大瀑布の術");
        }
    }


写輪眼でコピー忍者なカカシ先生を作成

つづきまして、写輪眼でコピー忍者なカカシ先生を作成します。
写輪眼では、血継限界能力による忍術はコピーすることができないということで、
忍術にメタデータを持たせようということで、血継限界カスタム属性を作成します。
今回の実装には直接は関係ありませんが、写輪眼も血継限界の能力なので、血継限界カスタム属性を付加しておきます。

    /// <summary>
    /// 血継限界カスタム属性
    /// </summary>
    [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
    public class KekkeiGenkaiAttribute : Attribute { }
    /// <summary>
    /// 写輪眼でコピー忍者なカカシ先生
    /// </summary>
    public class HatakeKakashi:ISharingan,IRaikiri{}

    /// <summary>
    /// 雷切
    /// </summary>
    public interface IRaikiri{}

    public static class IRaikiriExtensions
    {
        public static void Raikiri(this IRaikiri self)
        {
            Console.WriteLine("雷切");
        }
    }

    /// <summary>
    /// 写輪眼
    /// </summary>
    [KekkeiGenkai]
    public interface ISharingan{}

    public static class ISharinganExtensions
    {
        public static object CopyNinjyutsu(this ISharingan self,object copy)
        {
            return ExecuteMixin(self.GetType(),copy.GetType());
        }

        /// <summary>
        /// ラップクラスの型を保持するコレクション
        /// </summary>
        private static Dictionary<string, Type> dicWrapType = new Dictionary<string, Type>();

        private static object ExecuteMixin(Type original,Type copy) 
        {
            // コピー対象
            Type t = copy;

            // オリジナル
            Type o = original;  

            // コピーした結果
            Type wt;

            //ラップクラス名
            string mixinClassName = "Mixin" + t.Name;

            if (dicWrapType.TryGetValue(mixinClassName, out wt))
            {
                //既に作成済みのラップクラスがあれば、それを返す
                return Activator.CreateInstance(wt);
            }

            //動的アセンブリを定義
            AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Mixin" + o.Name + "Assembly"), AssemblyBuilderAccess.Run);

            //動的モジュール定義
            ModuleBuilder md = ab.DefineDynamicModule("Mixin" + o.Name + "Module");

            //動的ラップクラスのインスタンスを定義
            TypeBuilder tb = md.DefineType("Mixin" + o.Name, TypeAttributes.Class, o);

            //動的ラップクラスのデフォルトコンストラクタを定義 
            tb.DefineDefaultConstructor(MethodAttributes.Public);

            foreach (Type ifitem in t.GetInterfaces())
            {
                // 血継限界はコピーできませんよ
                KekkeiGenkaiAttribute[] items = (KekkeiGenkaiAttribute[])ifitem.GetCustomAttributes(typeof(KekkeiGenkaiAttribute), false);
                if (items.Count<KekkeiGenkaiAttribute>() > 0) continue;
               
                bool f = false;
                foreach (Type ci in GetAllInterfaces(ifitem))
                {
                    if (ci.GetMethods().Count<MethodInfo>() != 0) f = true;
                }
                if (!f) tb.AddInterfaceImplementation(ifitem);
            }

            tb.SetParent(original);

            //ラップクラスの型を生成
            wt = tb.CreateType();

            //Dictionaryにラップクラスを登録
            dicWrapType[mixinClassName] = wt;

            //ラップクラスを生成し返す
            return Activator.CreateInstance(wt);
        }
    
        private static List<Type> GetAllInterfaces(Type t) 
        {
            List<Type> lt = new List<Type>();
            lt.Add(t);
            foreach (Type ifitem in t.GetInterfaces()) {
                lt.Add(ifitem);
                GetAllInterfaces(ifitem);
            }
            return lt;
        }
    }


写輪眼による忍術のコピー

では、実際に写輪眼で、再不斬の忍術をコピーしてみましょう。

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // はたけカカシ
            var kakashi = new HatakeKakashi();

            // 桃地再不斬
            var zabuza = new MomochiZabuza();

            // 写輪眼で相手の忍術をコピーする
            var copyKakashi = kakashi.CopyNinjyutsu(zabuza) as IZabuza;

            // コピーした忍術を試す
            copyKakashi.KirigakureNoJyutsu();
            copyKakashi.MizubunshinNoJyutsu();
            copyKakashi.SuitonSuiryudanNoJyutsu();
            copyKakashi.SuitonDaibakufuNoJyutsu();

            // 勿論、自分の忍術も使えます
            ((HatakeKakashi)copyKakashi).Raikiri();

            Console.ReadLine();
        }
    }
}


実行結果

霧隠れの術
水分身の術
水遁・水龍弾の術
水遁・大瀑布の術
雷切

はい。とゆーことで、思惑通りカカシ先生が写輪眼で再不斬の忍術をコピーできていますね。
以上、C#3.0の拡張メソッドでMix-in的な何か。それは写輪眼でコピー忍者なカカシ先生でした。お粗末。