Reputation: 5789
I have an InteractionWindowPresenter
class in charge of creating Windows. Some of them may be modal and I want to keep a counter of the number of opened modal windows in order to notify other parts of the application.
Therefore I added a _modalsCount
variable to the class, updated whenever a modal window is opened or closed:
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();
}
}
Upon initialization, each Prism module - ie. each class implementing IModule
- instantiates an InteractionWindowPresenter
per view that must be shown on a Window and holds a reference to it. For instance:
[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);
}
}
The InteractionWindowPresenter
class is defined in an infrastructure assembly directly referenced by all the modules as well as other infrastructure assemblies. It is not referenced by the launcher application, which is merely a MefBootstrapper
. Hence, MEF is used for composition.
Setting a breakpoint on the _modalsCount
initialization line reveals that it is not executed when the InteractionWindowPresenter
instances are created. Instead, it is executed the first time (and only that time) the variable is used in each module - ie. the first time the Show
method is called from each module. Thus, each module has its own value, shared across all the instances of that specific module.
I understand that the lazy evaluation is due to the curious nature of beforefieldinit
. However I expected that evaluation to happen just once for the whole application instead of per module.
I also tried performing the initialization in the static constructor:
static int _modalsCount;
static InteractionWindowPresenter()
{
_modalsCount = 0;
}
In this case, the static constructor is invoked prior to the execution of the instance constructor, but every single time an instance is created. Therefore, the variable seems not to be static anymore.
From my understanding, static
variables are initialized once per AppDomain
. Thus, since all my assemblies (modules and infrastructure) are in the same AppDomain
, this should not happen. Am I wrong in any of these two assumptions?
Creating a simple class to hold the counter avoids this problem:
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;
}
}
Thus replacing the calls to _modalsCount
by:
ModalsCounter.Increment();
ServiceLocator.Current.GetInstance<IEventAggregator>()
.GetEvent<ModalStatusChanged>().Publish(true);
and:
if (_useModalWindow && ModalsCounter.Decrement() == 0)
ServiceLocator.Current.GetInstance<IEventAggregator>()
.GetEvent<ModalStatusChanged>().Publish(false);
So what am I missing here? Have I somehow misunderstood the lifecycle and scope of static variables or are Prism modules and/or MEF messing with me?
Upvotes: 1
Views: 364
Reputation: 14904
You class is generic, and each constructed generic type (with type arguments specified) is a separate type. Each of them has its own set of static members.
From C# language specification, section 4.4.2 Open and closed types:
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.
You can make a simple test:
public class Test<T>
{
public static object obj = new object();
}
Console.WriteLine(object.ReferenceEquals(Test<string>.obj, Test<object>.obj)); // false
Your workaround (keeping the static counter in a non-generic class) is correct.
Upvotes: 2
Reputation: 6222
The static is created once for each Type. Since you are using a Generic Type the number of Types created will be equivalent to the number of combinations of Type variables you use in the initializer. This is why hiding the static inside a non generic class works (probably a better pattern anyhow).
Upvotes: 3