『ぼくたちは勉強ができない(6)』を読んだ

真冬先生回が3話も収録されていた。 妹の美春まで登場し、 先生がフィギュアスケートをやめた過去を匂わせるシーンも。 再登場ありそうだ。 成幸とは、彼氏彼女というよりもはや夫婦の域で、今のところ全ヒロインの中で一番リードしてしまっている。 つい先日発表された人気投票の結果でも1位になったし。驚愕。 1位の可能性あるだろうなと思っていたが、本当になってしまうとは。

先輩は正直ビジュアルが好みではなかったけど、性格が男前で、 登場するたびに魅力的なキャラに思えてきた。 からかわれてあたふたする成幸が見れるのは先輩回だけ。 親へのネタバラシどうするんだろね。 筒井先生のことだから、上手く面白く調理してくれるに違いない。

文乃・理珠・うるかの3人娘は先生と先輩の勢いに押され気味。 うるかなんて、何度も告白のチャンスはあったはずなのに、その度余計にややこしくなってるし。 こちらもどうするよ。 文乃は女心の師匠になったあたりから、 キャラとして一皮向けたというか、頭角を表してきた感じ。 3人娘の中では今のところ一番かも。

AssemblyBuilder を使った動的クラス生成

先日、Expression と DynamicMethod でそれぞれ動的にデリゲートを生成するサンプルを書いてみた。

tnakamura.hatenablog.com

メタプログラミングの目的がリフレクションの高速化なら、 動的なデリゲート生成で事足りる場合が多い。 今回は一歩進んで、動的にクラスを生成してみたいと思う。 お題は、「WCF のサービスコントラクトから動的にクライアントのクラスを生成する」。

