Click here to Skip to main content
15,892,005 members
Articles / All Topics
Technical Blog

The IOC Container Anti-Pattern

Rate me:
Please Sign up or sign in to vote.
4.42/5 (17 votes)
14 Apr 2018CPOL10 min read 26.8K   7   27
Before I receive the Frankenstein-style lantern escort to the gallows, let me assure you: I love dependency injection.... The post The IOC Container Anti-Pattern appeared first on Marcus Technical Services..

The IOC DI Container Anti-Pattern

So Much IOC; So Little Inversion or Control

 

Before I receive the Frankenstein-style lantern escort to the gallows, let me assure you: I love dependency injection (the basis of inversion of control).  It is one of the core precepts of SOLID code design: one should “depend upon abstractions, not concretions.”  I also support real Inversion of Control – changing the flow of an application through a more complex form of dependency injection.

Many modern frameworks borrow the term “IOC” to convince us that they are useful because, after all, why else would they be called that?  Because they are selling the sizzle: a way for programmers to do a thing without understanding it, or bothering with its design.

This is how the IOC Container evolved. It often arrives as the cracker jacks toy inside of an MVVM Framework (https://marcusts.com/2018/04/06/the-mvvm-framework-anti-pattern/).  It suffers from the same short-sightedness.

“Water, water everywhere… and not a drop to drink.”
― Not So Happy Sailor in Life Raft

These are Not “IOC” Containers At All

To qualify as a form of Inversion of Control, these co-called “IOC Containers” would have to control something such as the app flow.  All the containers do is to store global variables.  In the more advanced containers, the programmer can insert constructor logic to determine how to create the variable that gets stored. That is not control. It is instantiation or assignment.

These entities should be called DI (”Dependency Injection”) containers.

If we are to be taken seriously for our ideas, we should be careful not to exaggerate their features.

Global Variables are Bad Design

A so-called “IOC Container” is a dictionary of global variables which is generally accessible from anywhere inside of a program.  This intrinsically violates C# coding principles.  C# and SOLID require that class variables be as private as possible.  This keeps the program loosely coupled, since interaction between classes must be managed by interface contracts.   Imagine if you handed this code to your tech lead:

public interface IMainViewModel
{
}

public class MainViewModel : IMainViewModel
{
}

public partial class App : Application
{
	public App()
	{
		GlobalVariables.Add(typeof(IMainViewModel), () => new MainViewModel());
		InitializeComponent();
		MainPage = new MainPage() { BindingContext = GlobalVariables[typeof(IMainViewModel)] };
	}

	public static Dictionary<Type, Func<object>> GlobalVariables = new Dictionary<Type, Func<object>>();
}

You would be fired.

“What are you thinking of?” , your supervisor demands. “Why not just create the MainViewModel where it is needed, and keep it private?”

Then you provide this code:

public interface IMainViewModel
{
}

public class MainViewModel : IMainViewModel
{
}

public static class AppContainer
{
	public static IContainer Container { get; set; }
}

public partial class App : Application
{
	public App()
	{
		var containerBuilder = new ContainerBuilder();
		containerBuilder.RegisterType<MainViewModel>().As<IMainViewModel>();
		AppContainer.Container = containerBuilder.Build();

		InitializeComponent();
		MainPage = new MainPage() { BindingContext = AppContainer.Container.Resolve<IMainViewModel>()};
	}

	public static IContainer IOCContainer { get; set; }
}

You now receive a pat on the back.  Brilliant!

Except for a tiny issue: this is the same code.  Both solutions rely on a global static dictionary of variables.  We don’t globalize any class variable in a program unless that variable must be readily available from anywhere.  This might apply to certain services, but almost nothing else.  Indeed, the precursor to modern IOC Containers is a “service locator”.  That’s where it should have ended.

Let’s refactor and expand the last example to add a second view model, which we casually insert into the IOC Container:

public static class AppContainer
{
	static AppContainer()
	{
		var containerBuilder = new ContainerBuilder();
		containerBuilder.RegisterType<MainViewModel>().As<IMainViewModel>();
		containerBuilder.RegisterType<SecondViewModel>().As<ISecondViewModel>();
		Container = containerBuilder.Build();
	}

	public static IContainer Container { get; set; }
}

Inside the second page constructor, we make a mistake. We ask for the wrong view model:

public partial class SecondPage : ContentPage
{
	public SecondPage()
	{
		BindingContext = AppContainer.Container.Resolve<IMainViewModel>();
		InitializeComponent();
	}
}

Oops!  Why are we allowed to do that? Because all of the view models are global, so can be accessed – correctly or incorrectly – from anywhere, by any consumer, for any reason.  This is a classic anti-pattern: a thing you should generally not do.

IOC Containers Are Not Compile-Time Type Safe

This actually compiles, even though the SecondViewModel does *not* implement IMainViewModel. 

containerBuilder.RegisterType<MainViewModel>().As<IMainViewModel>();
containerBuilder.RegisterType<SecondViewModel>().As<IMainViewModel>();

At run-time, it crashes!

The goal and responsibility of all C# platforms is to produce compile-time type-safe interactions.  Run-time is extremely unreliable in comparison.

IOC Containers Create New Instances of Variables By Default

Quick quiz: is this equality test true?

var firstAccessedMainViewModel = AppContainer.Container.Resolve<IMainViewModel>();
var secondAccessedMainViewModel = AppContainer.Container.Resolve<IMainViewModel>();

var areEqual = ReferenceEquals(firstAccessedMainViewModel, secondAccessedMainViewModel);

The answer is no, it is false.  The container routinely issues a separate instance every variable requested.  This is shocking, since most variables must maintain their state during run-time.  Imagine creating a system settings view model:

containerBuilder.RegisterType<SettingsViewModel>().As<ISettingsViewModel>();

You need this view model in two locations: at the profile (where the settings are stored) and the main page and its view model (where they are consumed).

So we create the same dilemma just cited:

At Settings:

var settingsViewModel = AppContainer.Container.Resolve<ISettingsViewModel>();

 

At Main:

var settingsViewModel = AppContainer.Container.Resolve<ISettingsViewModel>();

 

The user opens a menu, goes to their profile, and changes one of the settings.  They close that window, close the menu, and look at their main screen.  Is the change visible?  No!  It’s stored in another variable.  The main settings variable is now “stale”, so the main screen reflects incorrect values.

There is an official hack for this. Instead of:

containerBuilder.RegisterType<MainViewModel>().As<IMainViewModel>();
containerBuilder.RegisterType<SecondViewModel>().As<ISecondViewModel>();

We write:

containerBuilder.RegisterType<MainViewModel>().As<IMainViewModel>().SingleInstance();
containerBuilder.RegisterType<SecondViewModel>().As<ISecondViewModel>().SingleInstance();

The SingleInstance extension guarantees that the same instance of the variable will always be returned.

The exception to this guidance is a list of lists:

public interface IChildViewModel
{
}

public class ChildViewModel : IChildViewModel
{
}

public interface IParentViewModel
{
	IList<IChildViewModel> Children { get; set; }
}

public class ParentViewModel : IParentViewModel
{
	public IList<IChildViewModel> Children { get; set; }
}

The only way for the ParentViewModel to add a list of children is to set their view models uniquely.  So in this case, the registration will be:

containerBuilder.RegisterType<ChildViewModel>().As<IChildViewModel>();

We do not include the SingleInstance() suffix.

IOC Containers Instantiate Classes without Flexibility or Insight

Programmers learn to decouple classes to reduce inter-reliance (”branching”) with other classes. But this does not mean that we seek to give up control. 

A class can be instantiated and it can be destroyed.  Instantiation is important because it is the where all forms of dependency injection take place.  The IOC Container steals this control from us.  

The IOC Container analyzes the constructor of each store class to determine how to create an instance.  It seeks the path of least resistance to building a class.  But this does not guarantee an intelligent or predictable decision.  For instance, these two classes share the same interface, but set the interface’s Boolean to different values:

public interface ICanBeActive
{
	bool IsActive { get; set; }
}

public interface IGeneralInjectable : ICanBeActive
{
}

public class FirstPossibleInjectedClass : IGeneralInjectable
{
	public FirstPossibleInjectedClass()
	{
		IsActive = true;
	}

	public bool IsActive { get; set; }
}

public class SecondPossibleInjectedClass : IGeneralInjectable
{

	public SecondPossibleInjectedClass()
	{
		IsActive = false;
	}

	public bool IsActive { get; set; }
}

In order for the classes to be considered for injection, we have to add them “as” IGeneralInjectable:

containerBuilder.RegisterType<FirstPossibleInjectedClass>().As<IGeneralInjectable>();
containerBuilder.RegisterType<SecondPossibleInjectedClass>().As<IGeneralInjectable>();

Notice that the classes are otherwise identical, and that their constructors also match exactly.  Here is a class that receives an injection of only one of those two classes:

public class ClassWithConstructors
{
	public bool derivedIsActive { get; set; }

	public ClassWithConstructors(IGeneralInjectable injectedClass)
	{
		derivedIsActive = injectedClass.IsActive;
	}
}

containerBuilder.RegisterType<ClassWithConstructors>();

Now we ask the IOC Container for an instance of ClassWithConstructors:

var whoKnowsWhatThisIs = AppContainer.Container.Resolve<ClassWithConstructors>();

So how would an IOC Container decide what to inject to create ClassWithConstructors?  When the container checks the candidates for IGeneralInjectable, it will find two candidates:

   FirstPossibleInjectedClass
   SecondPossibleInjectedClass

Both classes are parameterless, so that makes them equal.  The IOC Container will pick the first one it can find.  Whichever that one is, it will be wrong.  That’s because the two classes make a different decision for IsActive.  Since both are legal, and only one can be allowed, the IOC Container cannot be trusted with this decision.  It should produce a compiler error.  But the “black box” logic inside the IOC Container masks this, and issues a result that we cannot rely on.

It might surprise you just which class “won out” in this contest. It was the last class added to the container!  I verified this by reversing the order in which they were added, and sure enough, the injection followed.

There are other issues.  Interfaces are flexible contracts, and a class can implement any number of them.  For each new interface implemented, the class *must* declare this using the “as” convention, or it won’t work:

public interface IOtherwiseInjectable
{
}

public interface IGeneralInjectable : ICanBeActive
{
}

public class FirstPossibleInjectedClass : IGeneralInjectable, IOtherwiseInjectable
{
	public FirstPossibleInjectedClass()
	{
		IsActive = true;
	}

	public bool IsActive { get; set; }
}


containerBuilder.RegisterType<FirstPossibleInjectedClass>().As<IGeneralInjectable>();
containerBuilder.RegisterType<FirstPossibleInjectedClass>().As<IOtherwiseInjectable>();

This can be done manually, of course.  But what if there are dozens?  What if you miss one?  The container could mis-inject without any warnings or symptoms.

IOC Containers do Not Manage Class Lifecycle Properly

The destruction of a class is also important because the class has gone out of scope, so should be disposed by the C# run-time environment.  That frees up memory and guarantees that the class will not interfere with a program where it has lost its role.

One of the reasons that we do not declare a lot of global variables is because their lifespan is forever.  The app never goes away.  Whatever is bootstrapped or directly declared in app.xaml.cs is a permanent fixture.  So in the current examples:

public static class AppContainer
{
	static AppContainer()
	{

		var containerBuilder = new ContainerBuilder();
		containerBuilder.RegisterType<ParentViewModel>().As<IParentViewModel>().
		SingleInstance();
		containerBuilder.RegisterType<ChildViewModel>().As<IChildViewModel>();
		containerBuilder.RegisterType<MainViewModel>().As<IMainViewModel>().SingleInstance();
		containerBuilder.RegisterType<SecondViewModel>().As<ISecondViewModel>().
		SingleInstance();
		containerBuilder.RegisterType<FirstPossibleInjectedClass>().As<IGeneralInjectable>();
		containerBuilder.RegisterType<FirstPossibleInjectedClass>().As<IOtherwiseInjectable>();
		containerBuilder.RegisterType<SecondPossibleInjectedClass>().As<IGeneralInjectable>();
		containerBuilder.RegisterType<ClassWithConstructors>();
		Container = containerBuilder.Build();
	}

	public static IContainer Container { get; set; }
}

.. everything here will survive from app startup to app shutdown.  That is not their purpose.  The view models are only needed as long as their accompanying views are visible.

IOC Containers have responded by adding a “scope” to the request for a class instance:

public partial class SecondPage : ContentPage
{
	public SecondPage()
	{
		using (var scope = AppContainer.Container.BeginLifetimeScope())
		{
			BindingContext = scope.Resolve<ISecondViewModel>();
		}

		InitializeComponent();
	}
}

Problem solved, right?  Let’s test it.  Here is a new version of the second page view model, which we set as our BindingContext above:

public class SecondViewModel : ISecondViewModel
{
	private bool _isAlive;

	public SecondViewModel()
	{
		_isAlive = true;

		Device.StartTimer(TimeSpan.FromSeconds(1), () =>
		{
			if (_isAlive)
			{
				Debug.WriteLine("Second View Model is still alive");
			}

			return _isAlive;
		});
	}

	// The only way to determine if this object is being garbage-collected
	~SecondViewModel()
	{
		_isAlive = false;
		Debug.WriteLine("Second View Model is being garbage collected");
	}
}

As long as the view model is alive, we write to the console.  We also add a finalizer check to see if the view model is ever placed for garbage collection.

Now the main application:

public App()
{
	InitializeComponent();
	var mainPage = new MainPage { BindingContext = AppContainer.Container.Resolve<IMainViewModel>() };
	MainPage = mainPage;

	Device.BeginInvokeOnMainThread
	(
		async () =>
		{
			await Task.Delay(5000);
			var secondPage = new SecondPage();
			MainPage = secondPage;
			await Task.Delay(5000);
			MainPage = mainPage;
			secondPage = null;
			GC.Collect();
		});
}

We set the main page, wait five seconds, set the second page, wait five seconds, and go back to the original main page again.  Just to make sure we have destroyed the second page, we hit it with a sledge-hammer: assign it to null and call for garbage collection. For the record, nobody ever does this!

The expected result: according to the IOC Container folks, the second view model will see that the second page is out of scope and will destroy itself.

Actual result: Nothing.  Na-da.  Zippo.  The second view model keeps on announcing that it is still alive. This goes on forever.

Should-a, Could-a

That’s a big miss for a bunch of programmers who apparently believe they have created an object life-cycle for the variables in their global static container. But the warning sign was in front of our faces all along:

using (var scope = AppContainer.Container.BeginLifetimeScope())

That is physically impossible.  You would (minimally) have to:

  • Create a lifetime scope using “this” so the hosting class variable could be stored and monitored: 
    using (var scope = AppContainer.Container.BeginLifetimeScope(this))
  • Require that the hosting class implement an interface that can support monitoring. Unfortunately , IDisposable does not do this!  You need an interface with an event attached. So the IOC programmers will need to create one: 
    public interface IReportDisposal
    {
    	event EventHandler<object> IsDisposing;
    }
  • If the call to BeginLifetimeScope is called by any class not implementing this interface, a compile-time error must be issued!
  • The hosting class *must* report disposal accurately.  That is quite a head-ache. Remember: every class every using this sort of paradigm must implement the interface and raise the event on their own disposal.
  • The IOC Container *must* monitor the IsDisposing event, and when received, *must* destroy the instance of the view model.
  • The real reason that this is not done is that it will create a lot of work for anyone using an IOC Container. The illusion of these containers is that they are easy to use. So reality would set in and the containers would probably be abandoned.

 

Takeaways

IOC Containers are an anti-pattern because:

  1. They are not at all IOC; they are dependency injection toys;
  2. They create global variables when none are needed;
  3. They are “too accessible” – in a class C# application, privacy rules the day. We don’t want everything to have access to everything else.
  4. They issue new instances of variables that should almost always be singletons;
  5. They leverage hyper-simplistic logic in instantiating classes that does not support the level of complexity and nuance present in most C# applications;
  6. They do not handle variable life-cycle; all variables are global variables, regardless of their so-called “scope”.

Hard Proofs

I created a Xamarin.Forms mobile app to demonstrate the source code in this article.  The source is available on GitHub at https://github.com/marcusts/xamarin-forms-annoyances.  The solution is called IOCAntipattern.sln.

The code is published as open source and without encumbrance.

The post The IOC Container Anti-Pattern appeared first on Marcus Technical Services.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
GeneralI am not agree with most of statements. Pin
Member 63998218-Dec-19 7:47
Member 63998218-Dec-19 7:47 
GeneralMy vote of 1 Pin
Halden4-Dec-19 4:01
Halden4-Dec-19 4:01 
GeneralMy vote of 1 Pin
Member 101897433-Nov-19 21:02
Member 101897433-Nov-19 21:02 
QuestionPlease check out a pull request + my intake Pin
Postnik9-Sep-18 18:54
Postnik9-Sep-18 18:54 
QuestionI've long suspected this Pin
David Sherwood20-Apr-18 9:51
David Sherwood20-Apr-18 9:51 
AnswerRe: I've long suspected this Pin
wkempf22-Apr-18 10:30
wkempf22-Apr-18 10:30 
PraiseMy vote of 5! Pin
jediYL16-Apr-18 16:35
professionaljediYL16-Apr-18 16:35 
QuestionYou don't understand the topic PinPopular
wkempf16-Apr-18 5:44
wkempf16-Apr-18 5:44 
AnswerRe: You don't understand the topic Pin
wmjordan18-Apr-18 0:28
professionalwmjordan18-Apr-18 0:28 
GeneralRe: You don't understand the topic Pin
wkempf18-Apr-18 3:01
wkempf18-Apr-18 3:01 
GeneralRe: You don't understand the topic Pin
wmjordan18-Apr-18 16:17
professionalwmjordan18-Apr-18 16:17 
AnswerRe: You don't understand the topic Pin
marcusts20-Apr-18 10:17
marcusts20-Apr-18 10:17 
Responses below:

Calling Resolve from the constructor isn't how you use a DI container. That's service location, not dependency injection.

I am not sure where you found this. In my code, I use AutoFac as described online and in countless examples:

C#
public SecondPage()
{
   using (var scope = AppContainer.Container.BeginLifetimeScope(this))
   {
      BindingContext = scope.Resolve<ISecondViewModel>();
   }

   InitializeComponent();
}


If you have other knowledge of how to do this, please share it, as well as the SAutoFac URL's where this is described. Normally, the registration involves a statement like this:

C#
containerBuilder.RegisterType<SecondViewModel>().As<ISecondViewModel>();


So then only way to get a reference out of the container is to resolve it. There are 10,000 examples of this online.

Exposing the container is an anti-pattern that allows you to do service location. Don't do that.

How would one access a "non-exposed" container? The published examples are pretty much the same -- in one way or another, the container is a global static variable. Sometimes this is cloaked in a practice called "bootstrapping", but it produces the same outcome.

C#
public static class AppContainer
{
   static AppContainer()
   {
      var containerBuilder = new ContainerBuilder();
      containerBuilder.RegisterType<ParentViewModel>().As<IParentViewModel>().SingleInstance();
      containerBuilder.RegisterType<ChildViewModel>().As<IChildViewModel>();
      containerBuilder.RegisterType<SecondViewModel>().As<ISecondViewModel>();


In order to work as advertised, the container has to be globally accessible. This is what I am complaining about in my article. So perhaps we are in agreement that, since this violates C# privacy rules, it is an anti-pattern.

"Inside the second page constructor, we make a mistake. We ask for the wrong view model" Not a fault of DI... if you were to not use a container and constructed the wrong type here you'd have the same problem. This bit is entirely meaningless in this discussion.

My point was that this technology is so global and so broadly accessible that it encourages sloppy error that are extremely hard to debug. The goal of C# is to promote compile time type safety. This means that we do not allow loose "resolves" that are not themselves type-safe.

Register<foo>().As<ifoo>() is very specific to a given DI container implementation. If that implementation doesn't give you compile time type checks there, that's a problem with that implementation, not the concept of a DI container.

Thanks for the accurate use of the term "DI Container" -- Smile | :)

All of the DI Containers I have seen provide a similar support for registration of classes as interfaces. In my article, I complain only that this is linear and verbose, and therefore encourages mistakes of omission that would be hard to find during a debug session.

A DI container will give you different instances only if you ask it to when you register the type. All DI containers allow you to control the lifetime aspects of the items created. This includes both when to create a new instance and when to dispose of an instance. The "default" lifetime behavior is framework dependent. So, I have no idea how any of the rant here is relevant to a broader discussion?

In my article, I complained that this is a very poor default behavior. This is based on coding in enterprise environments with up to 100 programmers. Most of the uses of DI Containers look like the one mentioned earlier:

C#
containerBuilder.RegisterType<SecondViewModel>().As<ISecondViewModel>();


... which automatically produces a new instance of the view model each time it is requested. This is extremely dangerous since view models inherently have states, and when multiple copies are opened at the same time, these states cannot be managed. So data is either misrepresented or lost. I recommend that the default behavior be:

C#
containerBuilder.RegisterType<SecondViewModel>().As<ISecondViewModel>()InstancePerDependency();


This is just AutoFac's version; other DI Containers will have a different nomenclature but will support the same concept.

With extreme precaution, this complaint can be neutralized, yes. But what if I asked you to bungee-jump or hang-glide or parachute your way to work each day? You might respond, "that is extremely dangerous!" To which I might remark, "Well, then be more careful!"

<be>Your rant about the IGeneralInjectable is meaningless. I'm sure you had some scenario in mind that was meaningful (to you, at least), but in dumbing the types down you've lost absolutely any point here.

The only way to argue a point successfully is to provide both design and philosophy, but provable tests. This example proves that, simply by making the Register<> calls in the wrong order, you can end up with an incorrect instance. This is a major point and should not be dismissed. The DI Container is asked to make an impossible choice, which it does both routinely and badly.

Your rant on lifetimes shows that you not only don't understand DI, you also don't understand .NET lifetime concepts. A finalizer is NOT related to disposal. The latter is used for deterministic lifetime, while the former is non-deterministic. A DI container CANNOT call a finalizer, as no code can. Your "test" is meaningless. Once you do start talking about IDisposable you confuse the concepts further and totally fail to understand how DI container's work.

There are some subtleties here that I did not intend to tread upon. I have improved my example, pushed it online, and welcome your re-review of exactly how the test was done. I will include that in the last section of this response. If I have mis-represented any facts about Dispose, I apologize. That was not the focus of this article. It also does not affect the outcome of the testing.

REVISED TEST -- SAME RESULT



Thank you everyone for your advice on creating a more bullet-proof test for my article. The source is now pushed at GitHub:

GitHub - marcusts/xamarin-forms-annoyances: Lessons Learned While Coding With Xamarin Forms[]

The solution is IOCAntipattern.sln.

REVISIONS



The SecondViewModel no longer uses a timer to report its status, and no longer attempts to leverage the Dispose pattern for that purpose either. I did implement IDisposable just to make sure that if an instance is created and disposed, we know about it:

C#
   public class SecondViewModel : ISecondViewModel, IDisposable
   {
      public SecondViewModel()
      {
         Device.BeginInvokeOnMainThread
            (
               async () =>
               {
                  while(true)
                  {
                     await Task.Delay(1000);
                     Debug.WriteLine("Second View Model is still alive");
                  }
               }
            );
      }

      private void ReleaseUnmanagedResources()
      {
         // TODO release unmanaged resources here
         Debug.WriteLine("Second View Model HAS BEEN DISPOSED");
      }

      protected virtual void Dispose(bool disposing)
      {
         ReleaseUnmanagedResources();
         if (disposing)
         {
         }
      }

      public void Dispose()
      {
         Dispose(true);
         GC.SuppressFinalize(this);
      }
   }
}


The SecondViewModel simply spits out write-lines as long as it is alive.

NOTE: when a class declares a timer using Device.StartTimer, it does *not* create a dependency that would cause that class to survive. A "dependency" would be a variable reference to an outside class or member that causes the current member to "stay alive". The SecondViewModel is all by itself. With or without a timer, its behavior is independent and separate.

I added some more write-lines to the main app so we can see when things occur, and avoid assuming anything about class lifetimes.

C#
public partial class App : Application
{
   public App()
   {
      var mainPage = new MainPage { BindingContext = AppContainer.Container.Resolve<IMainViewModel>() };
      MainPage = mainPage;

      Device.BeginInvokeOnMainThread
         (
            async () =>
            {
               await Task.Delay(5000);
               var secondPage = new SecondPage();
               Debug.WriteLine("About to assign the main page to the second page.");
               MainPage = secondPage;
               Debug.WriteLine("Finished assigning the main page to the second page.");
               await Task.Delay(5000);
               Debug.WriteLine("About to assign the main page to the original main page.");
               MainPage = mainPage;
               Debug.WriteLine("Finished assigning the main page to the original main page.");

               // Force clean-up
               secondPage = null;
               GC.Collect();
            });

.. .etc., etc. ...


In the AppContainer, I tried three separate variations on view model registration:

lang="c#">
  // Maintains only a single instance, but might cause problems with disposal
  //containerBuilder.RegisterType<MainViewModel>().As<IMainViewModel>().SingleInstance();
  //containerBuilder.RegisterType<SecondViewModel>().As<ISecondViewModel>().SingleInstance();

  // Normal usage
  //containerBuilder.RegisterType<MainViewModel>().As<IMainViewModel>();
  //containerBuilder.RegisterType<SecondViewModel>().As<ISecondViewModel>();

  // Revised to ensure that there is an *single* alignment between the view and the view model.
  containerBuilder.RegisterType<MainViewModel>().As<IMainViewModel>().InstancePerDependency();
  containerBuilder.RegisterType<SecondViewModel>().As<ISecondViewModel>().InstancePerDependency();


The last of these is the formal guidance from AutoFac for this scenario.

ALL OF THESE FAILED THE TEST.

RESULTS



The results are the same as previously. The SecondViewModel never dies. The output:


Second View Model HAS BEEN DISPOSED
About to assign the main page to the second page.
Finished assigning the main page to the second page.
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
About to assign the main page to the original main page.
Finished assigning the main page to the original main page.
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive
Second View Model is still alive


Regarding Tone And Attitude Between Professionals



It looks like my article upset you on several levels. That was not intended. I discussed the philosophical and technical dilemmas posed by the so-called IOC Container. I did not answer one of your questions in a petulant or arrogant manner. I did not mention your name in a derogatory way. So I am puzzled by the tone of your response.

A "rant" is:

* A broad and sweeping statement, made emotionally
* A failure to dig deep into the topic
* An argument that is flawed and backed up chiefly with false confidence
* A lack of proof for one's position

Is it fair to say this is a "rant"?

Aren't you the one that showed up without proof, without an example, without citing any information URL's, and insisting that you are right?

Let's rise above the emotions and communicate as professionals and as peers.

Thank you...
GeneralRe: You don't understand the topic Pin
wkempf22-Apr-18 10:21
wkempf22-Apr-18 10:21 
GeneralRe: You don't understand the topic Pin
marcusts25-Apr-18 9:05
marcusts25-Apr-18 9:05 
GeneralRe: You don't understand the topic Pin
wkempf26-Apr-18 2:46
wkempf26-Apr-18 2:46 
GeneralRe: You don't understand the topic Pin
marcusts26-Apr-18 17:30
marcusts26-Apr-18 17:30 
GeneralRe: You don't understand the topic Pin
wkempf27-Apr-18 2:35
wkempf27-Apr-18 2:35 
SuggestionRe: You don't understand the topic Pin
DevOvercome26-Apr-18 11:22
professionalDevOvercome26-Apr-18 11:22 
AnswerRe: You don't understand the topic Pin
Anders Baumann13-May-18 23:26
Anders Baumann13-May-18 23:26 
GeneralRe: You don't understand the topic Pin
Member 101897433-Nov-19 21:11
Member 101897433-Nov-19 21:11 
GeneralMy vote of 3 Pin
mesta16-Apr-18 3:06
mesta16-Apr-18 3:06 
GeneralRe: My vote of 3 Pin
marcusts20-Apr-18 10:19
marcusts20-Apr-18 10:19 
GeneralCan't fully agree with you Pin
Klaus Luedenscheidt14-Apr-18 21:02
Klaus Luedenscheidt14-Apr-18 21:02 
GeneralRe: Can't fully agree with you Pin
marcusts20-Apr-18 10:26
marcusts20-Apr-18 10:26 
AnswerExactly! Pin
Sergey Alexandrovich Kryukov4-Mar-19 21:57
mvaSergey Alexandrovich Kryukov4-Mar-19 21:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.