nuits.jp blog

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

XamarinでもAOPしたい! 希望編

この記事は [初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 の6日目の記事です。

qiita.com

空いたままだったのが忍びなく埋めようと書いた記事なので、完成まで到達できていませんがその辺はご容赦ください。
予定では25日の本チャン?までには完成する…といいな?的な?

ところでみなさんAOPしてますか?
Xamarin界隈では、あまり取り組まれていないイメージが個人的にはありますが、やはりiOSで動的コード生成がNGで選択肢が限られるのがネックなんでしょうか?
という訳で、AOP絡みの調査を少ししてみてました。

そもそもAOPって何?

AOPとはAspect Oriented Programmingの略です。日本語だとアスペクト指向プログラミングなんて言われますね。
こういうと、オブジェクト指向を捨てるのね!酷いわ!と思われるかもしれませんが、AOPはオブジェクト指向プログラミングとは直行する概念で、オブジェクト指向の苦手な部分を補強する概念になります。

ちなみに直行する概念てIT関連では稀に良く見かけますが、分かりにくい表現ですよね。間違ってるかも知れませんが私は次のようにとらえています。

「依存関係のない独立した概念だが、お互いを邪魔せず共存が可能な概念のこと」

ではAOPとOOPはどう共存できるのでしょうか?

誤解を恐れず言うと、一般的にオブジェクト指向は機能やユースケースという側面で関心事を分離していくことが多いかと思います。

  • 顧客登録機能
  • 商品検索機能
  • 商品購入機能

といった「機能要件」を実現することに主眼を置いています(もちろん、それだけだと限定するものではありません)。 しかし、これらの機能間には横断的な関心事がありますよね?

  • トランザクション制御
  • ロギング処理
  • 認証処理
  • 例外処理

おもに非機能要件とされる関心事が多めです。 これらの複数の機能から横断的に必要とされる関心事をアスペクトとしてとらえ、分離・記述し、必要とされる機能へ織り込む(Weaving)ことで機能に対して非機能を付与する考え方が、アスペクト指向プログラミングです。
もちろんこれらはOOPでは実現できないという訳ではありません。OOPとAOPを組み合わせることで、よりシンプルに実現できるということです。

AOPの具体例

文字で解説しても伝わり難いでしょうから、コードを見てもらいましょう。なおサンプルは諸事情によりコンソールアプリです。

まず足し算をするCalculatorクラスを作ります。

public class Calculator
{
    public virtual int Add(int left, int right)
    {
        return left + right;
    }
}

そして呼び出す側です。

class Program
{
    static void Main(string[] args)
    {
        Calculator calculator = CreateCalculator();
        Console.WriteLine(calculator.Add(1, 2));
        Console.ReadLine();
    }

    private static Calculator CreateCalculator()
    {
        return new Calculator();
    }
}

目標はAddメソッドの前後に引数と戻り値をログ出力するアスペクトを織り込む事とします。
今回はCastle CoreのDynamicProxyを利用します。

まずは、メソッドを実行前後でログ出力するインターセプターを作成します。

    public class Interceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            // 事前処理
            Console.WriteLine($"Arguments:{string.Join(", " ,invocation.Arguments)}");
            // インターセプトした処理の実行
            invocation.Proceed();
            // 事後処理
            Console.WriteLine($"ReturnValue:{invocation.ReturnValue}");
        }
    }

IInvocationがインターセプトした実行対象を抽象化したオブジェクトで、Proceedメソッドを呼ぶことで元々呼び出された処理を実行します。
ご覧の通り、呼ばないで処理を代替することも可能です。
さて、それではこのInterceptor を織り込み(Weaving)ましょう。
先ほどのProgramクラスのCreateCalculatorメソッドを次のように修正しましょう。

private static Calculator CreateCalculator()
{
    //return new Calculator();
    var generator = new ProxyGenerator();
    return generator.CreateClassProxy<Calculator>(new Interceptor());
}