using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace BlackMagicSample
{
    // WCF サービスコントラクト
    [ServiceContract]
    public interface IGreetingService
    {
        [OperationContract]
        string GoodMorning(string name);

        [OperationContract]
        string Hello(string name);

        [OperationContract]
        string GoodBye();

        [OperationContract]
        void GoodAfternoon(string name);
    }

    // WCF サービス
    class GreetingService : IGreetingService
    {
        public void GoodAfternoon(string name)
        {
            Console.WriteLine($"Good afternoon, {name}");
        }

        public string GoodBye()
        {
            return "Good bye.";
        }

        public string GoodMorning(string name)
        {
            return $"Good morning, {name}.";
        }

        public string Hello(string name)
        {
            return $"Hello, {name}.";
        }
    }

    // このクラスのようなものを実行時に生成する
    //class GreetingServiceClient : ClientBase<IGreetingService>, IGreetingService
    //{
    //    public GreetingServiceClient(Binding binding, EndpointAddress endpointAddress)
    //        : base(binding, endpointAddress)
    //    {
    //    }
    //
    //    public string GoodMorning(string name)
    //    {
    //        return Channel.GoodMorning(name);
    //    }
    //
    //    public string Hello(string name)
    //    {
    //        return Channel.Hello(name);
    //    }
    //
    //    public string GoodBye()
    //    {
    //        return Channel.GoodBye();
    //    }
    //
    //    public void GoodAfternoon(string name)
    //    {
    //        Channel.GoodAfternoon(name);
    //    }
    //}

    class Program
    {
        static void Main(string[] args)
        {
            Console.Title = "BlackMagicSample";

            // WCF サービスを開始
            var address = "net.pipe://localhost/GreetingService";
            var binding = new NetNamedPipeBinding();
            var host = new ServiceHost(typeof(GreetingService));
            host.AddServiceEndpoint(
                typeof(IGreetingService),
                binding,
                address);
            host.Open();

            // WCF サービスを呼び出すクライアントの型を生成
            var clientType = GenerateClientType<IGreetingService>();

            // WCF サービスを呼び出せるかテスト
            var client = (IGreetingService)Activator.CreateInstance(
                clientType,
                binding,
                new EndpointAddress(address));
            Console.WriteLine(client.GoodMorning("Honda"));
            Console.WriteLine(client.Hello("Kagawa"));
            Console.WriteLine(client.GoodBye());
            client.GoodAfternoon("Nagatomo");
            
            Console.ReadLine();

            // 後始末
            ((ClientBase<IGreetingService>)client).Close();
            host.Close();
        }

        // WCF サービスを呼び出すクライアントの型を生成する
        static Type GenerateClientType<T>()
            where T : class
        {
            var interfaceType = typeof(T);
            var moduleName = $"Client_{interfaceType.Name}";

            var clientBaseType = typeof(ClientBase<T>);
            var clientBaseConstructor = clientBaseType.GetConstructor(
                bindingAttr: BindingFlags.NonPublic | BindingFlags.Instance,
                binder: null,
                types: new[] { typeof(Binding), typeof(EndpointAddress) },
                modifiers: null);
            var channelProperty = clientBaseType.GetProperty(
                "Channel",
                BindingFlags.NonPublic | BindingFlags.Instance);

            var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(
                name: new AssemblyName(moduleName),
                access: AssemblyBuilderAccess.RunAndSave);

            var moduleBuilder = assemblyBuilder.DefineDynamicModule(
                moduleName,
                moduleName + ".cs");

            // ClientBase<T> 派生クラスを定義
            var typeBuilder = moduleBuilder.DefineType(
                name: moduleName,
                attr: TypeAttributes.Public,
                parent: typeof(ClientBase<T>),
                interfaces: new[] { typeof(T) });

            // Biding と EndpointAddress を受け取るコンストラクタを定義
            var constructorBuilder = typeBuilder.DefineConstructor(
                attributes: MethodAttributes.Public
                    | MethodAttributes.HideBySig
                    | MethodAttributes.SpecialName
                    | MethodAttributes.RTSpecialName,
                callingConvention: CallingConventions.Standard,
                parameterTypes: new[] { typeof(Binding), typeof(EndpointAddress) });
            var ctorIL = constructorBuilder.GetILGenerator();
            ctorIL.Emit(OpCodes.Ldarg_0);
            ctorIL.Emit(OpCodes.Ldarg_1);
            ctorIL.Emit(OpCodes.Ldarg_2);
            ctorIL.Emit(OpCodes.Call, clientBaseConstructor);
            ctorIL.Emit(OpCodes.Ret);

            var methods = interfaceType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
            foreach (var method in methods)
            {
                var parameterTypes = method.GetParameters()
                    .Select(p => p.ParameterType)
                    .ToArray();

                var methodBuilder = typeBuilder.DefineMethod(
                    name: method.Name,
                    attributes: MethodAttributes.Public
                        | MethodAttributes.HideBySig
                        | MethodAttributes.NewSlot
                        | MethodAttributes.Virtual
                        | MethodAttributes.Final,
                    returnType: method.ReturnType,
                    parameterTypes: parameterTypes);

                var methodIL = methodBuilder.GetILGenerator();

                // Channel プロパティからチャネルを取得
                methodIL.Emit(OpCodes.Ldarg_0);
                methodIL.Emit(OpCodes.Call, channelProperty.GetMethod);

                // チャネルのメソッドを呼び出す
                for (var i = 0; i < parameterTypes.Length; i++)
                {
                    methodIL.Emit(OpCodes.Ldarg, i + 1);
                }
                methodIL.Emit(OpCodes.Callvirt, method);
                methodIL.Emit(OpCodes.Ret);
            }

            return typeBuilder.CreateType();
        }
    }
}

動的にクラスを生成する場合は、AssemblyBuilder を使う。 AssemblyBuilder を使って、 動的にアセンブリを作り、 動的にモジュールを作り、 動的に型を作り、 動的にメソッドを作る。 メソッドの中身は IL 手書き。 超大変なので、今回も ILSpy を使ってカンニングした。

実行結果は次の通り。

f:id:griefworker:20180427150106p:plain

動的に生成したクライアントのクラスを使って、WCF サービスの呼び出しに成功した。 この手のクライアントは .NET Remoting の RealProxy を使っていたけど、 .NET Remoting は .NET Core で使えないし未来もないので、 動的にクラスを生成する手法に乗り換えたいところだ。

波佐見陶器まつり

