controlflow / resharper-heapview

ReSharper Heap Allocations Viewer plugin
MIT License
303 stars 28 forks source link

Trying to undertand inspection: "Possible object allocation: event subscription may allocate new delegate instance" #118

Closed Houtamelo closed 11 months ago

Houtamelo commented 1 year ago

Heap Allocations Viewer shows the following inspection: image

The event CharacterSetup is declared as follows: public event Action<CharacterStateMachine> CharacterSetup;

The delegate _onCharacterSetup is declared as follows: private readonly Action<CharacterStateMachine> _onCharacterSetup;

I understand the inspection only says that it "may" allocate a new delegate instance, my question is: When would that happen? When and why an event handler would create a new instance for a delegate that is already allocated?

Asking here because I couldn't find any information on this specific subject.

controlflow commented 11 months ago

Hi! The reason for allocation is the delegate creation to store the actual event subscribers list (events may have arbitrary number of subscriptions). Under public event Action<CharacterStateMachine> CharacterSetup; event declaration there is a hidden field of Action<CharacterStateMachine> type. Every time you subscribe to this event there is Delegate.Combine invocation happening to combine the current Action<CharacterStateMachine> instance stored in the field with the Action<CharacterStateMachine> instance you've provided when subscribing to the event. Delegates in .NET are immutable so the only way to get the delegate instance consisting all of the invocables combined is to allocate the new delegate instance, this is exactly what Delegate.Combine performs. The inspection message contains "may" word because Delegate.Combine do not allocates new instances if one of it's the arguments is null - in this case it is safe to just return another arguments instead (thx to immutability). So if your event contains zero subscribers (null is stored in Action<CharacterStateMachine> hidden field) or you're somehow subscribing with null as a Action<CharacterStateMachine> reference (this is safe in C# and will not trigger NRE) - there will be no actual allocation on both event subscription or unsubscription.

I'll try to clarify HeapView's message in this case.

Houtamelo commented 11 months ago

Hi! The reason for allocation is the delegate creation to store the actual event subscribers list (events may have arbitrary number of subscriptions). Under public event Action<CharacterStateMachine> CharacterSetup; event declaration there is a hidden field of Action<CharacterStateMachine> type. Every time you subscribe to this event there is Delegate.Combine invocation happening to combine the current Action<CharacterStateMachine> instance stored in the field with the Action<CharacterStateMachine> instance you've provided when subscribing to the event. Delegates in .NET are immutable so the only way to get the delegate instance consisting all of the invocables combined is to allocate the new delegate instance, this is exactly what Delegate.Combine performs. The inspection message contains "may" word because Delegate.Combine do not allocates new instances if one of it's the arguments is null - in this case it is safe to just return another arguments instead (thx to immutability). So if your event contains zero subscribers (null is stored in Action<CharacterStateMachine> hidden field) or you're somehow subscribing with null as a Action<CharacterStateMachine> reference (this is safe in C# and will not trigger NRE) - there will be no actual allocation on both event subscription or unsubscription.

I'll try to clarify HeapView's message in this case.

Thanks for the explanation!