New sample: CodePlexContainer

Topics: CodePlexContainer
Coordinator
May 31, 2007 at 11:19 AM
Edited May 31, 2007 at 11:20 AM
I left p&p about a year ago to join the CodePlex team. We have been using ObjectBuilder is two ways: as an unseen part of EntLib 2 and as an explicitly used container.

Today we uploaded our container source to this project, under /Samples/CodePlexContainer. This is not a static sample: this is where our container lives now. We will be making changes to it as necessary to support CodePlex (and our applications).

One thing you'll notice is that we've "internalized" ObjectBuilder rather than taking a dependency on it. This allows us to make modifications and tweaks to the things inside of the original ObjectBuilder project. The changes are few at the moment, but we're planning a few semi-sweeping architectural changes in the next couple weeks.

We're also planning on adding method interception to our container over the next few weeks, based loosely on the PIAB block that's part of EntLib 3. Our code will not be derived from nor compatible with PIAB; it's our own implementation of similar concepts, incorporating bits of the PIAB code with our own interception mechanism we use for mocks in unit testing, plus a bunch of hand-rolled code specifically for the container.

We'd love to see feedback on this container.

P.S. You can consider the old DependencyContainer sample to be pretty much dead at this point. If I ever get around to finishing my generalized event broker system, it will be integrated into the CodePlexContainer.
Coordinator
Jun 2, 2007 at 12:01 AM
Edited Jun 2, 2007 at 12:02 AM
We've just checked in some major changes, available in change set 23192.

