F# には TypeProvider というのがあって、動的にクラスを作り、それを F# のコードで扱えるものです。
F# Data: JSON 型プロバイダー
https://fsharp.github.io/FSharp.Data/ja/library/JsonProvider.html
でもって、C# には TypeProvider がないんので F# が羨ましかったり、いやそう言うなら F# で組めばいい訳ですが。以前、F# の XAML の TypeProvider を作ったときに、そのまま Xamarin の PCL には持って行けなくて諦めた覚えがあるんですが。今だともうちょっと工夫できるかも、ってことで、C# で TypeBuilder を使ってみます。
雑に JsonProvider を作る
JSON の文字列を渡して、それをプロパティに持つクラスを作ります。実は、Newtonsoft.Json は dynamic を持っているので、あまり意味はない…というか、結論から言えば T4 とか CodeDOM を使ったほうが早いのでは?という感じはします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | class Program { static void Main( string [] args) { var json = @"{ name: 'tomoaki', num: 101 }" ; var jp = new JsonProvider(json); var t = jp.Make( "SampleJson" ); var o = new SampleJson(); o.name = "aaaa" ; o.num = "100" ; } } public class JsonProvider { JObject root; Dictionary< string , string > dic = new Dictionary< string , string >(); public JsonProvider( string json ) { root = JObject.Parse(json); var cur = root.GetEnumerator(); while ( cur.MoveNext() ) { var it = cur.Current; Debug.WriteLine( "{0} {1}" , it.Key, it.Value); dic.Add(it.Key.ToString(), it.Value.ToString()); } } public Type Make( string className ) { var assemblyName = new AssemblyName( "JsonProviderAssembly" ); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll" ); var mb = moduleBuilder.DefineType(className, TypeAttributes.Class | TypeAttributes.Public, typeof ( object )); foreach ( var it in dic) { var propName = it.Key; FieldBuilder customerNameBldr = mb.DefineField( "_" + propName, typeof ( string ), FieldAttributes.Private); PropertyBuilder custNamePropBldr = mb.DefineProperty(propName, PropertyAttributes.HasDefault, typeof ( string ), null ); MethodBuilder custNameGetPropMthdBldr = mb.DefineMethod( "get_" + propName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof ( string ), Type.EmptyTypes); ILGenerator custNameGetIL = custNameGetPropMthdBldr.GetILGenerator(); custNameGetIL.Emit(OpCodes.Ldarg_0); custNameGetIL.Emit(OpCodes.Ldfld, customerNameBldr); custNameGetIL.Emit(OpCodes.Ret); MethodBuilder custNameSetPropMthdBldr = mb.DefineMethod( "set_" + propName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null , new Type[] { typeof ( string ) }); ILGenerator custNameSetIL = custNameSetPropMthdBldr.GetILGenerator(); custNameSetIL.Emit(OpCodes.Ldarg_0); custNameSetIL.Emit(OpCodes.Ldarg_1); custNameSetIL.Emit(OpCodes.Stfld, customerNameBldr); custNameSetIL.Emit(OpCodes.Ret); custNamePropBldr.SetGetMethod(custNameGetPropMthdBldr); custNamePropBldr.SetSetMethod(custNameSetPropMthdBldr); } Type t = mb.CreateType(); assemblyBuilder.Save(assemblyName.Name + ".dll" ); return t; } } |
ModuleBuilder.CreateType でクラスを作った後で、AssemblyBuilder.Save で保存します。F# の TypeProvider の場合はこれがビルド時に行われるので、ビルド時のアセンブリと実行時のアセンブリが異なるので不整合が起ります。じゃあ、どちらも .NET Frameworkの環境であったり、ビルド時に敢えて Xamarin.Forms の PCL に合うようなアセンブリを衝くてやれば良いのだろう、と考えているのですが、これはまた後で実験します。
さて、AssemblyBuilder.Save で保存した DLL を、プロジェクトから参照設定すると作成した SampleJson クラスが使えます。一度、実行して DLL を作らないと駄目ってところが、結局のところ T4 と同じで、あまり意味がない。F# の TypeBuilder のように自動でインテリセンスが効けばいいんだけど。
ちなみに、作成した JsonProviderAssembly.dll を参照設定して、SampleJson のインスタンスを作ろうとすると DLL がロックされて書き込めないというデッドロックな状態になります。ビルド時にコピーする処理が必要ですね。
あと、実行しないとアセンブリが作られないので、T4 にして、ビルド時にアセンブリを作って後から参照するとかにしないと。
クラスを定義するためのいくつかの方法: C# プログラミング 再入門
http://dotnetcsharptips.seesaa.net/article/416983160.html
これを見る限り、CSharpCodeProvider を使って文字列から生成するほうが楽そうですね。が、Xamarin.Android からは Microsoft.CSharp.CSharpCodeProvider が見当たらないので、これはこれで。