I don't think there is anything wrong with your design, although you already noticed that it isn't the easiest thing to configure. The problem isn't in the limitations of your DI framework, but more in the mental gymnastics you'll have to perform.
Here is an idea. Change your classes to the following:
public class Upper : IUpper, ILowerToUpperCallback
{
public Upper(/* all depedencies except ILower */) { }
// Promote ILower to property dependency
public ILower Lower { get; set; }
}
public class Lower : ILower
{
// Use the Null Object Pattern for default implementation to prevent
// null checks.
private ILowerToUpperCallback callback = new NullCallback();
public Upper(/* all dependencies except ILowerToUpperCallback */)
{
this.callback = callback;
}
// Allow overriding the default implementation using a method, just
// as you are already did.
public SetCallback(ILowerToUpperCallback callback)
{
if (callback == null) throw new ArgumentNullException("callback");
this.callback = callback;
}
}
With this design you can wire everything up as follows:
container.Register<ILower, Lower>();
container.Register<IUpper, Upper>();
container.RegisterInitializer<Upper>(upper =>
{
var lower = (Lower)container.GetInstance<ILower>();
lower.SetCallback(upper);
upper.Lower = lower;
});
Since Lower
and Upper
are normal services you can resolve them as usual. By registering an initializer delegate for Upper
, you can do some extra initialization after the container created Upper
. This initializer wires Lower
and Upper
together. Since the Composition Root knows about ILower
and Lower
, we can safely cast from ILower
to Lower
, without breaking any rule. Best about this design is that the ILower
interface is kept clean, and oblivious of the ILowerToUpperCallback
, which is in fact an implementation detail.