Search code examples
c#design-patternsinversion-of-controlunity-containerproperty-injection

Best Design For Constantly Changing Property Within A Singleton Class With IoC Usage?


I have the following helper class which has public property _variableHandler. The property is public as I initially had visions of setting the property from the calling code before involving methods within the XAMLHelper class, but now doubt this is a smart approach. Mostly because I will need to call the class fairly often and not always update the value of _variableHandler, meaning things will get messy.

public class XAMLHelper
{
    public IVariableTypeHandler _variableHandler;

    public XAMLHelper()
    {
    }
}

I also have a factory which is used to provide the desired concrete instance of a VariableTypeHandler.

I am also using an IoC container (Unity) to provide a single instance of the XAMLHelper class, as shown below.

container.RegisterInstance<XAMLHelper>(new XAMLHelper());

Ideally, I would like to keep this single instance but simply update the value of _variableHandler when specified using like the code below.

container.Resolve(new PropertyInjection(VariableHandlerFactory.GetInstance("Str")));

I have tried adding snippets into container registration, such as a

new InjectionProperty()

But this doesn't seem to update the _variableHandler property once it has been instantiated the first time. Am I missing something important here? Or trying to do something that is not possible with an IoC container?


Solution

  • Generally, good design proposes making helper\manipulator\operation classes stateless. Here you clearly have a helper class which is stateful as you are able to set the state of the object through the property _variableHandler (not real property, it is more like a field).

    What you could do is to make factory for IVariableHandler register that within the IoC and inject it into XAMLHelper. Then when invoking helper you just specify which handler to use and re-create it with factory. Factory could be a bit smarter to reuse already create object using some kind of caching.

    public intefrace IVariableHandlerFactory
    {
        IVariableHandler Get(string description);
    }
    
    public class VariableHandlerFactory : IVariableHandlerFactory
    {
        private readonly IDictionary<string, IVariableHandler> _cache;
    
        public VariableHandlerFactory()
        {
            _cache = new Dictionary<string, IVariableHandler>();
        }
    
        public IVariableHandler Get(string description)
        {
            IVariableHandler handler; 
    
            if(_cache.TryGetValue(description, out handler))
            {
                return handler;         
            }
    
            handler = //create it...
            _cache[description] = handler;
    
            return handler;
        }
    }
    

    and then use that within XAMLHelper

    public class XAMLHelper
    {
        private readonly IVariableHandlerFactory _factory;
    
        public XAMLHelper(IVariableHandlerFactory factory)
        {
            _factory = factory;
        }
    
        public void HelpMe(string description)
        {
            var handler = _factory.Get(description);
    
            //do actual work using handler
        }
    }
    

    Let's put that aside, and discuss the real problem here. First, you are missing annotation of your property:

    [Dependency]
    public IVariableTypeHandler VariableHandler { get; set; }
    

    then you could register the XAMLHelper using the InjectionProperty

    container.RegisterType<VariableTypeHandlerImpl, IVariableTypeHandler>();
    
    using(var lifetime = new ContainerControlledLifetimeManager())
        container.RegisterType<XAMLHelper>(lifetime, new InjectionProperty("VariableHandler");
    

    or, if you have concrete instance of the IVariableTypeHandler you could use:

    var variableHandler = VariableHandlerFactory.GetInstance("Str");
    
    using(var lifetime = new ContainerControlledLifetimeManager())
        container.RegisterType<XAMLHelper>(lifetime, new InjectionProperty("VariableHandler", variableHandler);