CAB IoC Gotchas

This document is a part of the "Inside CAB Dependency Injection" article. It is relatively self-contained, but you still may find it useful to read the whole article from the beginning.

As we discuss in the "Anatomy of a WorkItem", you kick off CAB dependency injection mechanism by adding your object to one of the collections defined inside the WorkItem class. Sometimes these collections behave in unepexpected and even bizzare ways.

Many (but not all) of the oddities become more understandable if we reacll that all work item collections are actually filtered views of the locator (and the lifetime container).

Gotcha 0. Items you add to one collection may "magically" appear in other collections. This should not come as a big surprise, since we already know that all collections are ultimately just filters of the locator. E.g., if you add a new workspace to the Workspaces collection, it will "magically" appear in the Items collection as well, under the same ID:

void  AddNewWorkspace( WorkItem workItem )
{
    IWorkspace myWorkspace = ...;
    workItem.Workspaces.Add(myWorkspace, "someId");
    IWorkspace magic = (IWorkspace)workItem.Items["someId"];
}

Gotcha 1. ContainsObject() method does not use any filtering whatsoever: it just checks that the object in question is in the lifetime container. This may be very confusing. Consider the following code:

void  CheckContains( WorkItem workItem )
{
    MyService service = ...;
    workItem.Services.Add(myService);
    bool contains = workItem.Items.ContainsObject(myService); // returns true
    int count = workItem.Items.FindByType<MyService>().Count; // returns 0
}

Gotcha 2. When you add an object to a collection, the filtering condition is not checked. This gotcha affects only SmartParts, since only this collection has a filter. If you add to SmartParts an object whose type is not marked with [SmartPart] attribute, the object will be happily added to the locator, but it won't appear in the SmartParts collection. However, it will appear an items. This particular gotcha caused me a couple of hours of debugging on one unlucky day.

void  SmartParts_AddNotSmartPart( WorkItem workItem )
{
    int itemCount = workItem.Items.Count;
    object notSmartPart = new object();
    workItem.SmartParts.Add(notSmartPart, "id");
    int smartPartsCount = workItem.SmartParts.Count; // returns the same count as before add
    int newItemCount = workItem.Items.Count;         // returns itemCount+1
    object obj1 = workItem.SmartParts["id"];         // returns null
    object obj2 = workItem.Items["id"];              // returns notSmartPart
    bool contains = workItem.SmartParts.ContainsObject(notSmartPart)); // returns true - gotcha #1
}

Gotcha 3. Both the indexer and Count are O(n) operations that enumerate over all objects in the locator. Theoretically the collections offer an optimization for the indexer via so called "indexer delegate", but in practice it is not used in the actual work item collections.

Gotcha 4. The collections do not control adding items to the locator. This is done as part of the object "build-up" process, as one of the build steps, and only under certain conditions. We describe the build-up process in further sections. The objects are added to the locator by the CreationStrategy class from the ObjectBuilder assembly, and only if the builder has a policy of type ISingletonPolicy installed, and that policy's IsSingleton property is set to true. All these conditions must be true for the built-up objects to be added to the locator. Frankly, this is very not obvious dependency. Worse, if one of these conditions is not true, Add() and AddNew() methods will still "work" without error: the items just won't be added to the locator (and hence to any collections). Fortunately, under normal circumstances, the builder is always properly configured. You may experience this problem if you build your own root work item, or attempt other customizations.

Gotcha 5. The collections assume that the locator and the lifetime container contain the same objects.. This is generally true under normal circumstances, but if this assumption is ever violated, it will lead to interesting results. Count property, and the indexer this[] enumerate the objects in the locator. However, FindByType() enumerates the objects in the lifetime container, for no particular reason. Thus, if the contents of the two somehow become different, you will get incosistent information.