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.