We did a lot of simplification and cleanup of the ObjectBuilder core itself inside of the container project. We made the following changes:
  • Locator previously had the concept of encapsulation, but policies and strategies did not. This made it difficult to write an encapsulating container, like we wanted DependencyContainer to become. So we enhanced PolicyList and StrategyList to support encapsulation. An additional benefit here is that policies are not copied any more when the build begins (we'd like to kill the copying of the strategies too, but have not yet done it).
  • The lifetime container is passed to BuildUp and TearDown rather than being "found" in the locator. This was a huge source of user unfriendliness in the builder API. The lifetime container is available to strategies via the context object now.
  • The Builder class does not contain policies and strategies any more; they are passed to BuildUp and TearDown explicitly. This eliminated the need for the BuilderBase class; previous uses of BuilderBase should use Builder instead. This means that Builder is not pre-configured with strategies any more, since builders don't hold policies or strategies. We temporarily removed the configurators, which were designed against the old API. Since builders don't hold strategies any more, IBuilder and BuilderBase (which became Builder) are not generic any more.
  • PolicyList supports "default for type" policies now, in addition to "policy for a specific type and name" and "default for all types". This means you can set a default policy for all IFoo objects, regardless of name, which will be used (unless over-riden with a more specific type & name-based policy).
  • DependencyContainer supports encapsulating containers, which work as a "live template" for the new container. The outer (encapsulating) container will delegate to the inner container for location, policies and strategies when the answer is not available locally. This meant we could delete SearchMode, since it wasn't architecturally appropriate any more.
  • Gradual introduction of the CodePlex.NUnitExtensions.Assert class (lots more to be done here).
  • New MockBuilderContext class
  • Temporarily removed DependencyContainer.Inject() (which was unused).
Jun 5, 2007 at 2:40 PM
This is truely great news. Fantastic work!
Coordinator
Jun 8, 2007 at 1:54 AM
Scott and I have made more changes, available in change set 23429.

What We Added


We introduced a generalized method interception system based loosely on the Policy Injection Application Block from EntLib 3. The reason I say that is because large parts of the PIAB code are unnecessary when using ObjectBuilder as the working pipeline. It was not a goal of ours to get PIAB compatibility (in fact, we're throwing out any compatibility necessary, even with core OB, to improve design). Most of the code that we took directly from PIAB was for the Remoting support. There were also a number of classes that were originally Remoting-specific that got simplified into generalized versions (like the input and output parameters collections). The end result is that we used less than half the code as PIAB, as a rough guess.

Like PIAB, our first swag includes support for using Remoting for the interception. We are also going to add support for code-gen'ing derived classes to intercept virtual methods.

We also extended the builder context to include the originally requested type and ID. This was necessary for interception, but it also opens up being able to fix some long-standing bugs around type mapping and singletons.

Strategies and Policies for Interception


From a raw ObjectBuilder sense, we introduced two strategies: InterceptionReflectionStrategy (which should be done somewhere in PreCreation, post TypeMapping), and RemotingInterceptionStrategy (which should be done absolutely last, in PostInitialization, even after BuilderAware). RemotingInterceptionStrategy is informed via policies of type IInterceptionPolicy. The handlers for interception need to implement IInterceptionHandler. Most of you probably don't care about the raw OB stuff, so if you're interested, consult the unit tests to see how it's all put together.

Remoting-Style Interception


To use remoting for interception, one of two things needs to be true: (a) the type you're building derives from MarshalByRefObject, or (b) the type you requested is an interface. You can use interception through two methods today: via code, and via attributes.
Configuring Interception With Code

To use interception via code, there are two methods introduced onto DependencyContainer: SetInterceptionType() and Intercept(). Before you call Intercept(), you must tell it which type of interception it should perform (although the enum today has values for Remoting and VirtualMethod, only Remoting is actually functional). Samples of how this works are found in DependencyContainerTest in the unit test library.

Here is an example:

// The class that will be created and intercepted
 
public class InterceptedClass : MarshalByRefObject
{
    public void InterceptedMethod(...params...)
    { ... }
}
 
//  The handler
 
class SampleHandler : IInterceptionHandler
{
    public IMethodReturn Invoke(IMethodInvocation call, GetNextHandlerDelegate getNext)
    {
        // Do any pre-method call work here...
 
        // Call the next handler in the chain
        IMethodReturn result = getNext().Invoke(call, getNext);
 
        // Do any post-method call work here...
 
        return result;
    }
}
 
// Configuring the container with code
 
DependencyContainer container = new DependencyContainer();
container.SetInterceptionType<InterceptedClass>(InterceptionType.Remoting);
MethodInfo method = typeof(InterceptedClass).GetMethod("InterceptedMethod");
container.Intercept<InterceptedClass>(method, new SampleHandler()); // Can provide as many handlers as you want...
 
InterceptedClass klass = container.Get<InterceptedClass>();
klass.InterceptedMethod(); // Calls the handlers transparently
You can provide as many handlers as you'd like for a single method, and you can intercept as many methods as you'd like. The handlers can short-circuit calls to the objects themselves by simply never calling the next handler in the chain. You can modify the in-bound parameters through the use of the IMethodInvocation object that you're given during the call. You can also modify the out and return values by manipulating the IMethodReturn object that comes from calling the next handler in the chain.

One of the nice things about using remoting for interception is that the methods to be intercepted don't have to be virtual. However, it does require that you have MarshalByRefObject somewhere in your inheritance hierarchy, which isn't always possible. Remoting does allow you to remote interfaces, even if the class doesn't support MBRO. You can see this behavior in action with our container, in combination with TypeMapping:

public interface IInterceptedInterface
{
    void InterceptedMethod(...params...)
}
 
public class InterceptedClass : IInterceptedInterface
{
    public void InterceptedMethod(...params...)
    { ... }
}
 
DependencyContainer container = new DependencyContainer();
container.RegisterTypeMapping<IInterceptedClass, InterceptedClass>();
container.SetInterceptionType<InterceptedClass>(InterceptionType.Remoting);
MethodInfo method = typeof(InterceptedClass).GetMethod("InterceptedMethod");
container.Intercept<InterceptedClass>(method, new SampleHandler());
 
IInterceptedClass klass = container.Get<IInterceptedClass>();
Even though the class does not derive from MBRO, we can still wrap it, since the request was for an interface and not the concrete class type. Note that the registration still takes place with the concrete class, not the interface. This allows you to have different rules for different concrete classes that implement an interface.
Configuring Interception with Attributes

So, all this code is fine, but verbose. For those who prefer attributes, you can use [InterceptType] on classes and [Intercept] on methods:

[InterceptType(InterceptionType.Remoting)]
public class InterceptedClass : MarshalByRefObject
{
    [Intercept(typeof(SampleHandler))]
    public void InterceptedMethod(...params...)
    { ... }
}
 
DependencyContainer container = new DependencyContainer();
InterceptedClass klass = container.Get<InterceptedClass>();
klass.InterceptedMethod();
You can stack up as many [Intercept] attributes as you'd like to add multiple interceptors.

Next Up


Virtual method interception is probably next. CAB-less EventBroker is probably still a ways out.
Coordinator
Jun 9, 2007 at 7:27 AM
Guess I spoke too soon. We decided to tackle event broker today. See change set 23461.

What We Added


We were able to get the basics of EventBroker up and running today without too much difficulty. Again, the name of the game was simplicity. The amount of code required to do it is dramatically smaller than what we had in CAB. Of course, we dumped a bunch of features, too, which makes it a lot easier to have less code. :)

As is the common pattern, we added two strategies (EventBrokerReflectionStrategy and EventBrokerStrategy) and a policy used to communicate between the two (IEventBrokerPolicy). All the heavy lifting is done by EventBrokerService, which DependencyContainer registers in itself on creation. Becuase CAB used a single strategy, it was entirely dependent on attributes and reflection, which was one of its biggest short-comings. Our new 2-strategy & policy system allows use of EventBroker through code as well as attributes.

Configuring EventBroker with Code


Two methods were added to DependencyContainer that allow you to configure EventBroker via code: RegisterEventSink and RegisterEventSource. A "source" is the generator of the event, and a "sink" is the receiver of the event. Like the EventBroker in CAB, we support multiple simultaneous sinks and sources for the same event ID.

This test from the DependencyContainerTest class shows the use of registration via code:

class EventSourceCode
{
    public event EventHandler<EventArgs> TheEvent;
 
    public void Invoke()
    {
        if (TheEvent != null)
            TheEvent(this, EventArgs.Empty);
    }
}
 
class EventSinkCode
{
    public EventArgs HandlerArgs;
 
    public void TheHandler(object sender,
                           EventArgs e)
    {
        HandlerArgs = e;
    }
}
 
[Test]
public void RegisterByCode()
{
    DependencyContainer container = new DependencyContainer();
    container.RegisterEventSource<EventSourceCode>("TheEvent", "MyEventID");
    container.RegisterEventSink<EventSinkCode>("TheHandler", "MyEventID");
    EventSourceCode source = container.Get<EventSourceCode>();
    EventSinkCode sink = container.Get<EventSinkCode>();
 
    source.Invoke();
 
    Assert.NotNull(sink.HandlerArgs);
}
The registration sets up the relationship: both register for the event ID "MyEvent". We ask the container to make instances of both, and the container automatically wires them today. When we ask the source to invoke the event, the test verifies that the sink did actually get called.

The current container does not contain tear-down functionality, so the only way that events get unwired is when the objects go out of scope and get garbage collected (or when the container gets cleaned up via Dispose). We're evaluating options here, but don't know the right answer quite yet.

Configuring EventBroker with Attributes


As usual, attributes make for less code. Here is the attribute-based unit test:

class EventSourceAttr
{
    [EventSource("MyEvent")]
    public event EventHandler<EventArgs> TheEvent;
 
    public void Invoke()
    {
        if (TheEvent != null)
            TheEvent(this, EventArgs.Empty);
    }
}
 
class EventSinkAttr
{
    public EventArgs HandlerArgs;
 
    [EventSink("MyEvent")]
    public void TheHandler(object sender,
                           EventArgs e)
    {
        HandlerArgs = e;
    }
}
 
[Test]
public void RegisterByAttributes()
{
    DependencyContainer container = new DependencyContainer();
    EventSourceAttr source = container.Get<EventSourceAttr>();
    EventSinkAttr sink = container.Get<EventSinkAttr>();
 
    source.Invoke();
 
    Assert.NotNull(sink.HandlerArgs);
}
Functionally identical to the test above, except the calls to configure the container are replaced with attributes on the event and method.

Missing Features


There are a few things that have not been done. I don't know whether we will do them or not.
  • Calling sinks on threads other than the source event's thread
  • Container hierarchy (right now, all events are local only)
  • Event handlers with additional parameters beyond the sender and the event data
  • Static sink methods and source events
  • Adding teardown support on the container to terminate event-wireup forcefully

Next Up


I'm on vacation next week, so it's time for a bit of radio silence. :) Virtual method interception is still high on the list, and I think I'm just about ready to re-introduce the concept of XML configuration of the container.
Jun 13, 2007 at 8:02 AM
Edited Jun 13, 2007 at 8:03 AM
Hi Brad,

