1

Context

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.

The problem

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?

Work-around employed so far

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?

Community
  • 1
  • 1
jnovo
  • 5,343
  • 2
  • 35
  • 50

2 Answers2

3

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).

PhillipH
  • 5,938
  • 1
  • 13
  • 24
2

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.

Jakub Lortz
  • 13,858
  • 3
  • 20
  • 32
  • Damn right, I hadn't considered the generics. The difference between the number of initializations executed when using a field initializer and the static constructor is due to the laziness mentioned in the question, right? (Indeed, the number of initializations in the former case correlates to the number of different type instances actually used.) – jnovo Dec 23 '15 at 11:57
  • 1
    Without a static constructor, the initialization can be delayed. If you never call the `Show` method, the static field might never get initialized. With static constructor, the initialization will happen before the type is used for the first time. – Jakub Lortz Dec 23 '15 at 12:07