こうすることでCalculatorクラスを継承したプロキシークラスが動的に生成されます。実際に生成されるクラスを見てみましょう。

f:id:nuitsjp:20171205131919p:plain

Casle.Proxies.CalculatorProxyというクラスが動的に生成されていることが見て取れるでしょう。 そして実行結果が次の通りです。

Arguments:1, 2
ReturnValue:3
3

ロギング処理が織り込まれていることが見て取れますね!
最後の行の「3」はreturnした後に、呼び出し元でConsole.WriteLineしているので想定通りです。

XamarinでAOPってどうするの?

上の例では簡単なロギング処理でしたが、共通する例外処理やトランザクション処理を都度コードに記載しないで済むことは非常に魅力的ですよね。
実装工数だけではなく、コードの見通しが良くなりメンテナンス性も高まりますし、テストも省略する事が可能です。
便利ですよね!Xamarinでもやりたい!ぜひ!!

しかしXamarinでは一筋縄ではいきません。すべてはiOSが動的コード生成を許可していないのが悪いのです。(プンスカ!

Casle.Coreの依存関係をNuGetで見てみましょう。

https://www.nuget.org/packages/Castle.Core

System.Reflection.Emitさんがいらっしゃいますね…
Xamarin.iOSの公式ドキュメントでも非対応だよとちゃんと記載があります。
https://developer.xamarin.com/guides/ios/advanced_topics/limitations/#No_Dynamic_Code_Generation
ぐぬぬぬぬ…

.NETでAOPを実装する代表的な手法の一つにRealProxyクラスを利用する方法がありますが、これも動的コード生成しているので動くわけがありません。って、あれ???

f:id:nuitsjp:20171205133015p:plain

Xamarin.iOSにRealProxyありますね?まさか動くのか??

f:id:nuitsjp:20171204133000p:plain

まぁ、ダメですよね…
やっぱりXamarin.iOSではRealProxyは使えなかった話 - nuits.jp blog

AOPをやるとなると、方式的には大きく二つに分けられます。

  1. 動的生成でプロキシークラスを作る手法
  2. ILを弄ってアスペクトを埋め込む方法
  3. 設計と実装ルールによる制約で疑似AOPを実現する

1.はXamarin.iOSでは全滅ですし、3.は最後の手段です。となるとILを弄りたくなります。(自分の能力で)できるかどうかは別として…

ILを弄る系のAOPフレームワーク、実は結構触ったことはあるって人多いんじゃないでしょうか?一時期話題になっていましたし。
そうです。PropertyChanged.Fodyです。今年ちょっと話題になっていた記事はこちらでしたっけ?

10.hateblo.jp

もちろん現在でも正しく動きます。
PropertyChanged.FodyはINotifyPropertyChangedの実装を容易にするためだけのものですが、そもそもこれのベースであるFodyはILを操作してAOPを実現するための基盤フレームワークです。
そしてPropertyChanged.Fodyの姉妹品にMethodDecorator.Fodyがあります。
そしてこいつはメソッドのインターセプトに対応しています!!おお!!!

しかしXamarinで動きません。おぉぉ…orz
まぁXamarinどころか現在開発が停止気味でVisual Studio 2017でそもそも動かない気がします。

じゃぁどうしたらいいの!!

答え:作るしかないよね

という訳で作りたいと思いますが、現在絶賛苦戦中です。とりあえず現状の課題は次の通り。

  1. Fodyの仕様が今一つ良く分からない
  2. Fodyを動かすのに.NET Standardが微妙に相性が悪いような?フォーラム探索すると.NET Standardの不具合ぽいんですが対処方法が上手くいったり行かなかったりで良く分かりません
  3. ILワカンネ

特に3番目は致命的な気がしますが、25日までに少しは希望が見えてくるといいなあと、夢を見ています。

という訳で今日はここまで!
[初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 - Qiita の7日目がまだ埋まっていませんので、よかったら誰かよろしくお願いします。