2 * Copyright (c) 2020 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 using System.ComponentModel;
20 using System.Collections.Generic;
21 using System.Reflection;
26 /// The WeakEvent without holding strong reference of event handler.
28 [EditorBrowsable(EditorBrowsableState.Never)]
29 public class WeakEvent<T> where T : Delegate
31 private const int addThreshold = 1000; // Experimetal constant
32 private const int listLengthThreshold = 1000; // Experimetal constant
33 private int cleanUpAddCount = 0;
34 private List<WeakHandler<T>> handlers = new List<WeakHandler<T>>();
37 /// The count of currently added event handlers.
39 [EditorBrowsable(EditorBrowsableState.Never)]
40 protected int Count => handlers.Count;
43 /// Add an event handler.
45 [EditorBrowsable(EditorBrowsableState.Never)]
46 public virtual void Add(T handler)
48 handlers.Add(new WeakHandler<T>(handler));
51 CleanUpDeadHandlersIfNeeds();
55 /// Remove last added event handler.
57 [EditorBrowsable(EditorBrowsableState.Never)]
58 public virtual void Remove(T handler)
60 int lastIndex = handlers.FindLastIndex(item => item.Equals(handler));
64 handlers.RemoveAt(lastIndex);
70 /// Invoke event handlers.
72 [EditorBrowsable(EditorBrowsableState.Never)]
73 public void Invoke(object sender, EventArgs args)
75 // Iterate copied one to prevent addition/removal item in the handler call.
76 var copiedArray = handlers.ToArray();
77 foreach (var item in copiedArray)
79 item.Invoke(sender, args);
83 CleanUpDeadHandlers();
87 /// Invoked when the event handler count is increased.
89 [EditorBrowsable(EditorBrowsableState.Never)]
90 protected virtual void OnCountIncreased()
95 /// Invoked when the event handler count is decreased.
97 protected virtual void OnCountDicreased()
101 private void CleanUpDeadHandlersIfNeeds()
103 // Once the list count exceed 'listLengthThreshold', do the clean-up every 'addThreshold' add operations.
104 // When list count go below `listLengthThreshold` before clean-up, pause operation counting.
105 if (handlers.Count > listLengthThreshold && ++cleanUpAddCount > addThreshold)
107 CleanUpDeadHandlers();
111 private void CleanUpDeadHandlers()
114 int count = handlers.Count;
115 handlers.RemoveAll(item => !item.IsAlive);
116 if (count > handlers.Count) OnCountDicreased();
119 internal class WeakHandler<U> where U : Delegate
121 private WeakReference weakTarget; // Null value means the method is static.
122 private MethodInfo methodInfo;
124 public WeakHandler(U handler)
126 Delegate d = (Delegate)(object)handler;
127 if (d.Target != null) weakTarget = new WeakReference(d.Target);
128 methodInfo = d.Method;
131 private bool IsStatic => weakTarget == null;
137 var rooting = weakTarget?.Target;
139 return IsStatic || !IsDisposed(rooting);
143 private static bool IsDisposed(object target)
145 if (target == null) return true;
147 if (target is BaseHandle basehandle) return basehandle.Disposed || basehandle.IsDisposeQueued;
149 if (target is Disposable disposable) return disposable.Disposed || disposable.IsDisposeQueued;
154 public bool Equals(U handler)
156 Delegate other = (Delegate)(object)handler;
157 bool isOtherStatic = other.Target == null;
158 return (isOtherStatic || weakTarget?.Target == other.Target) && methodInfo.Equals(other.Method);
161 public void Invoke(params object[] args)
165 Delegate.CreateDelegate(typeof(U), methodInfo).DynamicInvoke(args);
169 // Because GC is done in other thread,
170 // it needs to check again that the reference is still alive before calling method.
171 // To do that, the reference should be assigned to the local variable first.
172 var rooting = weakTarget.Target;
176 Delegate.CreateDelegate(typeof(U), rooting, methodInfo).DynamicInvoke(args);