nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Development Technologies。

Dapperの拡張ライブラリ 比較検討

皆さん、Dapper使っていますか?

私は比較的最近まで、オレオレMicro ORMを使っていたので、あまり使っていませんでしたが、いろいろと反省してDapperに移行中です。

さてDapper非常に良いと思うのですが、単品だとさすがに実装効率があまりよろしくありません*1。そこで活躍するのがDapperの拡張ライブラリです。

しかし、多くの拡張ライブラリが存在しているため、何を使うか悩みます。というかまだ悩み中です。何かお勧めがあれば教えてもらいたいところです。

とはいえ、自分で調べないという訳にもいきませんので、Dapper拡張ライブラリをいくつか比較してみました。

*1:主キー指定で1レコード取得したり、1レコードのアップデートしたりを都度SQL書くのは非効率ですよね

続きを読む

Prism for WPF で非表示となったViewのインスタンスを破棄する設定

Prism for WPFではRegionという機能を利用することで、画面遷移実装する事ができます。具体的にはこちらをご覧ください。

さて、Regionを利用して画面遷移した場合、デフォルトの振る舞いではViewやViewModelのインスタンスは、一度生成されると以後はキャッシュされて再利用されます。この振舞は、画面のプロパティの初期化もれによる不具合を引き起こしがちです。*1

このため、個人的にはパフォーマンスを特別に気にする個所を除いて、インスタンスのキャッシュを無効化したいと思う事が良くあります。

本稿では非表示となったViewのインスタンスを破棄する方法を、二つのユースケースにたいして実現方法を紹介したいと思います。

*1:何らかの登録処理を連続して行うような業務アプリケーションを想像してください。メニュー画面から登録画面に遷移し、登録完了後メニュー画面に戻るとします。続けて登録画面に遷移すると、登録画面とそのViewModel(およびそれらが参照しているインスタンス)は再利用されるため、登録後から再度表示するまでの間に初期化処理していないと、以前の登録内容が表示されてしまうといった不具合を引き起こします。

続きを読む

App Center Analyticsを使い倒す - 静的コード生成を活用したXamarinにおけるAOP活用

本エントリーは、つぎの二つの機会に発表した内容をまとめ直したものです。

資料はこちらに公開しています。ただスライドは発表の補助資料な為、資料だけ見ても伝わり切りませんので、本エントリーもあわせてご覧ください。

https://www.slideshare.net/AtsushiNakamura4/app-center-analytics-97896393

続きを読む

良さげなDI Containerの比較記事を見つけた

英語のブログですが良さげな比較記事を見つけました。

www.palmmedia.de

私が知らなかっただけで、DI Containerのパフォーマンス評価としては著名なもののようですが。

github.com

個人的にDI Containerへ基本機能や安定性は大前提として、つぎのような条件を満たしてほしいと考えています。

  1. オブジェクト ライフサイクルのカスタマイズ
  2. .NET Standard対応
  3. Xamarin対応
  4. Generic対応
  5. Interception機能またはオブジェクト生成の独自定義

2.と3.は一見同じことのようですが、.NET Standardに対応していても動的コード生成が含まれているとXamarinでは動かないため厳密には異なります。

5.はContainer側でInterception機能が提供されていなくても、オブジェクト生成に関与できれば自前で対応が可能だからです。

これらを踏まえて、軽量な物を選ぶとぱっと見

  • Grace
  • LightInject
  • SimpleInjector

あたりに興味を覚えました。ちゃんと調べれば他にも該当する者はありそうですし、UnityやAutofacも現在大幅な改修が行われているので速度的には今後改善されるかもしれません。またコードを見てる訳ではないので、上げたものが本当に良いかどうかも分かりません。

何にしろ前述のブログ記事は、世の中にあふれかえっているDI Containerを選択する指針の一つにはなるんではないかと思いました。

という訳で今回はこれだけです。

Infragistics Web Day 2017に参加してきました

表題の通りですが、Infragistics Web Day 2017に参加してきました。

