1

I have a WPF view that has a corresponding ViewModel. All instances are resolved via an unity container. Because I'm using prism I need two independent instances of the view to add it into two different regions the view is registered to. If I'd try to add one instance into both regions I get an

InvalidOperationException: Specified element is already the logical child of another element. Disconnect it first.

when the view is added into the second region because it is already added to the first region.

This problem can easily be solved by using a TransientLifetimeManager that always returns a new instance so both regions would be filled with an independent instance.

But we have decided to create a child container when a new user logs on. Every session related view and view model are resolved using this child container. When the user's session ends, the child container is disposed so that also every session related instances are disposed. But using a TransientLifetimeManager the unity container cannot dispose those instances.

What we need is a lifetime manager that always returns a new instance, but is also capable of disposing those instances. Is there already such an lifetime manager around? Or is there another way to achieve what I described above?

PVitt
  • 11,500
  • 5
  • 51
  • 85
  • For those reading the answers: "making eligible to be GC'ed" does not "imply calling Dispose [immediately, or ever technically]". For strict scopes/lifetimes this is a *big* difference. – user2864740 Mar 03 '15 at 21:47

2 Answers2

2

What you want sounds like a variant of the ContainerControlledLifetime manager that does not maintain a singleton instance, but a collection of instances. Unfortunately this is not one of the built-in lifetime managers.

You can look at the code for the ContainerControlledLifetimeManager and see that it is pretty simple. Your "SynchronizedGetValue" implementation would always return null (signaling to the container that a new instance needs to be instantiated). You could just subclass ContainerControlledLifetimeManager and override that method.

I've pretty much written it. I suppose I could give you the code. :)

public class ContainerTrackedTransientLifetimeManager :     
             ContainerControlledLifetimeManager
{
    protected override object SynchronizedGetValue()
    {
        return null;
    }
}

That should work. I've not tested it... from the interface, it looks like it's designed for a 1 to 1 LifetimeManager to Object relationship, but if it turns out it is more than that, you might have to override SetValue (adds to a collection of objects) and dispose (disposes that collection of objects). Here's that implementation:

public class ContainerTrackedTransientLifetimeManager : 
             SynchronizedLifetimeManager, IDisposable
{
    private ConcurrentCollection<object> values = new ConcurrentCollection<object>();

    protected override object SynchronizedGetValue()
    {
        return null;
    }

    protected override void SynchronizedSetValue(object newValue)
    {
        values.Add(newValue);
    }

    public override void RemoveValue()
    {
        Dispose();
    }

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

    protected void Dispose(bool disposing)
    {

         var disposables = values.OfType<IDisposable>();
         foreach(var disposable in disposables)
         {
              disposable.Dispose();
         }
         values.Clear();
    }

I'm not sure which of these is the right answer. Let me know how it goes for you.

Anderson Imes
  • 25,500
  • 4
  • 67
  • 82
  • Yes, I also tried this yesterday. My first approach as well as your two do the trick to create a new instance per resolve, but all three do not call Dispose on the resolved objects when the container is closed. What am I missing? – PVitt May 06 '11 at 07:43
  • Dammit. I just missed the IDisposable interface on the class definition. The implementation is present, but the object couldn't be casted to an IDisposable. Nevertheless, I ended up deriving the ContainerTrackedTransientLifetimeManager from the HierarchicalLifetimeManager to have the instances disposed when the child container gets disposed. – PVitt May 06 '11 at 09:59
  • I think the idea is right, but it cannot be done without patching Unity itself. If I derive from HierarchicalLifetimeManager, my SynchronizedGetValue method doesn't get called. If I derive from ContainerControllerLifetimeManager the instance is not disposed when the child container is disposed. Therefore I need a custom BuilderStartegy. But this needs access to the internel field IsUsed of the LifetimeManager. So I fear that this cannot be achieved from outside the assembly. – PVitt May 06 '11 at 11:29
  • @PVitt: I have some time today, so I might be able to look at it myself and actually *try* it :) In the meantime you might have to just track the objects yourself and use the ExternallyControlledLifetimeManager. Perhaps the tracking can be done from an object you insert in the sub container, so you have one point to deal with and it's scoped correctly. – Anderson Imes May 06 '11 at 13:08
  • @PVitt: I also wanted to mention that Damian Schenkelman's comment about circular references is indeed correct. A graph of objects can be collected as long as it is seperated from the graph rooted at the AppDomain, even if there is a circular reference. .NET doesn't technically use refcount to determine GC time. I didn't mention it because it's not what you asked, but you might be going down this path under a false assumption. – Anderson Imes May 06 '11 at 13:34
  • I think that it will be ok for the next time to derive from HierarchicalLifetimeManager to always get a new instance. Nevertheless it would be an interesting feature to have a completely functional ContainerTrackedTransientLifetimeManager or having Unity giving the ability to write such a lifetime manager from outside. This means having access to the internal fields needed (e.g. IsUsed). – PVitt May 09 '11 at 08:04
1

When you use transient lifetime manager (which is the default), Unity does not keep a reference to the created instance.

Thus, when there are no more reference to the instance, it will be GCed.

Damian Schenkelman
  • 3,505
  • 1
  • 15
  • 19
  • As far as we are using prism with a "view model first approach" we still have references. There is a circular reference between view and view model that uses the view model to set itself as the view's data context. – PVitt May 05 '11 at 12:32
  • 1
    circular references should also be GCed if they are part of an island. make sure that there is no other way to reach the circular reference – Damian Schenkelman May 05 '11 at 17:02