ゴールデンウィーク期間中に長崎県波佐見町で行われている波佐見陶器まつりに行ってきた。

60th 波佐見陶器まつり

陶器というと、同時期に開催されている有田陶器市の方が規模が大きく有名だけど、波佐見陶器まつりも中々。というか予想以上の賑わい。甘く見ていた。

会場は波佐見町やきもの公園一帯と、コンパクトにまとまっていて、家族で回るのにちょうどいい具合。 これ以上広いと、子供を連れて回るのはシンドイかもしれない。

大きなテントの中にはいろんな窯元のブースがあり、器を見て回るだけでも見応えがあった。 これぞザ・陶器といった窯元ばかりかと思っていたけど、モダンでオシャレな器を作っているところもあって、 老若男女問わず楽しめそうな印象。事実、若い人も結構見かけた。

芝生広場の方には、窯元のテントだけでなく、陶器以外の特産品も売り出していた。 ただこの日は暑かったので、アイスクリームやソフトクリーム、ジェラートなんかに目がいってしまいがち。

ちなみに戦利品は箸置き2種類。 1つ購入した後に、もっと良いものを見つけてしまい、もう1種類買ってしまった。 これ、陶器まつりあるあるだろうな。

PostgreSQL で Entity Framework Core を使っていて money 型でハマった

PostgreSQL で Entity Framework Core を使っていて、PostgreSQL の money 型の列を C# の decimal 型のプロパティにマップしたら、プロパティに格納される値がなんか変でハマった。テーブルに格納されている値の 100 分の 1 になってる。

試したのは下記のコード。

using Microsoft.EntityFrameworkCore;
using System;
using System.Threading.Tasks;

namespace PgSample
{
    public class Product
    {
        public long Id { get; set; }

        public string Name { get; set; }

        public decimal Price { get; set; }
    }

    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext() : base()
        {
            Products = Set<Product>();
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseNpgsql(
                "Host=localhost;Username=postgres;Password=p@ssword;Database=sample");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Product>(b =>
            {
                b.Property(e => e.Id)
                    .UseNpgsqlSerialColumn()
                    .HasColumnName("id");

                b.Property(e => e.Name)
                    .IsRequired()
                    .HasColumnName("name");

                b.Property(e => e.Price)
                    .IsRequired()
                    .HasColumnName("price")
                    .HasColumnType("money");

                b.ToTable("products");
                b.HasKey(e => e.Id);
            });
        }

        public DbSet<Product> Products { get; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MainAsync(args).GetAwaiter().GetResult();

            Console.WriteLine("Press Enter Key.");
            Console.ReadLine();
        }