Will you try to get injection and singletons work together? I noticed the codeplex container still uses a CreationStrategy that also registers the newly created object for use with the singleton policy, but since interception only happens at a later stage, you will end up with two objects (the original one and the intercepted one), which makes the singleton policy useless. Could we remove object registration from CreationStrategy and move it into another (very late) stage?
Jun 13, 2007 at 4:17 PM
Brad,

Could you elaborate a bit on the relationship between the CodePlexContainer and the ObjectBuilder project? Is there some communication going on between the ObjectBuilder team and yours on merging the two projects?

Derek
Coordinator
Jun 13, 2007 at 6:00 PM
Derek,

We are talking with the p&p team. They have not done anything with ObjectBuilder since Brad left. We are probably going to make our code Object Builder v 2.0.

Scott
Coordinator
Jun 13, 2007 at 9:14 PM
Yeah, we haven't addressed any of the issues re: singleton strategy and creation strategy bugs yet. It's on the list. :)
Coordinator
Jun 28, 2007 at 10:21 PM
Edited Jun 30, 2007 at 3:28 AM
We were back at it yesterday (and a little today) to finish virtual method interception. We also addressed Ewout's bug with the singleton strategy and registration of wrapped types. See change set 24213. (Change set updated for the bug fixes mentioned below.)

