fix: added dynamic index updates during event invocation so there are no missing/duplicate invocations

This also makes the events very not ideal for multithreaded applications at the moment.
This commit is contained in:
2026-01-31 00:49:00 +03:00
parent 4e9fda3d7c
commit 913af2a4a4

View File

@@ -58,6 +58,9 @@ public class Event
public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared; public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared;
private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private readonly List<ListenerData> listeners = null!; private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!; private readonly List<ListenerData> onceListeners = null!;
@@ -74,6 +77,9 @@ public class Event
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentCallIndex)
currentCallIndex++;
listeners.Insert(insertIndex, listenerData); listeners.Insert(insertIndex, listenerData);
} }
@@ -90,6 +96,9 @@ public class Event
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentOnceCallIndex)
currentOnceCallIndex++;
onceListeners.Insert(insertIndex, listenerData); onceListeners.Insert(insertIndex, listenerData);
} }
@@ -103,6 +112,8 @@ public class Event
if (listeners[i].Callback == listener) if (listeners[i].Callback == listener)
{ {
listeners.RemoveAt(i); listeners.RemoveAt(i);
if (i < currentCallIndex)
currentCallIndex--;
return; return;
} }
} }
@@ -117,6 +128,8 @@ public class Event
if (onceListeners[i].Callback == listener) if (onceListeners[i].Callback == listener)
{ {
onceListeners.RemoveAt(i); onceListeners.RemoveAt(i);
if (i < currentOnceCallIndex)
currentOnceCallIndex--;
return; return;
} }
} }
@@ -131,23 +144,23 @@ public class Event
/// </summary> /// </summary>
public void Invoke() public void Invoke()
{ {
for (int i = listeners.Count - 1; i >= 0; i--) for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--)
try { listeners[i].Callback.Invoke(); } try { listeners[currentCallIndex].Callback.Invoke(); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}()"; string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}()";
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(listeners[currentCallIndex].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
} }
for (int i = onceListeners.Count - 1; i >= 0; i--) for (currentOnceCallIndex = onceListeners.Count - 1; currentOnceCallIndex >= 0; currentOnceCallIndex--)
{ {
try { onceListeners[i].Callback.Invoke(); } try { onceListeners[currentOnceCallIndex].Callback.Invoke(); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}()"; string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}()";
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
} }
onceListeners.RemoveAt(i); onceListeners.RemoveAt(currentOnceCallIndex);
} }
} }
@@ -216,6 +229,9 @@ public class Event<TSender> where TSender : class
public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared; public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared;
private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private readonly List<ListenerData> listeners = null!; private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!; private readonly List<ListenerData> onceListeners = null!;
@@ -232,6 +248,9 @@ public class Event<TSender> where TSender : class
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentCallIndex)
currentCallIndex++;
listeners.Insert(insertIndex, listenerData); listeners.Insert(insertIndex, listenerData);
} }
@@ -248,6 +267,9 @@ public class Event<TSender> where TSender : class
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentOnceCallIndex)
currentOnceCallIndex++;
onceListeners.Insert(insertIndex, listenerData); onceListeners.Insert(insertIndex, listenerData);
} }
@@ -261,6 +283,8 @@ public class Event<TSender> where TSender : class
if (listeners[i].Callback == listener) if (listeners[i].Callback == listener)
{ {
listeners.RemoveAt(i); listeners.RemoveAt(i);
if (i < currentCallIndex)
currentCallIndex--;
return; return;
} }
} }
@@ -275,6 +299,8 @@ public class Event<TSender> where TSender : class
if (onceListeners[i].Callback == listener) if (onceListeners[i].Callback == listener)
{ {
onceListeners.RemoveAt(i); onceListeners.RemoveAt(i);
if (i < currentOnceCallIndex)
currentOnceCallIndex--;
return; return;
} }
} }
@@ -290,23 +316,23 @@ public class Event<TSender> where TSender : class
/// <param name="sender">The caller that's triggering this event.</param> /// <param name="sender">The caller that's triggering this event.</param>
public void Invoke(TSender sender) public void Invoke(TSender sender)
{ {
for (int i = listeners.Count - 1; i >= 0; i--) for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--)
try { listeners[i].Callback.Invoke(sender); } try { listeners[currentCallIndex].Callback.Invoke(sender); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender})"; string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}({sender})";
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(listeners[currentCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
} }
for (int i = onceListeners.Count - 1; i >= 0; i--) for (currentOnceCallIndex = onceListeners.Count - 1; currentOnceCallIndex >= 0; currentOnceCallIndex--)
{ {
try { onceListeners[i].Callback.Invoke(sender); } try { onceListeners[currentOnceCallIndex].Callback.Invoke(sender); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender})"; string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}({sender})";
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
} }
onceListeners.RemoveAt(i); onceListeners.RemoveAt(currentOnceCallIndex);
} }
} }
@@ -382,6 +408,9 @@ public class Event<TSender, TArguments> where TSender : class
public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared; public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared;
private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private readonly List<ListenerData> listeners = null!; private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!; private readonly List<ListenerData> onceListeners = null!;
@@ -398,6 +427,9 @@ public class Event<TSender, TArguments> where TSender : class
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentCallIndex)
currentCallIndex++;
listeners.Insert(insertIndex, listenerData); listeners.Insert(insertIndex, listenerData);
} }
@@ -414,6 +446,9 @@ public class Event<TSender, TArguments> where TSender : class
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentOnceCallIndex)
currentOnceCallIndex++;
onceListeners.Insert(insertIndex, listenerData); onceListeners.Insert(insertIndex, listenerData);
} }
@@ -427,6 +462,8 @@ public class Event<TSender, TArguments> where TSender : class
if (listeners[i].Callback == listener) if (listeners[i].Callback == listener)
{ {
listeners.RemoveAt(i); listeners.RemoveAt(i);
if (i < currentCallIndex)
currentCallIndex--;
return; return;
} }
} }
@@ -441,6 +478,8 @@ public class Event<TSender, TArguments> where TSender : class
if (onceListeners[i].Callback == listener) if (onceListeners[i].Callback == listener)
{ {
onceListeners.RemoveAt(i); onceListeners.RemoveAt(i);
if (i < currentOnceCallIndex)
currentOnceCallIndex--;
return; return;
} }
} }
@@ -457,23 +496,23 @@ public class Event<TSender, TArguments> where TSender : class
/// <param name="args">The arguments provided for this event.</param> /// <param name="args">The arguments provided for this event.</param>
public void Invoke(TSender sender, TArguments args) public void Invoke(TSender sender, TArguments args)
{ {
for (int i = listeners.Count - 1; i >= 0; i--) for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--)
try { listeners[i].Callback.Invoke(sender, args); } try { listeners[currentCallIndex].Callback.Invoke(sender, args); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender}, {args})"; string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}({sender}, {args})";
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(listeners[currentCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
} }
for (int i = onceListeners.Count - 1; i >= 0; i--) for (currentOnceCallIndex = onceListeners.Count - 1; currentOnceCallIndex >= 0; currentOnceCallIndex--)
{ {
try { onceListeners[i].Callback.Invoke(sender, args); } try { onceListeners[currentOnceCallIndex].Callback.Invoke(sender, args); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender}, {args})"; string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}({sender}, {args})";
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
} }
onceListeners.RemoveAt(i); onceListeners.RemoveAt(currentOnceCallIndex);
} }
} }