        static async Task MainAsync(string[] args)
        {
            // テストデータを作りなおす
            using (var context = new ApplicationDbContext())
            {
                await context.Database.EnsureDeletedAsync();
                await context.Database.EnsureCreatedAsync();
                context.Products.Add(new Product()
                {
                    Name = "foo",
                    Price = 12345,
                });
                context.Products.Add(new Product()
                {
                    Name = "bar",
                    Price = 67890
                });
                await context.SaveChangesAsync();
            }

            // Entity Framework Core で普通に取得
            Console.WriteLine("EF Normal");
            using (var context = new ApplicationDbContext())
            {
                var products = await context.Products.ToListAsync();
                foreach (var p in products)
                {
                    Console.WriteLine(
                        $"id:{p.Id}\tname:{p.Name}\tprice:{p.Price}");
                }
            }

            // Entity Framework Core で SQL を指定して取得
            Console.WriteLine("FromSql");
            using (var context = new ApplicationDbContext())
            {
                var products = await context.Products
                    .FromSql("SELECT id, name, price FROM products")
                    .ToListAsync();
                foreach (var p in products)
                {
                    Console.WriteLine(
                        $"id:{p.Id}\tname:{p.Name}\tprice:{p.Price}");
                }
            }

            // Entity Framework Core で money を numeric にキャストする SQL を指定して取得
            Console.WriteLine("FromSql with convert");
            using (var context = new ApplicationDbContext())
            {
                var products = await context.Products
                    .FromSql("SELECT id, name, price::money::numeric FROM products")
                    .ToListAsync();
                foreach (var p in products)
                {
                    Console.WriteLine(
                        $"id:{p.Id}\tname:{p.Name}\tprice:{p.Price}");
                }
            }

            // NpgsqlConnection を直に使って取得
            Console.WriteLine("ExecuteReaderAsync");
            using (var context = new ApplicationDbContext())
            {
                using (var connection = context.Database.GetDbConnection())
                {
                    await connection.OpenAsync();
                    using (var command = connection.CreateCommand())
                    {
                        command.CommandText = "SELECT id, name, price FROM products";
                        using (var reader = await command.ExecuteReaderAsync())
                        {
                            while (await reader.ReadAsync())
                            {
                                Console.WriteLine(
                                    $"id:{reader[0]}\tname:{reader[1]}\tprice:{reader[2]}");
                            }
                        }
                    }
                }
            }

            // NpgsqlConnection を直に使い、money を numeric にキャストする SQL を指定して取得
            Console.WriteLine("ExecuteReaderAsync with convert");
            using (var context = new ApplicationDbContext())
            {
                using (var connection = context.Database.GetDbConnection())
                {
                    await connection.OpenAsync();
                    using (var command = connection.CreateCommand())
                    {
                        command.CommandText = "SELECT id, name, price::money::numeric FROM products";
                        using (var reader = await command.ExecuteReaderAsync())
                        {
                            while (await reader.ReadAsync())
                            {
                                Console.WriteLine(
                                    $"id:{reader[0]}\tname:{reader[1]}\tprice:{reader[2]}");
                            }
                        }
                    }
                }
            }
        }
    }
}

実行結果がこちら。

f:id:griefworker:20180426114225p:plain

Entity Framework Core の問題かと思ってたら、Npgsql を直に使った場合も同じだった。 Npgsql 内で PostgreSQL の money 型を C# の decimal 型にマップしているところに原因がありそう。 GitHub の Npgsql リポジトリで MoneyHandler のソースコードを見てみた。

https://github.com/npgsql/npgsql/blob/b8ee19e50884ad740ebc78086d90082818f8c2c5/src/Npgsql/TypeHandlers/NumericHandlers/MoneyHandler.cs

短いのでクラスを引用。

[TypeMapping("money", NpgsqlDbType.Money, dbType: DbType.Currency)]
class MoneyHandler : NpgsqlSimpleTypeHandler<decimal>
{
    public override decimal Read(NpgsqlReadBuffer buf, int len, FieldDescription fieldDescription = null)
        => buf.ReadInt64() / 100m;

    public override int ValidateAndGetLength(decimal value, NpgsqlParameter parameter)
        => value < -92233720368547758.08M || value > 92233720368547758.07M
            ? throw new OverflowException($"The supplied value ({value}) is outside the range for a PostgreSQL money value.")
            : 8;

    public override void Write(decimal value, NpgsqlWriteBuffer buf, NpgsqlParameter parameter)
        => buf.WriteInt64((long)(Math.Round(value, 2, MidpointRounding.AwayFromZero) * 100m));
}

Read 時に 100m で割っている。怪しい。こいつが犯人か。 なぜこんな実装になっているのか調べたけど、ちょっと時間切れ。 money ではなく numeric を使った方が良かったかもな。

『かぐや様は告らせたい(9)』を読んだ

9巻でようやく、重要なエピソードの1つである石上の過去が明らかに。 石上を救う言葉をかけた会長にはシビれたし、石上がひた隠しにしていたものを独自調査で突き止めた生徒会はさすが。 あと、かぐやもしれっと暗躍していてウケた。 というか、かぐやは石上のこと可愛がり過ぎでは。 過去を乗り越えて前を向いた石上は、まさに裏主人公といった感じて好感持てる。

それ以外では、白銀家はとんでもない父親だったし、恒例の特訓回では藤原書記の新しい一面が見れたしと、収録されてる話のどれもが面白かった。満腹。

メタプログラミング事始め

はじめに