What We Added


There's very little visible change to the API, since we'd already put the hooks for virtual method interception into place. Now you can specify virtual method interception when decorating your intercepted class:

[InterceptType(InterceptionType.VirtualMethod)]

How It Works


The heavy lifting, for those who want to look in the code, takes place in VirtualMethodInterceptor and VirtualMethodProxy. The interception is done by dynamically creating a wrapper class that derives from the class that's being wrapped, and overriding the methods with emitted IL that leverages the proxy class. The IL generation takes place in VirtualMethodInterceptor and the handling of the callback chain takes place in VirtualMethodProxy.

Using virtual method interception gives you more flexibility of the types you can intercept because it does not make demands of your inheritance tree. Virtual method interception comes with the following requirements:
  • The intercepted class cannot be sealed
  • The intercepted method must be public, virtual, and not sealed

Just Methods?


Although it's only a theory at this point, we see no reason that you can't do this for the individual methods of properties, since in the underlying type system they appear to be methods. You can even apply the Intercept attribute to the get/set methods of the property (not the property itself). We'd be interested to know if anybody actually uses this, as we thought it was unlikely that people would want to intercept properties.

What's Next?


As soon as this version settles a bit, we will probably fix some of the most annoying bugs and create a binary release for the CodePlex Container. We've been talking to the p&p people, because we would like our improved version of ObjectBuilder to officially become OB 2.0. Stay tuned for details...
Coordinator
Jun 29, 2007 at 4:26 AM
After contemplating the implications of our implementation, I think what we've got is broken for certain scenarios (non-intercepted methods). Oops. :) So what we did today is really only usable for testing scenarios, or scenarios where every method is intercepted. Be fore-warned if you start using the code now.

We're thinking about ways to fix the problem.

Sorry!
Coordinator
Jun 29, 2007 at 10:05 PM
We fixed our implementation today, and in the process, we removed one of the requirements (I'm just going to edit the post above to remove the constructor requirement that's no long necessary).

We also got found a way (via Peter) to fudge the stack trace when the intercepted method throws an exception so that it appears like the original exception along with the original stack trace. It's hackish, so of course we love it. :)
Jun 30, 2007 at 10:56 PM
Edited Jun 30, 2007 at 10:59 PM
Hey guys,
it's great to see OB moving forward.

Some feedback from what I've seen just from this thread (I HAVE NOT LOOKED AT THE CODE):

  • I can easily see DependencyContainer becoming a mammoth of a class just like what happened to CAB WorkItem. It contains methods for all of the different pieces that are really just pluggable "services" if you will (i.e. interception, event broker, etc). I'd like to see more decoupling there, so that the container is more easy to extend with new "services". i.e.:
