Почему цикломатическая сложность события равна 2?

Я тщательно изучаю цикломатическую сложность приложения с целью тестирования всего, что имеет цикломатическую сложность больше 1.

Что я вижу, так это то, что очень простое средство доступа добавления события имеет цикломатическую сложность 2. Почему это так? Это потому, что метод добавления сначала проверяет, зарегистрирован ли уже метод обратного вызова?

Я создал очень простое приложение-калькулятор, чтобы воспроизвести это поведение. У меня есть событие CalculateComplete, которое срабатывает, когда метод Calculate() завершается.

введите здесь описание изображения


person Trevor    schedule 26.03.2017    source источник


Ответы (1)


Если есть какой-то класс с каким-то событием, например

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(), за которым следует «ветвь, если не равно». Так что да, ветвь есть, поэтому цикломатическая сложность равна 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;
}
person vyrp    schedule 26.03.2017
comment
чтобы действительно перевести его на C #, это будет цикл do...while. - person Cory Nelson; 26.03.2017
comment
Да, это будет цикл do..while. Но метки и goto также допустимы в C#, и я хотел оставить их как можно более похожими на IL. - person vyrp; 26.03.2017