17

私が開発中のフレームワークで、いくつかの従来のアセンブリ スキャン手法をバイパスしたいと考えています。

したがって、次のコントラクトを定義したとします。

public interface IModule
{

}

これは say に存在しますContracts.dll

ここで、このインターフェースのすべての実装を発見したい場合は、おそらく次のようにします。

public IEnumerable<IModule> DiscoverModules()
{
    var contractType = typeof(IModule);
    var assemblies = AppDomain.Current.GetAssemblies() // Bad but will do
    var types = assemblies
        .SelectMany(a => a.GetExportedTypes)
        .Where(t => contractType.IsAssignableFrom(t))
        .ToList();

    return types.Select(t => Activator.CreateInstance(t));
}

良い例ではありませんが、十分でしょう。

現在、これらの種類のアセンブリ スキャン手法はパフォーマンスが非常に低く、すべて実行時に実行されるため、通常は起動時のパフォーマンスに影響します。

新しい DNX 環境では、ICompileModuleインスタンスをメタプログラミング ツールとして使用できるため、 の実装をプロジェクト内のフォルダーにバンドルして、ICompileModuleファンキーCompiler\Preprocessなことを実行させることができます。

私の目標は、ICompileModule実装を使用して、代わりにコンパイル時に実行時に行う作業を行うことです。

  • 私の参照 (コンパイルとアセンブリの両方) と現在のコンパイルで、インスタンス化可能なすべてのインスタンスを見つけますIModule
  • クラスを作成し、ModuleList各モジュールのインスタンスを生成する実装で呼び出します。
public static class ModuleList
{
    public static IEnumerable<IModule>() GetModules()
    {
        yield return new Module1();
        yield return new Module2();
    }
}

そのクラスをコンパイル ユニットに追加すると、アタッチされているすべてのアセンブリを検索する代わりに、それを呼び出して、実行時にモジュールの静的リストを取得できます。基本的に、ランタイムではなくコンパイラで作業をオフロードしています。

プロパティを介してコンパイルのすべての参照にアクセスできることを考えるとReferences、バイトコードへのアクセス、リフレクション用のアセンブリのロードなど、有用な情報を取得する方法がわかりません。 .

考え?

4

2 に答える 2

6

考え?

はい。

通常、モジュール環境では、コンテキストに基づいてモジュールを動的にロードするか、該当する場合はサードパーティからモジュールをロードします。対照的に、Roslyn コンパイラ フレームワークを使用すると、基本的にこの情報をコンパイル時に取得できるため、モジュールが静的参照に制限されます。

昨日、工場を動的にロードするためのコードを投稿しました。DLL などをロードするための属性、更新など: GoF Factory の命名規則? . 私が理解していることから、それはあなたが達成しようとしていることと非常に似ています。このアプローチの利点は、実行時に新しい DLL を動的にロードできることです。試してみると、かなり速いことがわかります。

処理するアセンブリをさらに制限することもできます。たとえば、mscorliband System.*(またはおそらくすべての GAC アセンブリ) を処理しない場合は、もちろん、はるかに高速に動作します。それでも、私が言ったように、それは問題ではありません。タイプと属性をスキャンするだけでも、非常に高速なプロセスです。


OK、もう少し情報とコンテキスト。

楽しいパズルを探しているだけかもしれません。テクノロジーをいじるのがやっぱり楽しいのはわかります。以下の回答 (マシュー自身による) は、必要なすべての情報を提供します。

コンパイル時のコード生成とランタイム ソリューションの長所と短所のバランスを取りたい場合は、私の経験からの詳細情報を次に示します。

数年前、私は独自の C# パーサー/ジェネレーター フレームワークで AST 変換を行うのは良い考えだと判断しました。これは、Roslyn でできることとよく似ています。基本的に、プロジェクト全体を AST ツリーに変換します。AST ツリーを正規化し、コードを生成し、アスペクト指向プログラミングを行う際に追加のチェックを行い、新しい言語構造を追加します。ここでの私の当初の目標は、アスペクト指向プログラミングのサポートを C# に追加することでした。これにはいくつかの実用的なアプリケーションがありました。詳細は省きますが、このコンテキストでは、コード生成に基づくモジュール/ファクトリも、私が実験したものの 1 つであると言えれば十分です。