IEventBroker broker = container.Adapt<IEventBroker>();
broker.RegisterXXX();
I'd like the container to remain simple, so that it can be reused whenever none of the extended services are used (and avoiding their overhead).
The adapter-based approach is cool (and extensively used in Eclipse) because it allows you to adapt any object to any interface, provided there's an adapter registered. This makes for easy to evolve APIs. I'd add that capability to the container, so that you can also adapt arbitrary objects:

IFoo foo = container.Adapt<IFoo>(bar);  // Bar was typically retrieve from the same container.
  • I'd suggest that the full CAB Event Broker is brought-in. A substantial amount of work went in testing it with a myriad of scenarios, optimizing it throughout, and the UI thread marshaling feature is a huge win for users, as well as the hierarchical events (although I'd add downwards propagation too). Starting again from scratch doesn't sound to me like a good thing to do. CAB event broker has been successfully tested in real-world scenarios, and I'm more than confident on the quality of that code.
  • "fudge the stack trace": I assume you're aware of the expected behavior when arbitrary method inlining can happen through the JITter ;)
  • I'm not sure why there's a need for the InterceptionType enumeration. The kind of interception should be pluggable, and the handler pipeline should be generic and reusable. Seems limiting to have a single implementation which surely contains a lot of reusable code that I'll not be able to use if I want to create a new interception mechanism (i.e. use Reflection.Emit'ed interface implementation and restrict the interception to interfaces only, avoiding remoting altogether).

Cool stuff I say!
Coordinator
Jul 1, 2007 at 12:20 AM
Thanks for the feedback d :). Here are some things we are thinking about :

Right now the container is more of a sample container. I can see us changing it as we move forward with a release.
I do believe we brought over the full EventBroker except for the UI marshaling.
The InterceptionType is just based on our implementation right now. We put it in for now knowing we would be changing it eventually.
Coordinator
Jul 1, 2007 at 6:52 AM

I can easily see DependencyContainer becoming a mammoth of a class just like what happened to CAB WorkItem. It contains methods for all of the different pieces that are really just pluggable "services" if you will (i.e. interception, event broker, etc). I'd like to see more decoupling there...

Decoupling comes at the price of ease of use. Builder is already the de-coupled class, and the feedback I've heard loud and clear is that the API is just not appropriate.

The faults with WorkItem had little to do with coupling of services (in fact, it really didn't do that at all). That's a discussion for another thread/time.


I'd like the container to remain simple, so that it can be reused whenever none of the extended services are used (and avoiding their overhead).

Again, the feedback we hear again and again is: make a container that's easy to understand and use. It's not like there's only one container in the world: make your extensible container and add it to the samples area here. :)


I'd suggest that the full CAB Event Broker is brought-in.

I definitely disagree here, especially with your contention about the threading stuff. It was absolutely the wrong place to put threading support. In fact, with method interception, it becomes trivial to solve the problem in the right place: in the view.

See my blog post for more on my thoughts here.


"fudge the stack trace": I assume you're aware of the expected behavior when arbitrary method inlining can happen through the JITter ;)

Actually, this is about catching and stashing an exception that you need to re-throw later, without losing the original stack trace.


I'm not sure why there's a need for the InterceptionType enumeration.

We talked about this when we did it, because we didn't really like the enum, either. We debated using two policy types rather than a single policy type with the interception type as a parameter. Each way has plusses and minuses.

Thanks for your feedback! Keep it coming! :)
Coordinator
Jul 1, 2007 at 6:59 AM

I do believe we brought over the full EventBroker except for the UI marshaling.

Actually, it's all new code, and most of the features from CAB are missing. The original CAB implementation was tied intimately to the attributes rather than starting with code-based usage. The code is dramatically simpler as a result.
Jul 1, 2007 at 4:03 PM
Interesting discussion :)

I agree 100% with your post regarding the responsibility of determining the right thread: it definitely doesn't belong on the presenter. Putting it in the eventbroker service in CAB isn't precisely putting it in the presenter, so I think that wasn't wrong anyway.

