是的,NDepend 可以有效地查找循环引用让我解释一下如何,因为它可能比您想象的要容易(免责声明:我是 NDepend 的开发人员之一)。到目前为止,您可以找到开箱即用的命名空间依赖循环,但是,正如我在下面解释的那样,很容易找到命名空间中的类型或类型的方法之间的循环。
有一个默认的 C# LINQ 代码规则列出了命名空间依赖循环。然后可以将这样的循环导出到依赖图或依赖矩阵。这是 2012 年 6 月在 Roslyn 代码库 CTP 上执行的规则的屏幕截图(请注意,它只需要 16 毫秒即可运行)。它发现了 11 个不同的循环,如屏幕截图所示,您可以深入了解每个循环并将一个循环导出到图表中:
这是 7 个命名空间循环的依赖关系图。请注意,它看起来比经典的 O 形环循环更复杂。这里的关键是,您可以从这些命名空间中的任何一个访问所有其他命名空间。这是循环(纠缠)的广义概念。
列出命名空间依赖周期的默认 C# LINQ 代码规则的代码乍一看可能令人生畏。但是 C# 开发人员应该在几分钟内理解它,然后可以轻松地对其进行调整以找到任何类型的依赖循环。
例如,要查找相同类型的方法循环(而不是相同组装循环的命名空间),几乎就像用方法替换所有命名空间单词,用类型替换组装单词一样简单。
// <Name>Avoid methods of a type to be in cycles</Name>
warnif count > 0
from t in Application.Types
.Where(t => t.ContainsMethodDependencyCycle != null &&
t.ContainsMethodDependencyCycle.Value)
// Optimization: restreint methods set
// A method involved in a cycle necessarily have a null Level.
let methodsSuspect = t.Methods.Where(m => m.Level == null)
// hashset is used to avoid iterating again on methods already caught in a cycle.
let hashset = new HashSet<IMethod>()
from suspect in methodsSuspect
// By commenting this line, the query matches all methods involved in a cycle.
where !hashset.Contains(suspect)
// Define 2 code metrics
// - Methods depth of is using indirectly the suspect method.
// - Methods depth of is used by the suspect method indirectly.
// Note: for direct usage the depth is equal to 1.
let methodsUserDepth = methodsSuspect.DepthOfIsUsing(suspect)
let methodsUsedDepth = methodsSuspect.DepthOfIsUsedBy(suspect)
// Select methods that are both using and used by methodSuspect
let usersAndUsed = from n in methodsSuspect where
methodsUserDepth[n] > 0 &&
methodsUsedDepth[n] > 0
select n
where usersAndUsed.Count() > 0
// Here we've found method(s) both using and used by the suspect method.
// A cycle involving the suspect method is found!
// 8Feb2021: invoke extension method Append() explicitly to avoid ambiguous compiler error
// because of the new .NET BCL extension methods Append() method
let cycle = NDepend.Helpers.ExtensionMethodsEnumerable.Append(usersAndUsed,suspect)
// Fill hashset with methods in the cycle.
// .ToArray() is needed to force the iterating process.
let unused1 = (from n in cycle let unused2 = hashset.Add(n) select n).ToArray()
select new { suspect, cycle }
...这是该规则的结果的样子(仍然可以将方法循环导出到依赖图或矩阵)。请注意,由于方法和类型的数量远高于命名空间和程序集的数量,因此在 Roslyn 这样的大型代码库上运行此查询大约需要 10 秒(而不是命名空间周期的 16 毫秒),因此您可能需要调整CQLinq 查询执行超时(默认为 2 秒)。
完整地说,我注意到循环大部分时间是由一些双向引用引起的(即 A 使用 B,B 使用 A)。因此,删除双向引用是打破循环的第一件事。这就是为什么我们提供了默认的 CQLinq 规则避免命名空间相互依赖,它仍然可以适应类型或方法循环。