如何解决Autofac循环依赖?

时间:2017-05-02 02:25:24

标签: c# 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";
    }
 }

任何有关解决此问题的建议都会有所帮助。

4 个答案:

答案 0 :(得分:2)

这里有2个选项:

  1. 使用属性注入
  2. 使用工厂(可能会与您的工厂建立强烈的依赖关系)
  3. 以下是使用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";
            }
        }
    

    以下是Autofac建议: Autofac reference

答案 1 :(得分:2)

Autofac提供了另一种克服循环依赖的选项,如果A需要B并且B需要A,并且B在其构造函数中不使用A ,则B可以通过称为< strong>动态实例化,参见

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();时,有问题的代码进入

  

由于StackOverflowException,进程正在终止。

因为GetSettings调用Log,而Log调用GetSettings,但这是另一回事了

答案 2 :(得分:2)

另一种解决方法是在依赖服务的构造函数中使用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;
    }
}

答案 3 :(得分:1)

您需要在实施中使它们互斥。例如:

  1. 您可以从提取设置中删除日志记录
  2. 您可以从记录器中删除设置检查
  3. 此处的循环引用表明您可能不会以易于维护的方式执行此操作(即,您将具有更高的耦合)。

    如果您希望保持代码不变并且无法正常工作,那么您可以将日志方法和getsettings方法设置为静态。在我看来这是一个黑客,你应该尝试在顶部列出的选项1或2。这样做的原因是因为在我看来,制作一些静态的东西不应该改变代码的行为,而应该用于内存优化(即在这个区域中看一些类似读取的单例反模式)。

    对于您的代码,我建议您从appsettingsprovider中删除日志记录,而是使用logger的初始化来添加围绕该类使用的日志语句。或者你可以探索:

    1. 工厂模式,尝试包装任一类的创建。
    2. 最后在C#中,您可以使用lambda / function属性将实例传递给类,以便引用不会以递归方式创建新实例。