Why is the cyclomatic complexity of event 2?
I am looking at cyclomatic complexity in an application for the purpose of testing something that has a cyclomatic complexity greater than 1.
What I see is that a very simple access adding element has a cyclical complexity of 2. Why is that? Is it because the add method first checks if the callback method is registered?
I created a very simple calculator app to replicate this behavior. I have a CalculateComplete event that fires when the Calculate () method is complete.
source to share
If there is some class with some event like
class SomeClass
{
public event Action<int> SomeEvent;
}
Then the IL code generated for the event add method:
SomeClass.add_SomeEvent:
IL_0000: ldarg.0
IL_0001: ldfld UserQuery+SomeClass.SomeEvent
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: stloc.1
IL_0009: ldloc.1
IL_000A: ldarg.1
IL_000B: call System.Delegate.Combine
IL_0010: castclass System.Action<System.Int32>
IL_0015: stloc.2
IL_0016: ldarg.0
IL_0017: ldflda UserQuery+SomeClass.SomeEvent
IL_001C: ldloc.2
IL_001D: ldloc.1
IL_001E: call System.Threading.Interlocked.CompareExchange<Action`1>
IL_0023: stloc.0
IL_0024: ldloc.0
IL_0025: ldloc.1
IL_0026: bne.un.s IL_0007
IL_0028: ret
Note that there is a call at the end of the method Interlocked.CompareExchange()
, followed by "branch if not equal". So yes, there is a branch, so the cyclical complexity is 2.
Why is this so, you may ask? The reason is that delegates are immutable. When you add a method to a delegate, you are not modifying the original delegate, but you are actually creating a merged delegate from the existing and provided method and reassigning it to the event. See Delegate.Combine .
Also, the exchange between the new and old delegates must be thread safe, so Interlocked.CompareExchange . If the change is unsuccessful, try again.
To help, I translated IL to C #:
public void add_SomeEvent(Action<int> arg1)
{
var local0 = this.SomeEvent;
IL_0007:
var local1 = local0;
var local2 = (Action<int>)Delegate.Combine(local1, arg1);
local0 = Interlocked.CompareExchange(ref this.SomeEvent, local2, local1)
if (local0 != local1) goto IL_0007;
}
source to share