アプリケーションアーキテクチャの変更により、最近発生した循環依存関係があります。
アプリケーションは、MEFを介してプラグインをロードするプラグインマネージャーに依存しています。次のように表示されたため、これまでのすべてが正常に機能しました。
// model.cs
[Export("Model")]
public class Model
{
public PluginManager PM { get; set; }
[ImportingConstructor]
public Model( [Import] PluginManager plugin_manager)
{
PM = plugin_manager;
}
}
// pluginmanager.cs
[Export(typeof(PluginManager))]
public class PluginManager
{
[ImportMany(typeof(PluginInterface))]
private IEnumerable<PluginInterface> Plugins { get; set; }
}
プラグインは次のようになりました。
// myplugin.cs
[Export(typeof(PluginInterface))]
public class MyPlugin : PluginInterface
{
}
しかし、今では、すべてのプラグインに、インターフェイスを介してPluginManager(または場合によっては他のオブジェクト)にクエリを実行して、システム内の他のプラグインの機能を確認する機能を持たせたいという状況があります。別のインターフェースを追加することでこれを「解決」しました。これをPluginQueryInterfaceと呼びましょう。次に、モデルにこのインターフェイスを実装させました。
[Export("Model"))]
[Export(typeof(PluginQueryInterface))]
public class Model : PluginQueryInterface
{
// same as before
}
プラグインの署名は次のようになります。
// 1st possible implementation
[Export(typeof(PluginInterface))]
public class MyPlugin : PluginInterface
{
[Import(typeof(PluginQueryInterface))]
public PluginQueryInterface QueryInterface { get; set; }
public MyPlugin() {}
}
またはこれ
// 2nd possible implementation
[Export(typeof(PluginInterface))]
public class MyPlugin : PluginInterface
{
private PluginQueryInterface QueryInterface { get; set; }
[ImportingConstructor]
public MyPlugin( [Import] PluginQueryInterface query_interface)
{
QueryInterface = query_interface
}
}
プラグインではプラグインを作成する前にPluginQueryInterfaceを作成する必要があるため、 2番目の実装は明らかに循環参照ですが、PluginQueryInterfaceはモデルであり、PluginManagerをインポートする必要があります。これにより、すべてのPluginInterfaceを作成する必要があります...起動すると、MEF循環依存エラーが発生します。
最初の実装は、私にとって循環参照のようには見えません。PluginQueryInterfaceがプロパティの場合、使用されるまで解決されないと思いました。また、コンストラクターではまったく使用されません。では、なぜPluginManagerがすべてのMyPluginを楽しく作成しないのでしょうか。どちらの場合も同じMEFエラーが発生します。
PluginManagerにPluginQueryInterfaceを実装させることで、この問題を解決しようとしました。これは、a)とにかく意味があり、b)循環依存を処理する既知の方法であるためです。代わりに、2つの相互依存クラスを3番目のクラスに依存させます。問題は、別のMEFエラーが発生することです。これはそれが言うことです:
GetExportedValue cannot be called before prerequisite import 'Company.App.PluginManager..ctor(Parameter="database_filepath", ContractName="PluginManager.filename")' has been set.
WTF?コードにブレークポイントを設定し、GetExportedValueを呼び出す前にエクスポートされた値PluginManager.filename
が設定されています。
私は完全に困惑しています。どんな観察や提案も今すぐ大歓迎です。私はこの問題をデバッグしようとして何時間もMEFで覆われた壁に頭をぶつけてきました。
(更新しました)
以前はこれについて考えていませんでしたが、プラグイン間の違いである可能性があるため、2つのプラグインのいずれかを削除すると、MEFエラーなしでアプリケーションが読み込まれます。私はそれを追加し直しました、そしてそれは再び失敗しました。次に、他のプラグインを削除しましたが、機能しました。したがって、これは他のMEFエラーのようです。特定のインターフェイスで複数のプラグインをロードする必要がないかのようです...しかし、私はImportManyを使用していますが、それはCardinalityException
ある種のプラグインとして現れたのではないでしょうか。
アップデート
私はMEFのこの部分を理解していません、そしてうまくいけば、ここの誰かがそれが何であるかを説明することができます。しばらくコードにステップインした後、値を見つけた後にMEFがインポート定義を削除したことが原因でエラーが発生したことがわかりました。
private bool TryGetImportValue(ImportDefinition definition, out object value)
{
lock (this._lock)
{
if (this._importValues.TryGetValue(definition, out value))
{
this._importValues.Remove(definition); // this is the line that got me
return true;
}
}
value = null;
return false;
}
私はこれまでこの問題を経験したことがなく、率直に言って、この問題を表面化させたインポートとエクスポートで現在何をしているのかを理解するのに苦労しています。MEFの設計者が誰にも意図していないことをしていると思います。やみくもにコメントアウトすることはできthis._importValues.Remove(definition);
ましたが、それはおそらく正しくありませんでした。私の推測では、これは私が使用したMEF属性に要約されますが、この値をインポートするプラグインには作成ポリシーがあるCreationPolicy.Shared
ので、なぜ問題が発生するのでしょうか。