connpass.com

ここ数年(多分8年とか?)まともにWebシステム開発に携わって来なかったのですが、今後ノータッチという訳にもいかないし、特にWebフロントエンドの知識の最新化は、今期の組織課題としてちょうど取り上げていたところでした。非常によいタイミングで告知があったので少し厳しいスケジュールでしたが参加させていただきました。

結果としては満足いく内容で、とくにパネルディスカッションと懇親会でお話させていただいた内容は、面白くもあり、ためにもなりました。
こういった場を提供していただいたInfragisticsさんに感謝の念が絶えません。

という訳で?簡単にですが感じたこと、考えたことを残しておきたいと思います。ぶっちゃけポエムなんで怒らないでください。
なお当日の時系列には全く沿っていません。
また完全仕事モードでしたので、個人の趣味嗜好ではなく一般的な受託開発メインのSIer的視点での感想になっています。個人の趣味趣向とは大きくずれている点があることを先にお伝えしておきます。

続きを読む

XamarinでもAOPしたい! Fody&Mono.Cecil編

さて前回、Mono.Cecilを利用した静的なAOPについて解説しました。

今回はいよいよXamarin.iOSでも動作する、静的なAOPの実装について解説したいと思います。Mono.Cecilを使ってILを織り込むFodyのアドインを作成し、コンパイル時に自動的にILを織り込むところまでを解説します。

具体的にはViewModelのメソッド呼び出し時にDebug.WriteLineでクラス名とメソッド名をログ出力するサンプルを作成します。

「使い勝手の良いAOPフレームワーク」は、まだまだお預けですが、今回を理解すればあとはILを「頑張れば」できたも同然でしょう(つまり先はまだ長いです)。

では早速内容に入って行きましょう。

続きを読む

XamarinでもAOPしたい! Mono.Cecil編

さて前回、Cauldron.Interception.Fodyを利用すればXamarinでもAOPできるんだけど、仕様が好みじゃないので作るしかないな!というお話をしました。

IL弄ってアスペクトを織り込むアドインを作成するわけですが、ILを弄ると言うと黒魔術のように感じるかもしれません。私自身そう思っていました。
しかし今回実際に触ってみて思ったのですが、技術的な難易度的には黒魔術というほどでもないかな?と、感じることができました。これはもちろん、ILを編集するための基盤を過去の偉人が構築してくれていたことも大きいです。
ただ通常の開発手段として気軽に使うべきではないという意味では、いかに容易に扱えるフレームワークがあると言っても黒魔術であることは変わりないでしょう。

という訳で、今回から2回にわたってXamarinで静的AOPを実装するためのHello, Worldレベルの解説をしたいと思います。
これらのエントリーでILを操作するする雰囲気をつかんでいただけたらと思います。
それでは本題に入りましょう。

静的AOPのアプローチ

通常の実行時にReflection.Emitなどを使って動的コード生成を利用するAOPに対して、アプリケーションの実行前、コンパイル時にアスペクトを織り込んでおくアプローチのため、仮に静的AOPと呼ぶこととします。異論は受け付けますというか、適切な名称があるのであれば指摘ください。

さてこれまで、PropertyChanged.FodyやCauldron.Interception.Fodyを紹介してきましたが、実のところ静的なAOPを実現するためのコアテクノロジーは二つあります。

  1. Fody
  2. Mono.Cecil(実際には4種類程のNuGetパッケージに分割されています)

このうち、実際にILの編集を行う機能を提供するのはMono.Cecilになります。
FodyはMono.Cecilを利用してILを編集する機能を実装したアドインを、MSBuildと協調してコンパイル時に制御することで、静的AOPを容易に活用する仕組みを提供するものになります。

全2回による解説の概略

という訳で、静的AOPのHello, World.を解説するにあたり、今回と次回の2回にわたって次のような手順で解説していきます。

今回:Mono.Cecilを利用し既存アセンブリに任意のILを織り込む方法の解説
次回:Mono.Cecilで作成したAOPアドインをFodyを使って適用する方法の解説

