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.Collections.Generic;
20 using System.Reflection;
24 internal class WeakEvent<T> where T : Delegate
26 private const int addThreshold = 1000; // Experimetal constant
27 private const int listLengthThreshold = 1000; // Experimetal constant
28 private int cleanUpAddCount = 0;
29 private List<WeakHandler<T>> handlers = new List<WeakHandler<T>>();
31 protected int Count => handlers.Count;
33 public virtual void Add(T handler)
35 handlers.Add(new WeakHandler<T>(handler));
38 CleanUpDeadHandlersIfNeeds();
41 public virtual void Remove(T handler)
43 int lastIndex = handlers.FindLastIndex(item => item.Equals(handler));
47 handlers.RemoveAt(lastIndex);
52 public void Invoke(object sender, EventArgs args)
54 // Iterate copied one to prevent addition/removal item in the handler call.
55 var copiedArray = handlers.ToArray();
56 foreach (var item in copiedArray)
58 item.Invoke(sender, args);
62 CleanUpDeadHandlers();
65 protected virtual void OnCountIncreased()
70 protected virtual void OnCountDicreased()
74 private void CleanUpDeadHandlersIfNeeds()
76 // Once the list count exceed 'listLengthThreshold', do the clean-up every 'addThreshold' add operations.
77 // When list count go below `listLengthThreshold` before clean-up, pause operation counting.
78 if (handlers.Count > listLengthThreshold && ++cleanUpAddCount > addThreshold)
80 CleanUpDeadHandlers();
84 private void CleanUpDeadHandlers()
87 int count = handlers.Count;
88 handlers.RemoveAll(item => !item.IsAlive);
89 if (count > handlers.Count) OnCountDicreased();
92 internal class WeakHandler<U> where U : Delegate
94 private WeakReference weakTarget; // Null value means the method is static.
95 private MethodInfo methodInfo;
97 public WeakHandler(U handler)
99 Delegate d = (Delegate)(object)handler;
100 if (d.Target != null) weakTarget = new WeakReference(d.Target);
101 methodInfo = d.Method;
104 private bool IsStatic => weakTarget == null;
110 var rooting = weakTarget?.Target;
112 return IsStatic || !IsDisposed(rooting);
116 private static bool IsDisposed(object target)
118 if (target == null) return true;
120 if (target is BaseHandle basehandle) return basehandle.Disposed || basehandle.IsDisposeQueued;
122 if (target is Disposable disposable) return disposable.Disposed || disposable.IsDisposeQueued;
127 public bool Equals(U handler)
129 Delegate other = (Delegate)(object)handler;
130 bool isOtherStatic = other.Target == null;
131 return (isOtherStatic || weakTarget?.Target == other.Target) && methodInfo.Equals(other.Method);
134 public void Invoke(params object[] args)
138 Delegate.CreateDelegate(typeof(U), methodInfo).DynamicInvoke(args);
142 // Because GC is done in other thread,
143 // it needs to check again that the reference is still alive before calling method.
144 // To do that, the reference should be assigned to the local variable first.
145 var rooting = weakTarget.Target;
149 Delegate.CreateDelegate(typeof(U), rooting, methodInfo).DynamicInvoke(args);