はい、NDepend は循環参照を効率的に見つけることができます。あなたが考えるよりも簡単かもしれないので、その方法を説明させてください (免責事項: 私は NDepend の開発者の 1 人です)。これまでのところ、すぐに使える名前空間の依存関係サイクルを見つけることができますが、以下で説明しているように、名前空間内の型または型のメソッド間の循環も簡単に見つけることができます。
名前空間の依存関係のサイクルを一覧表示する既定の 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 のような大規模なコード ベースで実行すると (名前空間サイクルの 16 ミリ秒ではなく) 約 10 秒かかることに注意してください。したがって、調整が必要になる場合があります。 CQLinq クエリ実行タイムアウト (デフォルトでは 2 秒)。
完全にするために、私が気付いたのは、ほとんどの場合、いくつかの双方向の参照によってサイクルが引き起こされていることです (つまり、A は B を使用し、B は A を使用しています)。したがって、双方向参照を削除することは、サイクルを壊すために最初に行うことです。これが、デフォルトの CQLinq ルールの名前空間相互依存を回避する を提供した理由です。これは、型またはメソッドのサイクルに引き続き適応できます。