You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity C#中组合构造器注入父类:解决IoC循环依赖问题

解决Unity IoC中组合类与子组件的循环依赖问题

这确实是IoC容器里典型的循环依赖场景——Context依赖ISomething,而Something又反过来依赖IContext,导致容器解析时陷入无限递归,最终抛出栈溢出异常。下面分享几种实用的最佳实践,你可以根据自己的代码结构和设计偏好选择:


方案1:用属性注入替代构造函数注入

Unity原生支持属性注入,通过延迟依赖的注入时机来打破循环。我们可以把Something中对IContext的构造依赖改成属性依赖:

public class Something : ISomething {
    // 用[Dependency]标记需要容器注入的属性
    [Dependency]
    public IContext Wrapper { get; set; }

    // 提供无参构造函数,让容器先创建实例
    public Something() { }

    public void Do() {
        Console.WriteLine(Wrapper.GetCommonData());
    }
}

注册逻辑不需要改动,Unity会先创建Context实例(此时Something已被实例化但还没注入IContext),等Context构造完成后,再把它注入到Something的Wrapper属性中。

注意:属性注入的缺点是无法保证属性在使用前一定被注入,所以要避免在Something的构造函数中调用依赖Wrapper的逻辑,确保在容器完成解析后再使用该类的方法。


方案2:通过工厂模式延迟子组件创建

这种方式能保持构造函数注入的不可变性(依赖只读),同时把Something的创建时机推迟到Context构造完成之后:

首先定义一个工厂接口和实现:

public interface ISomethingFactory {
    ISomething Create(IContext context);
}

public class SomethingFactory : ISomethingFactory {
    public ISomething Create(IContext context) {
        return new Something(context);
    }
}

然后修改Context的构造逻辑,通过工厂创建Something:

public class Context : IContext {
    public ISomething SomethingProcessor { get; }
    public IAnother AnotherProcessor { get; }

    // 依赖工厂而非直接依赖ISomething
    public Context(ISomethingFactory somethingFactory, IAnother another) {
        this.AnotherProcessor = another;
        // 此时Context已构造完成,可以安全传递this给Something
        this.SomethingProcessor = somethingFactory.Create(this);
    }

    public string GetCommonData() {
        return this.AnotherProcessor.GetMyData();
    }
}

注册时加上工厂的映射:

container.RegisterType<ISomethingFactory, SomethingFactory>();
container.RegisterType<IAnother, Another>();
container.RegisterType<IContext, Context>();

var cxt = container.Resolve<IContext>(); // 不再有循环依赖

方案3:重构依赖关系(最推荐的长期方案)

循环依赖往往暗示着设计可以优化——Something其实不需要依赖整个IContext,它只需要GetCommonData这个功能。我们可以把这个功能抽象成独立的接口,拆解不必要的依赖:

  1. 定义功能接口:
public interface ICommonDataProvider {
    string GetCommonData();
}
  1. 实现这个接口(可以单独写类,也让Another直接实现):
// 这里选择让Another直接实现,减少额外类
public class Another : IAnother, ICommonDataProvider {
    public string GetMyData() {
        return "Foo";
    }

    public string GetCommonData() {
        return GetMyData();
    }
}
  1. 修改Something依赖具体的功能接口:
public class Something : ISomething {
    private readonly ICommonDataProvider _dataProvider;

    // 现在依赖的是单一功能接口,而非整个Context
    public Something(ICommonDataProvider dataProvider) {
        _dataProvider = dataProvider;
    }

    public void Do() {
        Console.WriteLine(_dataProvider.GetCommonData());
    }
}
  1. Context的依赖也可以简化:
public class Context : IContext {
    public ISomething SomethingProcessor { get; }
    public IAnother AnotherProcessor { get; }

    public Context(ISomething something, IAnother another) {
        this.SomethingProcessor = something;
        this.AnotherProcessor = another;
    }

    // 如果Context仍需要对外提供GetCommonData,可以委托给Another
    public string GetCommonData() {
        return this.AnotherProcessor.GetMyData();
    }
}

这样整个依赖链就变成了Context → ISomething、IAnother,Something → ICommonDataProvider,完全消除了循环依赖,同时也符合单一职责原则,让每个类的依赖更清晰。


内容的提问来源于stack exchange,提问作者SirKumbskull

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:16:13