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