今回のゴール

ビルド済みのアセンブリ内の、指定のクラスの指定のメソッド呼び出し時に、ログ出力するコードを織り込む。
今回は.NET Standardではなく、すべて.NET Frameworkで実装します。

参考情報

さて私自身、先にも書いた通りIL読んだり書いたりするのは今回が初めてで、ぶっちゃけちゃんと説明できません。
という訳で、参考にさせてもらった文献を紹介しておきたいと思います。

手順概略

ILを編集してログ出力するにあたり、今回は次の手順で進めます。

  1. IL編集前のコードを実装し、ILSpyを利用してILを確認する
  2. ログ出力コードをC#で普通埋め込み、ILSpyを利用して差分を解析する
  3. 既存アセンブリにログを出力するILを埋め込むコンソールアプリを作成する
  4. 3.で編集したアセンブリを実行し動作を確認する
  5. 3.で編集したアセンブリをILSpyを利用して確認する

IL編集前のコードを実装する

それでは実際に始めましょう。まずはILを編集対象のプロジェクトを作成しましょう。
WeaveTargetという名称のコンソールプロジェクトを作成します。

f:id:nuitsjp:20171211132308p:plain

そしてProgram.csを開いて次のようなコードを記載しビルドします。

class Program
{
    static void Main(string[] args)
    {
        SayHello();
        Console.ReadLine();
    }

    private static void SayHello()
    {
        Console.WriteLine("Hello, AOP!");
    }
}

このコードをビルドしてILを覗いてみます。覗くのにはILSpyを利用します。
ILSpyを開くと次のような画面が開かれます。

f:id:nuitsjp:20171211132914p:plain

ここに先にビルドしたWeaveTarget.exeをドラッグ&ドロップし、開かれた中から前述のSayHelloメソッドを覗いて見ましょう。

f:id:nuitsjp:20171211230510p:plain

デフォルトではILから逆コンパイルされたC#のコードが表示されますが、今回見たいのはILです。という訳でドロップダウンをC#からILに切り替えてILを確認して見ましょう。

f:id:nuitsjp:20171211230523p:plain

抜粋したILのコードが次の通りです。

IL_0000: nop
IL_0001: ldstr "Hello, AOP!"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret

さて、今回はILの読み方そのものは解説しません。興味ある方は次の二つを参考にされると理解が進むでしょう。

ログ出力コードをC#で普通埋め込み、ILSpyを利用して差分を解析する

さて、先ほどのSayHelloメソッドをもう一度見て見ましょう。

private static void SayHello()
{
    Console.WriteLine("Hello, AOP!");
}

今回のゴールは「指定のクラスの指定のメソッド呼び出し時に、ログ出力するコードを織り込む。」でしたよね?という訳でSayHello()メソッド編集してつぎのコードと同義のコードを生成することを目指します。
が、ひとまずは実際のコードを修正してログ出力コードを埋め込んでビルドし、ILがどう変わるか確認してみます。

private static void SayHello()
{
    Console.WriteLine("Program#SayHello()");
    Console.WriteLine("Hello, AOP!");
}

そして再びビルドしたアセンブリをILSpyで覗いてみましょう。ILは次のように変わっています。

IL_0000: nop
IL_0001: ldstr "Program#SayHello()"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ldstr "Hello, Fody!"
IL_0011: call void [mscorlib]System.Console::WriteLine(string)
IL_0016: nop
IL_0017: ret

IL_0000、IL_0001、IL_0006が新たに追加されており、IL_000b以降は変化がありません。
つまり、既存のILに対してIL_0000〜IL_0006の内容を挿入してあげれば、同等の振る舞いが追加できるはずだ、と言うことになります。

ILを自力で記述する場合は、このように

  1. 実現したいコードを書いて見てそのILをチェックする
  2. ILを吐き出すコードを記載する
  3. 吐き出されたILをチェックし、また逆アセンブルしてC#コードをチェクする

