如何解决Autofac循环依赖?
How to solve Autofac circular dependency?
最近开始使用 autofac,我遇到了一个循环依赖问题,我在使用 Unity 时已经克服了这个问题。这是我的代码:
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";
}
}
解决此问题的任何指示都会有所帮助。
您需要使它们在实现中互斥。示例:
- 您可以从获取设置中删除日志记录
- 您可以从记录器中删除设置检查
此处的循环引用表明您可能没有以易于维护的方式执行此操作(即,您会有更高的耦合度)。
如果您想保持代码原样并修改它以使其正常工作,您可以将 log 方法和 getsettings 方法设为静态。在我看来,这是一个 hack,您应该尝试顶部列出的选项 1 或 2。这样做的原因是因为在我看来将某些东西设为静态不应该改变代码的行为,而应该用于内存优化(即在这方面的一些类似阅读中参见单例反模式)。
对于您的代码,我建议您从 appsettingsprovider 中删除日志记录,而是使用它的记录器启动来添加围绕 class 使用的日志语句。或者,您可以探索:
- 工厂模式尝试包装您的 classes 中的任何一个的创建。
- 最后,在 C# 中,您可以使用 lambda / 函数 属性 将实例传递给 class,这样引用就不会递归地创建新实例。
这里有 2 个选项:
- 使用属性注入
- 使用工厂(这可能会与您的工厂产生强烈的依赖关系)
这是一个使用 属性 注入的例子:
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";
}
}
Autofac 建议如下:
Autofac reference
Autofac 提供了另一个选项来克服循环依赖,如果 A 需要 B 并且 B 需要 A,并且 B 在其构造函数中不使用 A,B 可以使用 A以一种称为动态实例化的方式参见
https://docs.autofac.org/en/latest/resolve/relationships.html#dynamic-instantiation-func-b
这样,B 将在其构造函数中使用 A 的工厂方法 Func<A>
(并在构造函数之后调用它,可以采用一些惰性方式)来获取 A 的实例。
例如在上面的问题中更改 Logger 以克服循环依赖问题:
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);
}
}
}
(注意:当我在 Do() 中添加 appSettingsProvider.GetSettings(); 时,有问题的代码进入
Process is terminating due to WhosebugException.
因为 GetSettings 调用 Log 而 Log 调用 GetSettings 但这是另一回事)
另一种解决方法是在相关服务的构造函数中使用 Lazy<ILogger>
。
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;
}
}
最近开始使用 autofac,我遇到了一个循环依赖问题,我在使用 Unity 时已经克服了这个问题。这是我的代码:
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";
}
}
解决此问题的任何指示都会有所帮助。
您需要使它们在实现中互斥。示例:
- 您可以从获取设置中删除日志记录
- 您可以从记录器中删除设置检查
此处的循环引用表明您可能没有以易于维护的方式执行此操作(即,您会有更高的耦合度)。
如果您想保持代码原样并修改它以使其正常工作,您可以将 log 方法和 getsettings 方法设为静态。在我看来,这是一个 hack,您应该尝试顶部列出的选项 1 或 2。这样做的原因是因为在我看来将某些东西设为静态不应该改变代码的行为,而应该用于内存优化(即在这方面的一些类似阅读中参见单例反模式)。
对于您的代码,我建议您从 appsettingsprovider 中删除日志记录,而是使用它的记录器启动来添加围绕 class 使用的日志语句。或者,您可以探索:
- 工厂模式尝试包装您的 classes 中的任何一个的创建。
- 最后,在 C# 中,您可以使用 lambda / 函数 属性 将实例传递给 class,这样引用就不会递归地创建新实例。
这里有 2 个选项:
- 使用属性注入
- 使用工厂(这可能会与您的工厂产生强烈的依赖关系)
这是一个使用 属性 注入的例子:
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";
}
}
Autofac 建议如下: Autofac reference
Autofac 提供了另一个选项来克服循环依赖,如果 A 需要 B 并且 B 需要 A,并且 B 在其构造函数中不使用 A,B 可以使用 A以一种称为动态实例化的方式参见
https://docs.autofac.org/en/latest/resolve/relationships.html#dynamic-instantiation-func-b
这样,B 将在其构造函数中使用 A 的工厂方法 Func<A>
(并在构造函数之后调用它,可以采用一些惰性方式)来获取 A 的实例。
例如在上面的问题中更改 Logger 以克服循环依赖问题:
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);
}
}
}
(注意:当我在 Do() 中添加 appSettingsProvider.GetSettings(); 时,有问题的代码进入
Process is terminating due to WhosebugException.
因为 GetSettings 调用 Log 而 Log 调用 GetSettings 但这是另一回事)
另一种解决方法是在相关服务的构造函数中使用 Lazy<ILogger>
。
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;
}
}