Amit
Amit

Reputation: 26316

How to solve Autofac circular dependency?

Recently started using autofac and I hit a Circular dependency issue, which I had overcome while I was using Unity. Here is my code:

 void Main()
 {
    var builder = new ContainerBuilder();

    // 1) Every class needs logger
    // 2) Logger needs AppSettingsProvider which needs AppEnvironmentProvider
    // 3) Listener needs AppSettingsProvider which needs AppEnvironmentProvider

    builder.RegisterType<Logger>().As<ILogger>().SingleInstance();
    builder.RegisterType<AppSettingsProvider>().As<IAppSettingsProvider>().SingleInstance();
    builder.RegisterType<AppEnvironmentProvider>().As<IAppEnvironmentProvider>().SingleInstance();

    builder.RegisterType<Listener>().As<IListener>().SingleInstance();

    var container = builder.Build();

    var listener = container.Resolve<IListener>();
    listener.Do();

 }

 public interface IListener
 { 
    string Do();
 }

 public class Listener : IListener
 { 
    IAppSettingsProvider appSettingsProvider;
    public Listener(IAppSettingsProvider appSettingsProvider)
    {
        // this class needs IAppSettingsProvider to get some settings
        // but not actually used on this example.
        this.appSettingsProvider = appSettingsProvider;
    }
    public string Do()
    {
        return "doing something";
    }
 }

 public interface ILogger
 { 
    void Log(string message);
 }

 public class Logger : ILogger
 {
    IAppSettingsProvider appSettingsProvider;
    public Logger(IAppSettingsProvider appSettingsProvider)
    {
        this.appSettingsProvider = appSettingsProvider;
    }

    public void Log(string message)
    {
        // simplified
        if (this.appSettingsProvider.GetSettings())
        {
            Console.WriteLine(message);
        }
    }
 }

 public interface IAppSettingsProvider
 { 
    // will return a class, here simplified to bool
    bool GetSettings();
 }

 public class AppSettingsProvider : IAppSettingsProvider
 { 
    ILogger logger;
    public AppSettingsProvider(ILogger logger)
    {
        this.logger = logger;
    }

    public bool GetSettings()
    {
        this.logger.Log("Getting app settings");

        return true;
    }
 }


 public interface IAppEnvironmentProvider
 { 
    string GetEnvironment();
 }

 public class AppEnvironmentProvider : IAppEnvironmentProvider
 { 
    ILogger logger;
    public AppEnvironmentProvider(ILogger logger)
    {
        this.logger = logger;
    }
    public string GetEnvironment()
    {
        this.logger.Log("returning current environment");

        return "dev";
    }
 }

Any pointers on solving this will be helpful.

Upvotes: 4

Views: 7977

Answers (4)

Kibria
Kibria

Reputation: 1883

Another work around is to use Lazy<ILogger> in the constructor of the dependent services.

public class AppSettingsProvider : IAppSettingsProvider
{
    Lazy<ILogger> logger;
    public AppSettingsProvider(Lazy<ILogger> logger)
    {
        this.logger = logger;
    }
    public bool GetSettings()
    {
        this.logger.Value.Log("Getting app settings");
        return true;
    }
}

Upvotes: 7

Aviko
Aviko

Reputation: 1199

Autofac provides another option to overcome circular dependencies, In case that A needs B and B needs A, and B doesn't use A in its constructor, B can consumes A in a way called Dynamic Instantiation see

https://docs.autofac.org/en/latest/resolve/relationships.html#dynamic-instantiation-func-b

In this way B will use factory method of A, Func<A>, in its constructor, (and call it after the constructor, can be in some lazy way) to get get instance of A.

e.g. in the above question change Logger to overcome the circular dependency issue:

    public class Logger : ILogger
    {
        Func<IAppSettingsProvider> appSettingsProviderFactory;
        IAppSettingsProvider _appSettingsProvider;
        IAppSettingsProvider appSettingsProvider { get
            {
                if (_appSettingsProvider == null) _appSettingsProvider = appSettingsProviderFactory();
                return _appSettingsProvider;
            }
        }
        public Logger(Func<IAppSettingsProvider> appSettingsProviderFactory)
        {
            this.appSettingsProviderFactory = appSettingsProviderFactory;
        }

        public void Log(string message)
        {
            // simplified
            if (this.appSettingsProvider.GetSettings())
            {
                Console.WriteLine(message);
            }
        }
    }