これを繰り返して行くのが一般的な開発スタイルのようです(私も始めたばかりなので間違ってるかもしれませんが)。

さて、ここでログ出力のC#コードはいったん削除してリビルドしておきましょう。

既存アセンブリにログを出力するILを埋め込むコンソールアプリを作成する

それでは実際にILを編集していきます。
まずはILを編集するコンソールアプリのひな型を作成しましょう。

WeaveLogOutputという名前のコンソールアプリを作成し、NuGetからFodyCecilパッケージを適用しましょう。

f:id:nuitsjp:20171211231301p:plain

すると実際にはFodyCecilライブラリの参照は追加されず、Mono.Cecil系のライブラリが4つ参照に追加されます。
FodyCecilは実体がなく、依存するライブラリが定義されているだけです(こういうライブラリを何て言うんでしたっけ?)。

それではここにILを編集するコードを記述していきましょう。 まずは編集対象のアセンブリを読み込み、対象のメソッドを特定します。

static void Main(string[] args)
{
    var path = @"..\..\..\WeaveTarget\bin\Debug";
    var module = ModuleDefinition.ReadModule(Path.Combine(path, "WeaveTarget.exe"));
    var type = module.Types.Single(x => x.Name == "Program");
    var method = type.Methods.Single(x => x.Name == "SayHello");
}

そしてILを編集するProcessorと、SayHelloメソッドの先頭行を取得します。

static void Main(string[] args)
{
    ...
    var processor = method.Body.GetILProcessor();
    var first = method.Body.Instructions.First();
}

ここからfirstの前にログ出力のILを埋め込んでいきます。
改めて織り込むILを確認します。

IL_0000: nop
IL_0001: ldstr "Program#SayHello()"
IL_0006: call void [System]System.Diagnostics.Debug::WriteLine(string)

1行目と2行目は簡単ですね。次のようにしてfirstの前にILを織り込みます。

static void Main(string[] args)
{
    ...
    processor.InsertBefore(first, Instruction.Create(OpCodes.Nop));
    processor.InsertBefore(first, Instruction.Create(OpCodes.Ldstr, $"{type.Name}#{method.Name}()"));
}

3行目は少しだけ面倒です。少しだけね。
ConsoleクラスのWriteLineメソッドのMethodInfoを取得した上でILを挿入してあげます。

static void Main(string[] args)
{
    ...
    var consoleWriteLine = typeof(Console)
        .GetTypeInfo()
        .DeclaredMethods
        .Where(x => x.Name == nameof(Console.WriteLine))
        .Single(x =>
        {
            var parameters = x.GetParameters();
            return parameters.Length == 1 &&
                    parameters[0].ParameterType == typeof(string);
        });
    processor.InsertBefore(first, Instruction.Create(OpCodes.Call, module.ImportReference(consoleWriteLine)));
}

そして最後に編集したモジュールをかき出します。

static void Main(string[] args)
{
    ...
    module.Write("WeavedTarget.exe");
}

読み込んだ編集対象のアセンブリは「WeaveTarget.exe」でしたが、書き出したあとは「WeavedTarget.exe」に名前を変えています。

編集したアセンブリを実行し動作を確認する

それではWeavedTarget.exeを実行してみましょう。

f:id:nuitsjp:20171211232934p:plain

正しくログが織り込まれていることが確認できます。

編集したアセンブリをILSpyを利用して確認する

続いてWeavedTarget.exeをILSpyで覗いてみましょう。

f:id:nuitsjp:20171211233120p:plain

想定通りのILが書き出されていることが見て取れます。C#コードにリバースした結果も見てみましょう。

f:id:nuitsjp:20171211233209p:plain

完璧に想定通りのコードですね。
これで既存のアセンブリへ、ILを織り込むことができました。
今回は手動で織り込みを実行しましたが、次回はFodyを利用して自動的にコンパイル時に織り込まれるようFodyのアドイン化を解説したいと思います。

という訳で、今回はここまで! 近いうちに続きでお会いしましょう!