关于Prism Mef程序启动程序Shell(主窗体)被导出两次的原因分析及解决办法。

关于Prism.Mef方式启动一个wpf程序的文章已经相当多,形式大致相同。

我要说明的问题是默认的Prism.Mef的MefBootStrapper启动方式下(如果你使用了网上提供的标准模板代码),导致主窗体在MefBootStrapper->Container中存在了两个引用(事实上,这两个引用指向的是同一个实例)的原因。

因为在程序运行的时候,程序的其它组件(比如对话框服务)其它对象可能间接地需要主窗体对象工作,如果IOC容器中存在两个引用,如果在容器中尝试拿取主窗体实例的单例将会导致一个运行时异常。

废话不多说,先贴一段BootStrapper代码:

 1 class DemoBootStrapper:MefBootstrapper {
 2         protected override void ConfigureAggregateCatalog() {
 3             base.ConfigureAggregateCatalog();
 4             //这里加入自定义的模块;
 5         }
 6 
 7 
 8         protected override IModuleCatalog CreateModuleCatalog() {
 9             return new ConfigurationModuleCatalog();
10         }
11 
12         protected override DependencyObject CreateShell() {

14 return this.Container.GetExportedValue<MainWindow>(); 15 } 16 17 protected override void InitializeModules() { 18 base.InitializeModules(); 19 20 } 21 22 protected override void InitializeShell() { 23 base.InitializeShell(); 24 Application.Current.MainWindow = this.Shell as Window; 25 Application.Current.MainWindow.Show(); 26 } 27 28 29 }

为了使这个例子尽量简单,我直接使用了最终的MainWindow类型,事实上,更为通用且合理的做法是使用一个类似如下的IShell接口,使主窗体继承它,并以IShell导出实例,以便在程序的逻辑处理部分如果需要使用到主窗体对象,能够有效低降低与MainWindow所在程序集的耦合,并便于单元测试。

public interface IShell{

}

这是题外话,在程序集的主窗体类定义的地方,如你所见,我导出了该实例。

[Export]
public MainWindow:Window{
     public MainWindow(){
        IntializeComponent();
     }  
}

运行以上的代码,程序正常启动,但是,如前言所述,如果在程序运行的过程中,我尝试向容器中拿取主窗体实例的单例,比如使用Prism依赖库的ServiceLocator.GetInstance<>,或者使用[Import]注解,或者使用你自己的ServiceProvider等。

同样为了简单,我们在主窗体的加载后事件执行这一过程。

[Export]
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            this.Loaded += delegate {
                ServiceLocator.Current.GetInstance<MainWindow>();
            };
        }

      

如之前所述,我们获得了一个运行时错误:

异常信息给出了导致错误的原因,Sequence contains more than one element,也就是说同样的MainWindow类型,被导出了两个实例,或者两个指向同一个实例的引用,但是这里非常确信的是,整个程序集中只有此处才会导出了MainWindow类型的实例。

这使人一度非常困惑,为此,我在BootStrapper代码中的不同地方获取MainWindow实例调试,并查阅了Prism.Mef.Wpf的MefBootStrapper源码,以查看其启动方式:

https://github.com/PrismLibrary/Prism/blob/master/Source/Wpf/Prism.Mef.Wpf/MefBootstrapper.cs

 在其InitializeShell的基类方法中,注意到:

protected override void InitializeShell()

        {

            this.Container.ComposeParts(this.Shell);

        }

这一句便是就是导致问题的原因,ComposeParts在组合Shell(主窗体)的依赖时,还会将Shell实例再次导出到容器中,但是BootStrapper的CreateShell方法中,Shell实例及其导入依赖项(这里是没有的)已经被构造完毕,所以,针对这种情况,我们不应该执行基类的InitializeShell方法。

去除base.InitializeShell();程序运行无异常:).

OK,虽然这是一个非常细小的问题,但是,解决问题的过程是非常值得深思的,一个合格的程序员不应该过度依赖拿来主义,还要能够浅尝辄止,甚至刨根问底低去思考更深层次的问题。

希望能够帮助到你。

原文地址:https://www.cnblogs.com/ponus/p/8872702.html