Статическая переменная инициализируется более одного раза с использованием Prism и MEF

контекст

у меня есть InteractionWindowPresenter класс, отвечающий за создание Windows. Некоторые из них могут быть модальными, и я хочу сохранить счетчик количества открытых модальных окон, чтобы уведомить другие части приложения.

Поэтому я добавил _modalsCount переменная для класса, обновляется всякий раз, когда модальное окно открывается или закрывается:

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 - т.е. каждый класс, реализующий IModule - создает экземпляр 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 Класс определяется в сборке инфраструктуры, на которую напрямую ссылаются все модули, а также другие сборки инфраструктуры. На него не ссылается приложение запуска, которое является просто MefBootstrapper, Следовательно, MEF используется для композиции.

Эта проблема

Установка точки останова на _modalsCount Строка инициализации показывает, что она не выполняется, когда InteractionWindowPresenter экземпляры созданы. Вместо этого он выполняется в первый раз (и только в этот момент), когда переменная используется в каждом модуле, т.е. первый раз Show метод вызывается из каждого модуля. Таким образом, каждый модуль имеет свое собственное значение, общее для всех экземпляров этого конкретного модуля.

Я понимаю, что ленивая оценка связана с любопытным характеромbeforefieldinit, Однако я ожидал, что оценка произойдет только один раз для всего приложения, а не для каждого модуля.

Я также попытался выполнить инициализацию в статическом конструкторе:

static int _modalsCount;

static InteractionWindowPresenter()
{
    _modalsCount = 0;
}

В этом случае статический конструктор вызывается до выполнения конструктора экземпляра, но каждый раз, когда создается экземпляр. Следовательно, переменная больше не является статической.

Из моего понимания, static переменные инициализируются один раз заAppDomain, Таким образом, поскольку все мои сборки (модули и инфраструктура) находятся в одном AppDomainЭтого не должно быть. Я ошибаюсь в любом из этих двух предположений?

До сих пор работаю

Создание простого класса для хранения счетчика позволяет избежать этой проблемы:

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 и / или MEF мешают мне?

2 ответа

Решение

Ваш класс является универсальным, и каждый созданный универсальный тип (с указанными аргументами типа) является отдельным типом. Каждый из них имеет свой набор статических элементов.

Из спецификации языка C#, раздел 4.4.2 Открытые и закрытые типы:

Каждый закрытый составной тип имеет свой собственный набор статических переменных, которые не используются совместно с другими закрытыми составными типами. Поскольку открытый тип не существует во время выполнения, нет статических переменных, связанных с открытым типом.

Вы можете сделать простой тест:

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

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

Ваш обходной путь (хранение статического счетчика в неуниверсальном классе) является правильным.

Статика создается один раз для каждого типа. Поскольку вы используете универсальный тип, количество созданных типов будет эквивалентно количеству комбинаций переменных типа, которые вы используете в инициализаторе. Вот почему работает скрытие статики внутри неуниверсального класса (возможно, лучший шаблон в любом случае).

Другие вопросы по тегам