Visual Studio Code Metrics Cyclomatic Complexity & LINQ

Check about Cyclomatic Complexity.

How does it work in Visual Studio?

* CC = Cyclomatic Complexity

CC = 1
static int MyComplexMethod(int number)
{
    return number;
}

In the previous code fragment CC = 1 = one execution path.

Next, having two possible execution paths (inline conditionals “?” affect control flow) .

CC = 2
static int MyComplexMethod(int number)
{
    bool condition = number > 10;
    return condition ? 1 : -1;
}

Next, short circuiting boolean expressions generate additional execution paths:

CC = 3
static int MyComplexMethod(int number)
{
    bool condition = number > 10 && number < 20;
    return condition ? 1 : -1;
}

The previous is a cool example of how execution branches could be very subtle. Short circuiting evaluation avoids executing code fragments that are not necessary (the first term in the “And” expression is False, thus not second term evaluation is not required).

A Bug may arise when executing those Terms not regularly evaluated due to Short Circuiting:

Bug in Short Circ. Branch
static int MyComplexMethod(int number)
{
    bool condition = number > 10 && MethodWithABug() < 20;
    return condition ? 1 : -1;
}
CC = 4 (more complex boolean)
static int MyComplexMethod(int number)
{
    bool condition = number > 10 && number < 20 && DateTime.Now.Year == 2012;
    return condition ? 1 : -1;
}

Keep adding branches and CC will keep growing:

CC = 5
static int MyComplexMethod(int number)
{
    bool condition = number > 10 && number < 20 && DateTime.Now.Year == 2012;
    if (condition)
    {
        CallSomeOtherMethod();
    }
    
    return condition ? 1 : -1;
}

Loops add cycling execution paths, thus they contribute to CC as well.

CC = 6 (foreach loop)
static int MyComplexMethod(int number)
{
    bool condition = number > 10 && number < 20 && DateTime.Now.Year == 2012;
    if (condition)
    {
        CallSomeOtherMethod();
    }

    int[] numbers = GetNumbers();
    foreach (var item in numbers)
    {
        DoSomethingWithNumber(item);
    }
    return condition ? 1 : -1;
}

Loop with a filtering “if”.

CC = 7
static int MyComplexMethod(int number)
{
    bool condition = number > 10 && number < 20 && DateTime.Now.Year == 2012;
    if (condition)
    {
        CallSomeOtherMethod();
    }

    int[] numbers = GetNumbers();
    foreach (var item in numbers)
    {
        if (item % 2 == 0)
            DoSomethingWithEvenNumber(item);
    }
    return condition ? 1 : -1;
}

Compare the previous to using LINQ.

CC = 9 (LINQ)
static int MyComplexMethod(int number)
{
    bool condition = number > 10 && number < 20 && DateTime.Now.Year == 2012;
    if (condition)
    {
        CallSomeOtherMethod();
    }

    int[] numbers = GetNumbers();
    
    foreach (var item in numbers.Where(i=> i % 2 == 0))
    {
            DoSomethingWithEvenNumber(item);
    }
    return condition ? 1 : -1;
}

Could you guess Why CC is grater in this case? Hint: check the IL!

When using LINQ, generic IEnumerator<T> is used instead. Generic enumerators implement IDisposable interface:

IEnumerator<T>
interface IEnumerator<out T> : IDisposable, IEnumerator

Thus, generated IL is adding a Try/Finally block in order to Dispose the enumerator. In the Finally block, the compiler generates code to compare the enumerator against null and disposing it.

IEnumerator<T> Dispose Pattern
finally
{
    if (enumerator != null)
        ((IDisposable)enumerator).Dispose();
}

This adds 1 point to the metric (“if” control flow).

The other extra point is due to the cache mechanism used by C# compiler when generating anonymous delegates. If we check the IL we’ll see an extra conditional branch checking for cached delegates.

image

We could assume the generated code is relative Bug free. The latter is not entirely true, imagine if we would have implemented a custom Enumerator with a custom Dispose. Now imagine you have a Bug in the Dispose pattern :( .

No Comments