目前,如果我想在解决方案中检查循环引用,请选择Architecture - Generate Dependency Graph - For Solution
。
然后,从打开的新标签中选择Layout - Analyzers - Circular References Analyzer
。
最后,如果我从单个程序集向下钻取并且有循环引用,我可以看到它们在图表上以红色突出显示,它们也在错误列表中显示为警告。
由于我打算在相同类的方法之间发现循环引用,因此在适度大的代码库上这非常容易出错且耗时。
我想知道是否有办法在不必扩展节点的情况下立即获取所有警告,或者可能打开父节点的突出显示,以便我可以仅向下钻取肯定包含循环引用的汇编
NDepend应该能够提供帮助,但我更喜欢让事情变得尽可能简单,所以我总是对采用其他工具持谨慎态度。
答案 0 :(得分:6)
是NDepend可以高效地查找循环引用让我解释一下因为它可能比你想象的更容易(免责声明:我是NDepend上的开发人员之一)。到目前为止,你可以找到 namespace 依赖循环开箱即用的,但正如我在下面解释的那样,很容易找到之间的循环类型名称中的类型,或类型的方法。
有default C# LINQ code rule列出名称空间依赖项周期。然后可以将这种循环导出到依赖图或依赖矩阵。以下是2012年6月Roslyn代码库CTP上执行的规则的屏幕截图(注意它只需要16毫秒才能运行)。它找到了11个不同的周期,如屏幕截图所示,您可以深入查看每个周期并将周期导出到图表中:
这是7个名称空间循环的依赖关系图。请注意,它看起来比传统的O形环循环更复杂。这里的关键是,从这些命名空间中的任何一个,您都可以访问所有其他命名空间。这是循环(纠缠)的概括概念。
列出名称空间依赖性周期的default C# LINQ code rule的代码乍一看可能看起来令人生畏。但是C#开发人员应该在几分钟内理解它,然后可以轻松地调整它以找到任何类型的依赖循环。
例如,要查找相同类型循环的方法(而不是相同汇编周期的名称空间),它几乎就像替换所有名称空间一样简单 word by method , assembly word by type 。
// <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!
let cycle = usersAndUsed.Append(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秒钟(而不是16ms用于命名空间循环),因此您可能需要调整CQLinq查询执行超时(默认为2秒)。
为了完成,我注意到循环是大部分时间由一些双向引用引起的(即A使用B,B使用A)。因此,删除双向引用是打破循环的第一步。这就是为什么我们提供了默认的CQLinq规则Avoid namespaces mutually dependent,它仍然可以适应类型或方法周期。