I also agree that with an interception mechanism in place, it could be solved at that level. I think some real-world testing should be done before going in that direction. Intercepting all members of a control, involving the pretty heavyweight remoting infrastructure, just to solve this seems a bit overkill to me. Placing this in the event broker means it can be more performant by just applying to the handling of events (maybe it could also be part of the command handling), and not involving any remoting stuff. Granted, the event broker should not be tied to WinForms, so determining the right thread could be a strategy of the broker service.

It's a different approach, but it also decouples the responsibility. And I think this is such boring and repetitive code that you have to always remember in the view that it should belong in some piece of infrastructure, not in the view (same thing you're proposing to do with interception). So I think we agree on that too.

Regarding usability vs decoupling, I see two levels of layering: the Builder (and the corresponding policies and strategies) which what people don't like about OB. That's because the second layer, the container, was missing. Achieving decoupling at this level doesn't necessarily mean we'll end up in the same situation as the Builder. Keep in mind that very few people will be using your RegisterXXX, Intercept, etc, but everyone will see those members when all they want to do is Get<> or Build<> something. I think those infrastructure methods will generally be used by configurators or custom containers that provide pre-packaged behavior, but I certainly don't see end users doing those tasks. Most of them hardly get the concept of interceptors and handlers ;).

So, keeping the interface of the container highly focused on the core tasks of the regular user (getting and building stuff) and separating the infrastructure behavior into separate interfaces, will actually increase usability IMO. To the already advanced user that will be using RegisterXXX methods, adapting the container interface via a method before getting to the actual broker interface hardly sounds like a difficult task for someone who already understands interception, strategies, etc.

Finally, I agree that we can have multiple containers. But when it comes the time to decide which one will become OB 2.0, that will no longer be the case ;). There will be a "blessed" one, and we should discuss which one that is ;)
Coordinator
Jul 2, 2007 at 4:55 PM

I also agree that with an interception mechanism in place, it could be solved at that level. I think some real-world testing should be done before going in that direction. Intercepting all members of a control, involving the pretty heavyweight remoting infrastructure, just to solve this seems a bit overkill to me.

I'm not a fan of the remoting version of interception. I much prefer the virtual method version, which creates derived classes to do the work. The performance impact is relatively negligible, certainly much much lower than remoting.

Of course, if you call the public APIs of your WinForms views so often that interception becomes your bottleneck, then perhaps you have another problem. :)


Regarding usability vs decoupling, I see two levels of layering: the Builder (and the corresponding policies and strategies) which what people don't like about OB. That's because the second layer, the container, was missing. Achieving decoupling at this level doesn't necessarily mean we'll end up in the same situation as the Builder. Keep in mind that very few people will be using your RegisterXXX, Intercept, etc, but everyone will see those members when all they want to do is Get<> or Build<> something. I think those infrastructure methods will generally be used by configurators or custom containers that provide pre-packaged behavior, but I certainly don't see end users doing those tasks. Most of them hardly get the concept of interceptors and handlers ;).

It really depends on how you want to wire things together. Some people want to use code, some want to use attributes, and some want to use XML. I think you need to leave the door open for all three in simple-to-use ways. IMO, Builder should be mute about how to use strategies, but specific instances of containers should take a stance and provide user-friendly APIs.

We also have your highly focused API, which is IObjectFactory. You can pass this around and avoid all the non-building methods of the container. :)


Finally, I agree that we can have multiple containers. But when it comes the time to decide which one will become OB 2.0, that will no longer be the case ;). There will be a "blessed" one, and we should discuss which one that is ;)

I really don't agree that there will be one blessed container. I think, if we ship containers in OB 2.0 at all, then it makes sense to ship at least 2 (if not more) that are purpose-built containers, not the one be-all, end-all container (which I don't believe exists, personally).

We have a meeting with the p&p team tomorrow, and I'll remember to bring up your points (or you can e-mail your thoughts ahead to Blaine). :)
Jul 2, 2007 at 9:04 PM
Of course, if you call the public APIs of your WinForms views so often that interception becomes your bottleneck, then perhaps you have another problem. :)