(Note: when I added appSettingsProvider.GetSettings(); in Do(), the code in question get into

Process is terminating due to StackOverflowException.

since GetSettings calls Log and Log calls GetSettings but this is another story)

Upvotes: 3

Rick
Rick

Reputation: 146

You have 2 options here:

  1. Use Property Injection
  2. Use Factory (that might create a strong dependency with you factory)

Here is an example using Property Injection:

    static void Main()
    {
        var builder = new ContainerBuilder();

        // 1) Every class needs logger
        // 2) Logger needs AppSettingsProvider which needs AppEnvironmentProvider
        // 3) Listener needs AppSettingsProvider which needs AppEnvironmentProvider

        builder.RegisterType<Logger>().As<ILogger>().SingleInstance();

        builder.RegisterType<AppSettingsProvider>()
            .As<IAppSettingsProvider>()
            .SingleInstance()
            .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);

        builder.RegisterType<AppEnvironmentProvider>().As<IAppEnvironmentProvider>().SingleInstance();

        builder.RegisterType<Listener>().As<IListener>().SingleInstance();

        var container = builder.Build();

        var listener = container.Resolve<IListener>();

        Console.WriteLine(listener.Do());

        Console.Read();

    }

    public interface IListener
    {
        string Do();
    }

    public class Listener : IListener
    {
        IAppSettingsProvider appSettingsProvider;
        public Listener(IAppSettingsProvider appSettingsProvider)
        {
            // this class needs IAppSettingsProvider to get some settings
            // but not actually used on this example.
            this.appSettingsProvider = appSettingsProvider;
        }
        public string Do()
        {
            return "doing something using circular Dependency";
        }
    }

    public interface ILogger
    {
        void Log(string message);
    }

    public class Logger : ILogger
    {
        IAppSettingsProvider appSettingsProvider;
        public Logger(IAppSettingsProvider appSettingsProvider)
        {
            this.appSettingsProvider = appSettingsProvider;
        }

        public void Log(string message)
        {
            // simplified
            if (this.appSettingsProvider.GetSettings())
            {
                Console.WriteLine(message);
            }
        }
    }

    public interface IAppSettingsProvider
    {
        // will return a class, here simplified to bool
        bool GetSettings();
    }

    public class AppSettingsProvider : IAppSettingsProvider
    {
        ILogger logger;
        public AppSettingsProvider()
        {

        }

        public ILogger Logger { get; set; }

        public bool GetSettings()
        {
            Logger.Log("Getting app settings");

            return true;
        }
    }


    public interface IAppEnvironmentProvider
    {
        string GetEnvironment();
    }

    public class AppEnvironmentProvider : IAppEnvironmentProvider
    {
        ILogger logger;
        public AppEnvironmentProvider(ILogger logger)
        {
            this.logger = logger;
        }
        public string GetEnvironment()
        {
            this.logger.Log("returning current environment");

            return "dev";
        }
    }

Here is Autofac suggestion: Autofac reference

Upvotes: 5

Dessus
Dessus

Reputation: 2177

You would need to make them mutually exclusive in implementation. Example:

  1. You could remove the logging from fetching the settings
  2. You could remove the settings check from the logger

Circular references here indicate that you are probably not doing this in the way that is easily maintainable (ie you would have higher coupling).

If you wanted to keep your code as is and hack it to work, you can probably make your log method and getsettings method static. That is a hack in my opinion and you should try either option 1 or 2 listed at the top. The reason for this is because making something static in my opinion should not change the behavior of code but should instead be used for memory optimization (ie see singleton anti pattern for some similar reading in this area).

For your code I would suggest that you remove logging from the appsettingsprovider and to instead use the loggers initation of it to add log statements around the use of that class. Alternately you could explore:

  1. the factory pattern to try and wrap creation of either of your classes.
  2. Lastly in C# you can have a lambda / function property you could use to pass an instance to a class so that the reference is not going to recursively create new instances.

Upvotes: 2

Related Questions