为什么一个事件的圈复杂度是2?
Why is the cyclomatic complexity of an event 2?
我正在仔细检查应用程序中的圈复杂度,目的是测试任何圈复杂度大于 1 的东西。
我看到的是一个非常简单的事件的 add 访问器的圈复杂度为 2。这是为什么?是不是add方法先检查回调方法是否已经注册?
我创建了一个非常简单的计算器应用程序来复制此行为。我有一个名为 CalculateComplete 的事件,它会在 Calculate() 方法完成时触发。
如果有一些class与一些事件,例如
class SomeClass
{
public event Action<int> SomeEvent;
}
那么事件添加方法生成的IL代码为:
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
请注意,在该方法的末尾有一个 Interlocked.CompareExchange()
调用,然后是 "branch if not equal"。所以是的,有一个分支,因此圈复杂度为 2。
你可能会问为什么会这样?原因是委托是不可变的。当您向委托添加方法时,您不会修改原始委托,但实际上是从现有委托和提供的方法创建一个组合委托,并将其重新分配给事件。参见 Delegate.Combine。
除此之外,新旧委托之间的交换必须是线程安全的,这就是 Interlocked.CompareExchange 的原因。如果交换失败,请重试。
为了提供帮助,我已将 IL 翻译成 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;
}
我正在仔细检查应用程序中的圈复杂度,目的是测试任何圈复杂度大于 1 的东西。
我看到的是一个非常简单的事件的 add 访问器的圈复杂度为 2。这是为什么?是不是add方法先检查回调方法是否已经注册?
我创建了一个非常简单的计算器应用程序来复制此行为。我有一个名为 CalculateComplete 的事件,它会在 Calculate() 方法完成时触发。
如果有一些class与一些事件,例如
class SomeClass
{
public event Action<int> SomeEvent;
}
那么事件添加方法生成的IL代码为:
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
请注意,在该方法的末尾有一个 Interlocked.CompareExchange()
调用,然后是 "branch if not equal"。所以是的,有一个分支,因此圈复杂度为 2。
你可能会问为什么会这样?原因是委托是不可变的。当您向委托添加方法时,您不会修改原始委托,但实际上是从现有委托和提供的方法创建一个组合委托,并将其重新分配给事件。参见 Delegate.Combine。
除此之外,新旧委托之间的交换必须是线程安全的,这就是 Interlocked.CompareExchange 的原因。如果交换失败,请重试。
为了提供帮助,我已将 IL 翻译成 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;
}