It's actually much worse than that. If you're intercepting all virtuals, then you'll also be intercepting things like OnPaint, etc. (note: you don't need any priviledged access to intercept/override protected members; limiting the feature to public virtuals may be an alternative, but it could be annoying - i.e. I have a base "provider" class of some sort, which provides basic functionality and hooks via protected virtuals. this is very common across the framework)

BTW I want to know how you quote me!!! It's not in the Wiki Markup Guide
Coordinator
Jul 2, 2007 at 9:59 PM

dcazzulino wrote:
It's actually much worse than that. If you're intercepting all virtuals, then you'll also be intercepting things like OnPaint, etc.

Performance isn't a problem until it's a problem. :)


(note: you don't need any priviledged access to intercept/override protected members; limiting the feature to public virtuals may be an alternative, but it could be annoying - i.e. I have a base "provider" class of some sort, which provides basic functionality and hooks via protected virtuals. this is very common across the framework)

Yes, we limit it to publics, as with the rest of ObjectBuilder.


BTW I want to know how you quote me!!! It's not in the Wiki Markup Guide

If you want to quote a post, on the upper right corner of that post is a "Reply" link, which will quote the original post. Use that, rather than the button at the bottom. The tags are {quote:} to start a quote and {quote} to end it.
Oct 26, 2007 at 12:09 PM
I tried to create a nested tree of containers:

DependencyContainer inner = new DependencyContainer();
DependencyContainer nested = new DependencyContainer(inner);
DependencyContainer outer = new DependencyContainer(nested);

Unfortunately, this doesn't seem to work: MakeStrategyChain() builds an empty chain, so strategies.Head remains nothing.

It can easily be reproduced by adapting the last unit-test in the DependencyContainer tests....

Isn't this supposed to be possible, or do I simply not fully understand container nesting?



Coordinator
Oct 26, 2007 at 5:28 PM

Ewout wrote:
Isn't this supposed to be possible, or do I simply not fully understand container nesting?

That should work. I'll take a look at it.
Nov 1, 2007 at 12:43 AM


BradWilson wrote:

Ewout wrote:
Isn't this supposed to be possible, or do I simply not fully understand container nesting?

That should work. I'll take a look at it.


I took a look for myself: the problem occurs because StagedStrategyChain.MakeStrategyChain() isn't really recursive. It checks its innerChain (so only one level deeper), and adds the innerChain's stages, but not the stages of this innerChain's innerChain. And so on....

Maybe MakeStrategyChain shouldn't use the internal member "stages" but invoke some new CollectStages function which will recursively call CollectStages on its innerChain.

Anyway, let me know what you think about it.

Nov 1, 2007 at 1:03 AM
Edited Nov 1, 2007 at 1:11 AM


Ewout wrote:


BradWilson wrote:

Ewout wrote:
Isn't this supposed to be possible, or do I simply not fully understand container nesting?

That should work. I'll take a look at it.


I took a look for myself: the problem occurs because StagedStrategyChain.MakeStrategyChain() isn't really recursive. It checks its innerChain (so only one level deeper), and adds the innerChain's stages, but not the stages of this innerChain's innerChain. And so on....

Maybe MakeStrategyChain shouldn't use the internal member "stages" but invoke some new CollectStages function which will recursively call CollectStages on its innerChain.

Anyway, let me know what you think about it.



snippet from a fixed(?) StagedStrategyChain....


       public IList<IBuilderStrategy> CollectStrategies(TStageEnum stage)
        {
            List<IBuilderStrategy> result = new List<IBuilderStrategy>();
 
            result.AddRange(stages[stage]);
 
            if (innerChain != null)
                result.AddRange(innerChain.CollectStrategies(stage));
 
            return result;
        }   
 
        public StrategyChain MakeStrategyChain()
        {
            lock (lockObject)
            {
                StrategyChain result = new StrategyChain();
 
                foreach (TStageEnum stage in stageValues)
                {
                    //if (innerChain != null)
                    //    result.AddRange(innerChain.stages[stage]);
 
                    //result.AddRange(stages[stage]);
 
                    result.AddRange(CollectStrategies(stage));
                }
 
                return result;
            }
        } 

Unfortunately I cannot run the xUnit tests yet, so I'll hope it doesn't break something else....
Coordinator
Nov 1, 2007 at 10:25 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.