パフォーマンス、柔軟性、およびコードの量 (非ライブラリ ソリューション) は、実行時とコンパイル時の決定の間の決定に重みを付けるための重要な側面です。それらを分解しましょう:

  • パフォーマンス。ライブラリ コードがクリティカル パス上にないことは想定できないため、これは重要です。ランタイムには、appdomain インスタンスごとに数ミリ秒かかります。(方法/理由については、以下を参照してください)。
  • 柔軟性。どちらも、属性/スキャンに関してほぼ同じように柔軟です。ただし、実行時には、ルールを変更する可能性が高くなります (たとえば、動的にモジュールをプラグインするなど)。特に構成に基づいて、これを使用することがあります。これにより、同じソリューションですべてを開発する必要がなくなります (非効率的であるため)。
  • コードの量。経験則として、通常、コードが少ないほど優れたコードになります。正しく行うと、両方がクラスで必要な単一の属性になります。つまり、ここでは両方のソリューションで同じ結果が得られます。

ただし、パフォーマンスに関するメモは適切です。コード内のファクトリ パターン以外にもリフレクションを使用しています。私は基本的に、すべての設計パターン (および他の多くのもの) を含む「ツール」の広範なライブラリをここに持っています。いくつかの例: ファクトリ、一連の責任、デコレータ、モッキング、キャッシング/プロキシ (およびその他多数) などのコードを実行時に自動的に生成します。これらのいくつかは、すでにアセンブリをスキャンする必要がありました。

簡単な経験則として、私は常に属性を使用して何かを変更する必要があることを示します。これを有利に利用できます。(正しいアセンブリ/名前空間の) 属性を持つすべての型をシングルトン/ディクショナリのどこかに格納するだけで、アプリケーションを大幅に高速化できます (スキャンが 1 回だけで済むため)。Microsoft からアセンブリをスキャンすることもあまり役に立ちません。大規模なプロジェクトで多くのテストを行ったところ、最悪の場合、スキャンによってアプリケーションの起動時間が約 10 ミリ秒長くなることがわかりました。これは appdomain のインスタンス化ごとに 1 回だけであることに注意してください。

タイプのアクティブ化は、実際に得られる唯一の「実際の」パフォーマンスの低下です。このペナルティは、IL コードを発行することで最適化できます。それは本当に難しいことではありません。最終結果は、ここでは何の違いもないということです。

最後に、私の結論は次のとおりです。

  • パフォーマンス: わずかな違い。
  • 柔軟性: ランタイムが優先されます。
  • コードの量: わずかな違い。

私の経験から、多くのフレームワークはプラグ アンド プレイ アーキテクチャをサポートすることを望んでいますが、これはアセンブリにドロップすることでメリットが得られますが、現実には、これが実際に適用できるユース ケースはそれほど多くありません。

適用できない場合は、そもそもファクトリ パターンを使用しないことを検討することをお勧めします。また、適用可能であれば、実際の欠点がないことを示しました。つまり、適切に実装する場合です。残念ながら、私は多くの悪い実装を見てきたことをここで認めなければなりません。

実際には当てはまらないという事実に関しては、それは部分的にしか当てはまらないと思います。ドロップイン データ プロバイダーは非常に一般的です (論理的には 3 層アーキテクチャから派生しています)。また、ファクトリを使用して、通信/WCF API、キャッシング プロバイダー、デコレータ (論理的には n 層アーキテクチャから派生) などを接続します。一般的に言えば、考えられるあらゆる種類のプロバイダーに使用されます。

パフォーマンスが低下するという議論がある場合は、基本的に型スキャンプロセス全体を削除する必要があります。個人的には、キャッシング、統計、ロギング、構成など、さまざまなことにそれを使用しています。また、パフォーマンスのマイナス面は無視できると思います。

ちょうど私の2セント。HTH。

于 2015-07-08T06:28:33.363 に答える