OB2: How to create a simple builder for singletons

May 19, 2008 at 7:34 PM
Hello.
I ran into difficulties when I started migrating my code from ObjectBuilder onto ObjectBuilder2.
I had simple builder for singletons:

public static class MyFactory {
        private static readonly BuilderBase<BuilderStage> s_builder = new BuilderBase<BuilderStage>(new MyObjectBuilderConfigurator());
        private static readonly IReadWriteLocator s_locator = createLocator();

        public static MyObject GetProvider(Type providerType)
        {
            return (MyObject)s_builder.BuildUp(s_locator, providerType, null, null);
        }
        private static Locator createLocator()
        {
            Locator locator = new Locator();
            LifetimeContainer lifetime = new LifetimeContainer();
            locator.Add(typeof(ILifetimeContainer), lifetime);
            return locator;
        }
}
and MyObjectBuilderConfigurator:
    internal class MyObjectBuilderConfigurator: IBuilderConfigurator<BuilderStage>
    {
        /// <summary>
        /// <see cref="IBuilderConfigurator{TStageEnum}.ApplyConfiguration"/> for details.
        /// </summary>
        /// <param name="builder"></param>
        public void ApplyConfiguration(IBuilder<BuilderStage> builder)
        {
            builder.Strategies.AddNew<SingletonStrategy>(BuilderStage.PreCreation);
            builder.Strategies.AddNew<ConstructorReflectionStrategy>(BuilderStage.PreCreation);
            builder.Strategies.AddNew<CreationStrategy>(BuilderStage.Creation);
                       
            builder.Policies.SetDefault<ISingletonPolicy>(new SingletonPolicy(true));
        }
    }
That code worked.
Now I'm trying to migrate onto OB2. There are no more such strategies: SingletonStrategy, CreationStrategy, ConstructorReflectionStrategy.
For singleton we have LifetimeStrategy and SingletonLifetimePolicy. Ok. But how to create object for the first time?
I tried this:
    public static class MyFactory
    {
        private static readonly Builder s_builder = new Builder();
        private static readonly IStrategyChain s_builderStrategyChain;
        private static readonly IPolicyList s_builderPolicies;

        static MyFactory ()
        {
            var chain = new StagedStrategyChain<BuilderStage>();
            chain.AddNew<LifetimeStrategy>(BuilderStage.PreCreation);
            chain.AddNew<DynamicMethodConstructorStrategy>(BuilderStage.Creation);
            s_builderStrategyChain = chain.MakeStrategyChain();

            s_builderPolicies = new PolicyList();
            s_builderPolicies.Set(new SingletonLifetimePolicy(), null);

            s_builderPolicies.Set<IConstructorSelectorPolicy>(
                new FixedConstructorSelectorPolicy(new SelectedConstructor(typeof(XStorageProvider).GetConstructor(Type.EmptyTypes))), null);
        }

        public static XStorageProvider GetProvider(Type providerType)
        {
            return (XStorageProvider)s_builder.BuildUp(null, null, s_builderPolicies, s_builderStrategyChain, providerType, null);
        }
}
Class FixedConstructorSelectorPolicy is from EntLib4. It just hold reference to ConstructorInfo.

