Reputation: 26316
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
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
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
Reputation: 146
You have 2 options here:
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
Reputation: 2177
You would need to make them mutually exclusive in implementation. Example:
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:
Upvotes: 2