メタプログラミング.NET』を読んで、 C# で黒魔術もといメタプログラミングを習得したくなってしまったので、 まずは簡単なお題で練習してみた。

メタプログラミング.NET (アスキー書籍)

メタプログラミング.NET (アスキー書籍)

お題

任意のクラスのインスタンスを生成し Dictionary<string, object> に格納された値で初期化するファクトリーメソッドを作成する。

例えば

public class Product
{
    public long Id { get; set; }

    public string Code { get; set; }

    public string Name { get; set; }

    public decimal Price { get; set; }
}

のようなクラスがある場合に

public static Product Create(IDictionary<string, object> data)
{
    var product = new Product();
    product.Id = (long)data["Id"];
    product.Code = (string)data["Code"];
    product.Name = (string)data["Name"];
    product.Price = (decimal)data["Price"];
    return product;
}

という感じのファクトリーメソッドを動的に生成するのが目標。

リフレクション

まずは素直にリフレクションを使ってみる。

 public static T CreateUsingRefrection<T>(IDictionary<string, object> data)
 {
     var obj = Activator.CreateInstance<T>();

     var type = typeof(T);
     var properties = type.GetProperties(
         BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
     foreach (var p in properties)
     {
         var value = data[p.Name];
         var convertedValue = Convert.ChangeType(value, p.PropertyType);
         p.SetValue(obj, convertedValue);
     }

     return obj;
 }

動的に生成していないけど、分かりやすいので、 パフォーマンスを求められない部分ならこれでもいいかと思ってる。

Expression

お次は System.Linq.Expressions にある Expression を使って、動的にデリケートを生成してみる。

public static Func<IDictionary<string, object>, T> CreateFuncUsingExpression<T>()
{
    var resultType = typeof(T);

    // IDictionary<string, object> data;
    var data = Expression.Parameter(typeof(IDictionary<string, object>), "data");

    // T result;
    var result = Expression.Parameter(resultType, "result");

    var expressions = new List<Expression>();

    // result = new T();
    expressions.Add(
        Expression.Assign(
            result,
            Expression.New(resultType)));

    var properties = resultType.GetProperties(
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
    foreach (var p in properties)
    {
        // result.Property = (PropertyType)data.Item["Property"];
        // を組み立てる
        expressions.Add(
            Expression.Assign(
                Expression.PropertyOrField(
                    result,
                    p.Name),
                Expression.Convert(
                    Expression.Property(
                        data,
                        "Item",
                        Expression.Constant(p.Name)),
                    p.PropertyType)));
    }

    expressions.Add(result);

    var body = Expression.Block(
        resultType,
        new[] { result },
        expressions);

    var e = Expression.Lambda<Func<IDictionary<string, object>, T>>(
        body,
        data);

    var f = e.Compile();
    return f;
}

メソッド呼び出すだけなら Expression で良いけど、ブロックが必要になった場合はシンドイ。デバッガを使ったカンニングがそのまま使えず苦労した。

Refrection.Emit

最後は Refrection.Emit を使い、IL 手書きで動的デリゲート生成。

public static Func<IDictionary<string, object>, T> CreateFuncUsingRefrectionEmit<T>()
{
    var resultType = typeof(T);
    var resultConstructor = resultType.GetConstructor(new Type[0]);
    var resultProperties = resultType.GetProperties(
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);

    var argType = typeof(IDictionary<string, object>);
    var itemProperty = argType.GetProperty(
        "Item",
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);


    var dm = new DynamicMethod(
        Guid.NewGuid().ToString("N"),
        resultType,
        new[] { argType });

    var il = dm.GetILGenerator();

    il.Emit(OpCodes.Newobj, resultConstructor);

    foreach (var p in resultProperties)
    {
        // result.Property = (PropertyType)arg.Item["Property"];
        // を組み立てる。
        il.Emit(OpCodes.Dup);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldstr, p.Name);
        il.Emit(OpCodes.Callvirt, itemProperty.GetMethod);
        if (p.PropertyType.IsValueType)
        {
            // 値型のときはボックス化解除
            il.Emit(OpCodes.Unbox_Any, p.PropertyType);
        }
        else
        {
            // クラスのときはキャスト
            il.Emit(OpCodes.Castclass, p.PropertyType);
        }
        il.Emit(OpCodes.Callvirt, p.SetMethod);
    }

    il.Emit(OpCodes.Ret);

    var f = (Func<IDictionary<string, object>, T>)dm.CreateDelegate(typeof(Func<IDictionary<string, object>, T>));
    return f;
}

Expression で苦労したブロックも、IL 手書きだと意外にスンナリ書けた。

コード全体

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

namespace BlackMagicBenchmarks
{
    public class RefrectionVsExpressionVsIL
    {
        IDictionary<string, object> data;
        Func<IDictionary<string, object>, Product> expression;
        Func<IDictionary<string, object>, Product> refrectionEmit;

        [GlobalSetup]
        public void Setup()
        {
            data = new Dictionary<string, object>()
            {
                ["Id"] = 1L,
                ["Code"] = "0001",
                ["Name"] = "Visual Studio",
                ["Price"] = 100000m,
            };
            expression = ProductFactory.CreateFuncUsingExpression<Product>();
            refrectionEmit = ProductFactory.CreateFuncUsingRefrectionEmit<Product>();
        }

        [Benchmark]
        public Product Manual()
        {
            return ProductFactory.Create(data);
        }

        [Benchmark]
        public Product Refrection()
        {
            return ProductFactory.CreateUsingRefrection<Product>(data);
        }

        [Benchmark]
        public Product Expression()
        {
            return expression(data);
        }

        [Benchmark]
        public Product RefrectionEmit()
        {
            return refrectionEmit(data);
        }
    }

    public static class ProductFactory
    {
        public static Product Create(IDictionary<string, object> data)
        {
            var product = new Product();
            product.Id = (long)data["Id"];
            product.Code = (string)data["Code"];
            product.Name = (string)data["Name"];
            product.Price = (decimal)data["Price"];
            return product;
        }

        public static T CreateUsingRefrection<T>(IDictionary<string, object> data)
        {
            var obj = Activator.CreateInstance<T>();

            var type = typeof(T);
            var properties = type.GetProperties(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
            foreach (var p in properties)
            {
                var value = data[p.Name];
                var convertedValue = Convert.ChangeType(value, p.PropertyType);
                p.SetValue(obj, convertedValue);
            }

            return obj;
        }

        public static Func<IDictionary<string, object>, T> CreateFuncUsingExpression<T>()
        {
            var resultType = typeof(T);

            // IDictionary<string, object> data;
            var data = Expression.Parameter(typeof(IDictionary<string, object>), "data");

            // T result;
            var result = Expression.Parameter(resultType, "result");

            var expressions = new List<Expression>();

            // result = new T();
            expressions.Add(
                Expression.Assign(
                    result,
                    Expression.New(resultType)));

            var properties = resultType.GetProperties(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
            foreach (var p in properties)
            {
                // result.Property = (PropertyType)data.Item["Property"];
                // を組み立てる
                expressions.Add(
                    Expression.Assign(
                        Expression.PropertyOrField(
                            result,
                            p.Name),
                        Expression.Convert(
                            Expression.Property(
                                data,
                                "Item",
                                Expression.Constant(p.Name)),
                            p.PropertyType)));
            }

            expressions.Add(result);

            var body = Expression.Block(
                resultType,
                new[] { result },
                expressions);

            var e = Expression.Lambda<Func<IDictionary<string, object>, T>>(
                body,
                data);

            var f = e.Compile();
            return f;
        }

        public static Func<IDictionary<string, object>, T> CreateFuncUsingRefrectionEmit<T>()
        {
            var resultType = typeof(T);
            var resultConstructor = resultType.GetConstructor(new Type[0]);
            var resultProperties = resultType.GetProperties(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);

            var argType = typeof(IDictionary<string, object>);
            var itemProperty = argType.GetProperty(
                "Item",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);


            var dm = new DynamicMethod(
                Guid.NewGuid().ToString("N"),
                resultType,
                new[] { argType });

            var il = dm.GetILGenerator();

            il.Emit(OpCodes.Newobj, resultConstructor);

            foreach (var p in resultProperties)
            {
                // result.Property = (PropertyType)arg.Item["Property"];
                // を組み立てる。
                il.Emit(OpCodes.Dup);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldstr, p.Name);
                il.Emit(OpCodes.Callvirt, itemProperty.GetMethod);
                if (p.PropertyType.IsValueType)
                {
                    // 値型のときはボックス化解除
                    il.Emit(OpCodes.Unbox_Any, p.PropertyType);
                }
                else
                {
                    // クラスのときはキャスト
                    il.Emit(OpCodes.Castclass, p.PropertyType);
                }
                il.Emit(OpCodes.Callvirt, p.SetMethod);
            }

            il.Emit(OpCodes.Ret);

            var f = (Func<IDictionary<string, object>, T>)dm.CreateDelegate(typeof(Func<IDictionary<string, object>, T>));
            return f;
        }
    }

    public class Product
    {
        public long Id { get; set; }

        public string Code { get; set; }

        public string Name { get; set; }

        public decimal Price { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            BenchmarkRunner.Run<RefrectionVsExpressionVsIL>();
        }
    }
}

ベンチマーク

BenchmarkDotNet を使ってベンチマークをとってみた。

f:id:griefworker:20180416172552p:plain

リフレクションは桁違いに遅い。 Expression と Refrection.Emit は大差無い。

おわりに

Expression ならデバッガー、Reflection.Emit ではアセンブリを dnSpy で解析することでカンニングできる。 一から全部自分で書かなくてもいい。 今回のケースでは、Expression よりも Reflection.Emit の方が書きやすかった。 Reflection.Emit に慣れたら、Expression を使うことは無くなるかもな。

『渡くんの××が崩壊寸前(1)〜(5)』を読んだ

月刊ヤングマガジンで連載中の『渡くんの××が崩壊寸前』を Kindle でまとめ買いして読んだ。ラブコメなんだけど、 ところどころサスペンスの香りが漂っていて、二つの意味でドキドキハラハラしている。登場人物みんな何らかの闇を抱えてそうで。

あらすじ

公式から引用。

ラーメン大好き小泉さん』の鳴見なるによる、“三角関係+妹”の日常崩壊ラブコメ

「最悪の幼なじみ」「憧れの同級生」「ブラコンの妹」の3人の女性に主人公・渡直人が振り回される!

紗月は最悪の幼なじみには思えない

6年前に渡家の畑を荒らし、渡くんが恋愛に臆病になった元凶で、さらにはストーカーじみた言動も目立つけど、言うほど最悪の幼なじみではない。 基本的に渡くんの味方だし。 渡くんの恋の応援さえしてしまう、 憎めないヒロイン。 6年前の事件は何か訳がありそうだけど、まだ明らかになってない。気になる。

正統派ヒロインの石原さんは危うさが見え隠れしている

渡くんは他の男の子とは違う、と一方的に潔癖なイメージを押し付けていると思ったら、一転、紗月に対抗心燃やしてキスより進みたがったり。 渡くん憧れの同級生で、外見良し性格良しという正統派ヒロインなんだけど、どこか危うさがある。

あと梅澤っていう後輩キャラにも期待

渡くんが気になりだしたみたいだが、悲しいかな報われることはないんだろうな。結構好きなんだけど。 渡くんと紗月と石原さんの三角関係を掻き回してほしい。

まだ明らかになってない謎が多い

叔母の多摩代が渡兄妹を引き取ったのにも、理由があるみたいだ。少なくとも、親切とかじゃなさそう。 さらに、もっともマトモに見える友人の徳井も、人に言えない趣味嗜好とか持ってそうな描写があって、もうね…。おいおい、主要な登場人物みんな何か抱えてるのかよ、って思ったね。

とにかく最後までラブコメでいってほしい

各自が抱えてる爆弾が爆発して、 一転サスペンスとかは勘弁してほしい。 そういう意味でも続きが非常に気になっているんだけど、月刊なので、単行本は年に2冊がいいところ。 待ち遠しい。 読者としては週刊に移籍して欲しいくらいだ。