什么是循环依赖,如何解决?

21

场景


我有一个解决方案,其中包含(超过)两个项目。

第一个项目引用了第二个项目。第二个项目没有引用第一个项目。

在第一个项目中,我定义了一个可继承的类类型,并希望第二个项目的某些类从它继承。

问题


显然,如果我想在第二个项目中继承在第一个项目中定义的类型,我需要添加对第一个项目的项目引用才能看到该类型并继续进行操作。

问题是,当我尝试添加项目引用时,会出现以下错误消息:

Circular dependency error

问题


请有人用其他简单的词语(可能还有代码示例,如果在错误中涉及到代码)来解释什么是“循环依赖”?最重要的是:我可以做什么来解决它? (在回答之前,请阅读我的研究的最后一句话)。

研究


这是我第一次听说“循环依赖”一词。我阅读了MSDN的这篇文章,但我什么也没看懂。

无论如何,我看到了许多关于循环依赖的问题,比如这个。从那个问题中我所看到的来看,循环依赖意味着两个项目不能同时相互引用,只有其中一个项目可以引用另一个项目;如果两个项目相互引用,则会发生死循环和编译错误。

因此,在您的情况下,由于第一个项目已引用第二个项目,而且您想要在第二个项目中使用第一个项目中定义的类型,所以您不能再向第二个项目添加对第一个项目的引用。为了解决这个问题,您应该将公共的类库(例如DLL文件)从两个项目中提取出来,并将其包含为它们都可以引用的独立项目或程序集。这样,两个项目都可以引用并使用该公共类型,而不会发生循环依赖问题。

而且所有回答该问题的人都说了类似于“重新设计是解决方案”或者“循环依赖不是好的实践”,然而,在我的情况下重新设计意味着在两个项目中定义相同的类型,我认为这也不是一个好的实践。当然,建立一个额外的程序集/项目仅仅为了存储单个类型并将该程序集用于两个项目中是我认为最坏的想法。


定义一个IInterface(或基类),并将其放在单独的DLL中,两个项目都可以引用它。 - relascope
4个回答

39

什么是依赖关系?

为了理解循环依赖关系,最好先了解什么是依赖关系及其对编译器的意义。

假设您有一个项目,在一个类中,您定义了以下内容:

Public Class MyClass
    'Some code here
    Private MyString As String
    'Some code there
End Class

编译您的项目时,编译器遇到了名为System的DLL文件中定义的String类。然后它将链接该DLL文件到您的项目中,因此在运行时,在字符串上定义或执行操作时,将加载System.dll以执行这些操作。

现在,假设您在类中有以下定义:

'Some code here
Private MyObjet as CustomClass1
'Some code there

假设您的另一个项目 Project2.DLL 中定义了 CustomClass1

Public Class CustomClass1
    'Your custom class code
End Class

因此,在编译您的第一个项目时,编译器会遇到CustomClass1的定义,它知道它位于Project2.dll中,因此将在编译第一个项目之前编译Project2,以便能够将该引用添加到您的第一个项目中。这就是依赖性的概念,呈层次结构,必须有一个起点。即使是String类也依赖于其他类,并且最终它们都依赖于字节或位来完成工作,因为这是计算机唯一可以做的事情,操作10

所以循环部分是怎么回事?

如果在Project2中有一个引用(字段定义或类似的东西)链接到您的第一个项目,会发生什么呢?

  • 编译器读取您的第一个项目,然后遇到CustomClass1
  • 然后尝试编译Project2,因为CustomClass1在那里被定义
  • 然后运行到在您的第一个项目中定义的类
  • 编译器尝试编译您的第一个项目以将其链接到第二个项目
  • 然后运行到CustomClass1
  • 然后尝试编译Project2
  • 我猜你懂的......

因此,在某个时刻,编译器会显示一个错误,表示无法进行编译,因为它不理解您想要做什么......

是的,计算机就是那么傻。

如何解决?

解决这种问题有时很困难,但基本想法是建立一个分层结构,将基类(那些不需要依赖项的类)放在一起,然后在它们之上构建。把所有相互依赖的类放在一起,它们形成了您的应用程序中尝试完成的某个操作的层。


38
循环依赖是指项目A依赖于项目B中的某些内容,而项目B又依赖于项目A中的某些内容。这意味着要编译项目A,必须先编译项目B,但由于B需要先编译A,因此无法这样做。这就是循环依赖带来的问题。
如果在已构建的项目中引入循环依赖,可能很难发现,因为标准的构建选项不会删除现有的目标文件,从而使您能够首先构建A(或B)。只有在尝试在从未构建过该解决方案的其他计算机上进行构建或进行清理和构建时才会发现它。
在这种情况下,您需要创建第三个项目"C",其中包含A和B都依赖的类,以便它们不再相互依赖。您可能只需拆分这些类,使得依赖关系可以通过这种方式排序,而无需创建第三个项目。

感谢您的回答。我看到了其他解释“循环依赖”的答案,大体上使用了类似的词语,但是它并没有解释原因,我的意思是为什么试图使项目A依赖于项目B,反之亦然会有问题?为什么这不能被编译?这个问题的确切原因是什么? - ElektroStudios
2
@ElektroStudios 如果你的问题是“为什么我不能有循环依赖关系?”而不是“什么是循环依赖关系,我该如何解决它?”,那么你可能需要编辑你的问题来澄清。 - Blackwood
3
@ElektroStudios,因为正如名称所示,循环依赖是循环的,因此编译器无法确定哪个项目应该先进行。从哲学上讲,这就像问计算机谁先出现,鸡蛋还是鸡? - Martin Verjans
@Blackwood “什么是循环依赖” 暗示了必须解释它导致的与编译器相关的错误/错误原因,该答案没有给出这样的解释,但我既没有问一个不同的问题也不是无关的,你真的认为值得发布另一个额外的线程来解释循环依赖吗?那么我们可以引入一个新的 QA 模式...在专题中。常见的,很多人像你一样可以找到任何小事情讨论的理由。最糟糕的是,还有其他人认为这是一个好主意来点赞。感谢 SuperPeanut 的解释。 - ElektroStudios

2

我知道的最简单的修复CD的方法是创建一个接口项目,并让涉及到CD的项目引用该接口项目,而不是相互引用。 虽然有点混乱,但它确实有效。


-4

不要使用项目作为引用,而是使用该项目生成的 DLL 进行引用。如果在解决方案的每个模块中都这样做,您就可以解决它。 如果您有两个名为“main”和“sub”的项目, 那么在主项目中添加子项目的 DLL 作为引用文件,同时在子项目中添加主项目的 DLL 作为引用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接