I suggest you ...

Cyclomatic Complexity in Code Metrics is calculated based on IL

Calculation of Cyclomatic Complexity (at least and possibly other metrics) is based on compiler-generated IL. To calculate code metrics at the IL level just seems wrong. Code metrics are meant to assist developers in creating better code – not worrying about how it is compiled – which is nothing more than an implementation detail we shouldn’t be concerned with. I would argue that this is a bug in Roslyn/VS by calculating at the IL level.

I know that this metric is calculated differently by NDepend and ReSharper which is very confusing when this is supposed to be an industry-wide standard calculation.

So the way Roslyn and previous MS compilers measure CC is via a certain set of OpCodes (transfer of control) in IL generated by the compiler. The compiler to generate source into IL (rsc.exe) doesn’t do much optimization – that is owned primarily by the JIT (which is one reason to never compare perf metrics using IL comparisons). There is no concept of lambda expressions at the IL level. The lambdas have all been converted into JIT’ed classes. And while, it is true that delegates/lambdas are cached, it is only done if there is no closure over them – another performance reason to avoid lambdas in “hot” methods”. So, every time a lambda exists in code, it causes a branch of control flow. The IL generated checks if the class exists, and if not, creates the class and return. This means that the CC for even the simplest of methods with no obvious control-flow and also contains a lambda has a CC of 2*n + 1 where n is the number of lambdas.

So even this simple code has a CC of 3:

static void Main(string[] args)
{
List<int> testList = new List<int> { 1, 2, 3 };
testList.Select(i => i);
}

Looking at the IL generated for this code, it becomes clear:
IL_0000: newobj System.Collections.Generic.List<System.Int32>..ctor
IL_0005: stloc.1 // <>g__initLocal0
IL_0006: ldloc.1 // <>g__initLocal0
IL_0007: ldc.i4.1
IL_0008: callvirt System.Collections.Generic.List<System.Int32>.Add
IL_000D: ldloc.1 // <>g__initLocal0
IL_000E: ldc.i4.2
IL_000F: callvirt System.Collections.Generic.List<System.Int32>.Add
IL_0014: ldloc.1 // <>g__initLocal0
IL_0015: ldc.i4.3
IL_0016: callvirt System.Collections.Generic.List<System.Int32>.Add
IL_001B: ldloc.1 // <>g__initLocal0
IL_001C: stloc.0 // testList
IL_001D: ldloc.0 // testList
IL_001E: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate2
IL_0023: brtrue.s IL_0036 //stands for branch if true
IL_0025: ldnull
IL_0026: ldftn b__1
IL_002C: newobj System.Func<System.Int32,System.Int32>..ctor
IL_0031: stsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate2
IL_0036: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate2
IL_003B: call System.Linq.Enumerable.Select
IL_0040: pop
IL_0041: ret //return control flow

b__1:
IL_0000: ldarg.0
IL_0001: ret //return control flow

33 votes
Vote
Sign in
(thinking…)
Sign in with: facebook google
Signed in as (Sign out)
You have left! (?) (thinking…)
Dave Black shared this idea  ·   ·  Flag idea as inappropriate…  ·  Admin →

0 comments

Sign in
(thinking…)
Sign in with: facebook google
Signed in as (Sign out)
Submitting...

Feedback and Knowledge Base