But, in DynamicMethodConstructorStrategy.PreBuildUp I get NullReferenceException on bolded line:
        public override void PreBuildUp(IBuilderContext context)
        {
            Guard.ArgumentNotNull(context, "context");

            DynamicBuildPlanGenerationContext buildContext =
                (DynamicBuildPlanGenerationContext)context.Existing;

            IConstructorSelectorPolicy selector = context.Policies.Get<IConstructorSelectorPolicy>(context.BuildKey);

            SelectedConstructor selectedCtor = selector.SelectConstructor(context);
            Label existingObjectNotNull = buildContext.IL.DefineLabel();
because of buildContext is null.

What should I do?
Thanks in advance.
May 19, 2008 at 9:45 PM
My first suggestion would be to use Unity as your container instead of trying to build one on top of OB2, but that's probably not very helpful.

The fundamental issue here is that you shouldn't add the DynamicMethodConstructorStrategy to the build chain. What you need to do is define TWO strategy chains. The first is the normal chain you're used to. It needs to include the BuildPlanStrategy.

Next, you need to create a second strategy chain. That second strategy chain is where you add the DynamicMethodConstructorStrategy.

Third, you create a DynamicMethodBuildPlanCreatorPolicy. It takes the second strategy chain as a constructor parameter. Add the policy to the policy list.

From that point you should be ok - you add the selector policies to the policy set as expected.

The reason it's done this way is to separate the strategies that create the build plan for an object from the strategies that actually create the object. This'll fix the buildContext == null issue. Look at the Unity code and the OB2 unit tests for examples.

Another option would be to go over to the UnityContrib site - all the old strategies that were removed from OB2 are available over there.

-Chris
May 20, 2008 at 9:19 AM
Chris, thanks for an answer.
Trying to follow your advice:
        static MyFactory()
        {
            // the FIRST chain with BuildPlanStrategy
            var chain = new StagedStrategyChain<BuilderStage>();
            chain.AddNew<LifetimeStrategy>(BuilderStage.PreCreation);
            chain.AddNew<BuildPlanStrategy>(BuilderStage.Creation);
            s_builderStrategyChain = chain.MakeStrategyChain();

            // the SECOND chain with DynamicMethodConstructorStrategy
            var chain2 = new StagedStrategyChain<BuilderStage>();
            chain2.AddNew<DynamicMethodConstructorStrategy>(BuilderStage.Creation);
            // > Third, you create a DynamicMethodBuildPlanCreatorPolicy. It takes the second strategy chain as a constructor parameter.
            var p = new DynamicMethodBuildPlanCreatorPolicy(chain2);

            s_builderPolicies = new PolicyList();
            s_builderPolicies.Set(new SingletonLifetimePolicy(), null);
            // >Add the policy (DynamicMethodBuildPlanCreatorPolicy) to the policy list.
            s_builderPolicies.Set(typeof(DynamicMethodBuildPlanCreatorPolicy), p, null);

            // >you add the selector policies to the policy set as expected.
            s_builderPolicies.Set<IConstructorSelectorPolicy>(
                new FixedConstructorSelectorPolicy(
                    new SelectedConstructor(typeof(XStorageProvider).GetConstructor(Type.EmptyTypes))
                    ),
                null);
        }
no luck:
during runtime BuildPlanStrategy doesn't find any policy.

Honestly I don't quite understand buildKey concept. Could you clarify it a bit. When we add policy to builder's policy list we specify buildKey. In my case I don't know what types exactly my builder will build. So I pass null as buildKey. It seems to me (plz correct me if I'm wrong) that during runtime strategies choose pocilies relying on a buildKey, which passed into BuildUp builder's method. And that buildKey is only way to specify desirable type to build. And if I added all policies with null buildKey none of them will be selected during BuildUp time. Is it the true?
If so how to apply policies for building all types?

After all in the Test.ObjectBuilder project I found what I probably neeed - ActivatorCreationStrategy :
    class ActivatorCreationStrategy : BuilderStrategy
    {
        public override void PreBuildUp(IBuilderContext context)
        {
            if(context.Existing == null)
            {
                context.Existing = Activator.CreateInstance(BuildKey.GetType(context.BuildKey));
            }
        }
    }
then:
        static MyFactory()
        {
            var chain = new StagedStrategyChain<BuilderStage>();
            chain.AddNew<LifetimeStrategy>(BuilderStage.PreCreation);
            chain.AddNew<ActivatorCreationStrategy>(BuilderStage.Creation);
            s_builderStrategyChain = chain.MakeStrategyChain();
            s_builderPolicies = new PolicyList();
            s_builderPolicies.Set(new SingletonLifetimePolicy(), null);
        }

it works, but without singleton. Each time new instance is created. It's because of my misunderstanding of buildKey probably.
But inspite of ActivatorCreationStrategy works I'd like to learn how to use DynamicMethodConstructorStrategy.
May 20, 2008 at 9:59 AM
Ok, as for buildKey, it seems to me I get the gist. I need to use IBuildKeyMappingPolicy and BuildKeyMappingStrategy:
        static MyFactory()
        {
            var chain = new StagedStrategyChain<BuilderStage>();
            chain.AddNew<BuildKeyMappingStrategy>(BuilderStage.PreCreation);
            chain.AddNew<LifetimeStrategy>(BuilderStage.PreCreation);
            chain.AddNew<ActivatorCreationStrategy>(BuilderStage.Creation);
            s_builderStrategyChain = chain.MakeStrategyChain();

            s_builderPolicies = new PolicyList();
            s_builderPolicies.Set(typeof(IBuildKeyMappingPolicy), new BuildKeyMappingPolicy(null), null);
            s_builderPolicies.Set(typeof(ILifetimePolicy), new SingletonLifetimePolicy(), null);
        }
then, in ActivatorCreationStrategy I have to use context.OriginalBuildKey instead of context.BuildKey:
        public override void PreBuildUp(IBuilderContext context)
        {
            if (context.Existing == null)
            {
                context.Existing = Activator.CreateInstance(BuildKey.GetType(context.OriginalBuildKey));
                context.BuildComplete = true;
            }
        }
Ok.
But if could clarify the solution with DynamicMethodConstructorStrategy I would be appreciate.
May 20, 2008 at 7:39 PM
The build key is used for many things in the OB chain. It's how we indicate what we want to build. It's used as a dictionary lookup key when looking up policies.

What you're missing, I think, is that you're calling PolicyList.Set. That associated the policy with the specific build key you pass, and no other. You probably want to use SetDefault instead, which says "if you don't find a better match, use this policy instance". SetDefault lets you set a, well, default policy instance for a particular policy interface. If you do a Set later for a specific build key, only that build key will get that policy instance.

You've use OB 1. Remember how everything was done using type and id, and the DependencyResolutionKey class? The build key is just a generalization of that, it serves the exact same purpose.