使用 Prism 和 MEF 多次初始化静态变量

Static variable initialized more than once using Prism and MEF

上下文

我有一个 InteractionWindowPresenter class 负责创建 Windows。其中一些可能是模态的,我想保留一个 打开模态数 windows 的计数器,以便通知应用程序的其他部分。

因此我向 class 添加了一个 _modalsCount 变量,每当打开或关闭模态 window 时更新:

public class InteractionWindowPresenter<TWindow, TNotification>
    where TWindow : System.Windows.Window
    where TNotification : Prism.Interactivity.InteractionRequest.INotification
{
   private static int _modalsCount = 0;

   ...

   private bool _useModalWindow;

   public InteractionWindowPresenter(InteractionRequest<TNotification> request, 
      bool useModalWindow = false)
   {
      _useModalWindow = useModalWindow;
   }

   public void Show()
   {
      var window = ...

      window.Closed += (s, e) =>
      {
         if (_useModalWindow)
         {
             _modalsCount = Math.Max(0, --_modalsCount);

             if (_modalsCount == 0)
                 ServiceLocator.Current.GetInstance<IEventAggregator>()
                    .GetEvent<ModalStatusChanged>().Publish(false);
         }       
      };

      if (_useModalWindow)
      {
         _modalsCount++;

         ServiceLocator.Current.GetInstance<IEventAggregator>()
            .GetEvent<ModalStatusChanged>().Publish(true);

         window.ShowDialog();
      }
      else
         window.Show();
   }
}

初始化后,每个 Prism 模块 - 即。每个 class 实现 IModule - 实例化每个必须显示在 Window 上的视图的 InteractionWindowPresenter 并持有对它的引用。例如:

[ModuleExport("ClientsModule", typeof(Module), 
    DependsOnModuleNames = new[] { "RibbonModule", "ClientsModelModule" }, 
    InitializationMode = InitializationMode.WhenAvailable)]
public class Module : IModule
{
    InteractionWindowPresenter<ClientSelectionWindow, ClientSelection> _selectionPresenter;

    public void Initialize()
    {
       _selectionPresenter = 
           new InteractionWindowPresenter<ClientSelectionWindow, ClientSelection>
              (Interactions.ClientSelectionRequest, useModalWindow: true);
    }
}

InteractionWindowPresenter class 是在所有模块以及其他基础结构程序集直接引用的基础结构程序集中定义的。它没有被启动器应用程序引用,它只是一个 MefBootstrapper。因此,MEF用于合成。

问题

_modalsCount 初始化行设置断点显示在创建 InteractionWindowPresenter 实例时它不会执行。相反,它是第一次(并且只有那一次)在每个模块中使用变量时执行 - 即。第一次从每个模块调用 Show 方法。因此,每个模块都有自己的价值,在该特定模块的所有实例之间共享。

我了解到懒惰评估是由于 the curious nature of beforefieldinit。但是我希望整个应用程序而不是每个模块只进行一次评估。

我也试过在静态构造函数中执行初始化:

static int _modalsCount;

static InteractionWindowPresenter()
{
    _modalsCount = 0;
}

在这种情况下,静态构造函数在实例构造函数执行之前被调用,但每次都会创建一个实例。因此,该变量似乎不再是静态的。

据我了解,static variables are initialized once per AppDomain。因此,由于我所有的程序集(模块和基础设施)都在同一个 AppDomain 中,所以这不应该发生。这两个假设我错了吗?

目前采用的解决方法

创建一个简单的 class 来保存计数器可以避免这个问题:

static class ModalsCounter
{
    private static int _modalsCount = 0;

    public static int Increment()
    {
        return ++_modalsCount;
    }

    public static int Decrement()
    {
        _modalsCount = Math.Max(0, --_modalsCount);
        return _modalsCount;
    }
}

因此将对 _modalsCount 的调用替换为:

ModalsCounter.Increment();

ServiceLocator.Current.GetInstance<IEventAggregator>()
   .GetEvent<ModalStatusChanged>().Publish(true);

和:

if (_useModalWindow && ModalsCounter.Decrement() == 0)
    ServiceLocator.Current.GetInstance<IEventAggregator>()                    
      .GetEvent<ModalStatusChanged>().Publish(false);

那么我在这里缺少什么?我是否以某种方式误解了静态变量的生命周期和范围,或者 Prism 模块 and/or MEF 在搞乱我?

为每个类型创建一次静态。由于您使用的是通用类型,因此创建的类型数量将等于您在初始化程序中使用的类型变量组合的数量。这就是为什么将静态隐藏在非通用 class 中的原因(无论如何可能是更好的模式)。

你 class 是泛型,每个构造的泛型类型(指定类型参数)都是一个单独的类型。他们每个人都有自己的一组静态成员。

来自 C# language specification,第 4.4.2 节开放和封闭类型:

Each closed constructed type has its own set of static variables, which are not shared with any other closed constructed types. Since an open type does not exist at run-time, there are no static variables associated with an open type.

你可以做个简单的测试:

public class Test<T>
{
    public static object obj = new object();
}

Console.WriteLine(object.ReferenceEquals(Test<string>.obj, Test<object>.obj)); // false

您的解决方法(将静态计数器保持在非泛型 class 中)是正确的。