1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Diagnostics;
6 using System.Diagnostics.CodeAnalysis;
7 using System.Reflection;
8 using System.Runtime.CompilerServices;
9 using System.Runtime.InteropServices;
10 using System.Runtime.Serialization;
12 using Internal.Runtime.CompilerServices;
16 [ClassInterface(ClassInterfaceType.None)]
18 public abstract class MulticastDelegate : Delegate
20 // This is set under 2 circumstances
21 // 1. Multicast delegate
22 // 2. Wrapper delegate
23 private object? _invocationList; // Initialized by VM as needed
24 private IntPtr _invocationCount;
26 // This constructor is called from the class generated by the
27 // compiler generated code (This must match the constructor
29 protected MulticastDelegate(object target, string method) : base(target, method)
33 // This constructor is called from a class to generate a
34 // delegate based upon a static method name and the Type object
35 // for the class defining the method.
36 protected MulticastDelegate(Type target, string method) : base(target, method)
40 internal bool IsUnmanagedFunctionPtr()
42 return (_invocationCount == (IntPtr)(-1));
45 internal bool InvocationListLogicallyNull()
47 return (_invocationList == null) || (_invocationList is LoaderAllocator) || (_invocationList is System.Reflection.Emit.DynamicResolver);
50 public override void GetObjectData(SerializationInfo info, StreamingContext context)
52 throw new SerializationException(SR.Serialization_DelegatesNotSupported);
55 // equals returns true IIF the delegate is not null and has the
56 // same target, method and invocation list as this object
57 public override sealed bool Equals(object? obj)
61 if (object.ReferenceEquals(this, obj))
63 if (!InternalEqualTypes(this, obj))
66 // Since this is a MulticastDelegate and we know
67 // the types are the same, obj should also be a
69 Debug.Assert(obj is MulticastDelegate, "Shouldn't have failed here since we already checked the types are the same!");
70 var d = Unsafe.As<MulticastDelegate>(obj);
72 if (_invocationCount != (IntPtr)0)
74 // there are 4 kind of delegate kinds that fall into this bucket
75 // 1- Multicast (_invocationList is Object[])
76 // 2- Wrapper (_invocationList is Delegate)
77 // 3- Unmanaged FntPtr (_invocationList == null)
78 // 4- Open virtual (_invocationCount == MethodDesc of target, _invocationList == null, LoaderAllocator, or DynamicResolver)
80 if (InvocationListLogicallyNull())
82 if (IsUnmanagedFunctionPtr())
84 if (!d.IsUnmanagedFunctionPtr())
87 return CompareUnmanagedFunctionPtrs(this, d);
90 // now we know 'this' is not a special one, so we can work out what the other is
91 if ((d._invocationList as Delegate) != null)
92 // this is a wrapper delegate so we need to unwrap and check the inner one
93 return Equals(d._invocationList);
95 return base.Equals(obj);
99 if (_invocationList is Delegate invocationListDelegate)
101 // this is a wrapper delegate so we need to unwrap and check the inner one
102 return invocationListDelegate.Equals(obj);
106 Debug.Assert((_invocationList as object[]) != null, "empty invocation list on multicast delegate");
107 return InvocationListEquals(d);
113 // among the several kind of delegates falling into this bucket one has got a non
114 // empty _invocationList (open static with special sig)
115 // to be equals we need to check that _invocationList matches (both null is fine)
116 // and call the base.Equals()
117 if (!InvocationListLogicallyNull())
119 if (!_invocationList!.Equals(d._invocationList))
121 return base.Equals(d);
124 // now we know 'this' is not a special one, so we can work out what the other is
125 if ((d._invocationList as Delegate) != null)
126 // this is a wrapper delegate so we need to unwrap and check the inner one
127 return Equals(d._invocationList);
129 // now we can call on the base
130 return base.Equals(d);
134 // Recursive function which will check for equality of the invocation list.
135 private bool InvocationListEquals(MulticastDelegate d)
137 Debug.Assert(d != null);
138 Debug.Assert(_invocationList is object[]);
139 object[] invocationList = (object[])_invocationList;
141 if (d._invocationCount != _invocationCount)
144 int invocationCount = (int)_invocationCount;
145 for (int i = 0; i < invocationCount; i++)
147 Debug.Assert(invocationList[i] is Delegate);
148 Delegate dd = (Delegate)invocationList[i]; // If invocationList is an object[], it always contains Delegate (or MulticastDelegate) objects
150 object[] dInvocationList = (d._invocationList as object[])!;
151 if (!dd.Equals(dInvocationList[i]))
157 private bool TrySetSlot(object?[] a, int index, object o)
159 if (a[index] == null && System.Threading.Interlocked.CompareExchange<object?>(ref a[index], o, null) == null)
162 // The slot may be already set because we have added and removed the same method before.
163 // Optimize this case, because it's cheaper than copying the array.
164 if (a[index] != null)
166 MulticastDelegate d = (MulticastDelegate)o;
167 MulticastDelegate dd = (MulticastDelegate)a[index]!; // TODO-NULLABLE: Indexer nullability tracked (https://github.com/dotnet/roslyn/issues/34644)
169 if (dd._methodPtr == d._methodPtr &&
170 dd._target == d._target &&
171 dd._methodPtrAux == d._methodPtrAux)
179 private MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready)
181 // First, allocate a new multicast delegate just like this one, i.e. same type as the this object
182 MulticastDelegate result = (MulticastDelegate)InternalAllocLike(this);
184 // Performance optimization - if this already points to a true multicast delegate,
185 // copy _methodPtr and _methodPtrAux fields rather than calling into the EE to get them
186 if (thisIsMultiCastAlready)
188 result._methodPtr = this._methodPtr;
189 result._methodPtrAux = this._methodPtrAux;
193 result._methodPtr = GetMulticastInvoke();
194 result._methodPtrAux = GetInvokeMethod();
196 result._target = result;
197 result._invocationList = invocationList;
198 result._invocationCount = (IntPtr)invocationCount;
203 internal MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount)
205 return NewMulticastDelegate(invocationList, invocationCount, false);
208 internal void StoreDynamicMethod(MethodInfo dynamicMethod)
210 if (_invocationCount != (IntPtr)0)
212 Debug.Assert(!IsUnmanagedFunctionPtr(), "dynamic method and unmanaged fntptr delegate combined");
213 // must be a secure/wrapper one, unwrap and save
214 MulticastDelegate d = ((MulticastDelegate?)_invocationList)!;
215 d._methodBase = dynamicMethod;
218 _methodBase = dynamicMethod;
221 // This method will combine this delegate with the passed delegate
222 // to form a new delegate.
223 protected override sealed Delegate CombineImpl(Delegate? follow)
225 if ((object?)follow == null) // cast to object for a more efficient test
228 // Verify that the types are the same...
229 if (!InternalEqualTypes(this, follow))
230 throw new ArgumentException(SR.Arg_DlgtTypeMis);
232 MulticastDelegate dFollow = (MulticastDelegate)follow;
233 object[]? resultList;
235 object[]? followList = dFollow._invocationList as object[];
236 if (followList != null)
237 followCount = (int)dFollow._invocationCount;
240 if (!(_invocationList is object[] invocationList))
242 resultCount = 1 + followCount;
243 resultList = new object[resultCount];
244 resultList[0] = this;
245 if (followList == null)
247 resultList[1] = dFollow;
251 for (int i = 0; i < followCount; i++)
252 resultList[1 + i] = followList[i];
254 return NewMulticastDelegate(resultList, resultCount);
258 int invocationCount = (int)_invocationCount;
259 resultCount = invocationCount + followCount;
261 if (resultCount <= invocationList.Length)
263 resultList = invocationList;
264 if (followList == null)
266 if (!TrySetSlot(resultList, invocationCount, dFollow))
271 for (int i = 0; i < followCount; i++)
273 if (!TrySetSlot(resultList, invocationCount + i, followList[i]))
282 if (resultList == null)
284 int allocCount = invocationList.Length;
285 while (allocCount < resultCount)
288 resultList = new object[allocCount];
290 for (int i = 0; i < invocationCount; i++)
291 resultList[i] = invocationList[i];
293 if (followList == null)
295 resultList[invocationCount] = dFollow;
299 for (int i = 0; i < followCount; i++)
300 resultList[invocationCount + i] = followList[i];
303 return NewMulticastDelegate(resultList, resultCount, true);
307 private object[] DeleteFromInvocationList(object[] invocationList, int invocationCount, int deleteIndex, int deleteCount)
309 Debug.Assert(_invocationList is object[]);
310 object[] thisInvocationList = (object[])_invocationList;
311 int allocCount = thisInvocationList.Length;
312 while (allocCount / 2 >= invocationCount - deleteCount)
315 object[] newInvocationList = new object[allocCount];
317 for (int i = 0; i < deleteIndex; i++)
318 newInvocationList[i] = invocationList[i];
320 for (int i = deleteIndex + deleteCount; i < invocationCount; i++)
321 newInvocationList[i - deleteCount] = invocationList[i];
323 return newInvocationList;
326 private bool EqualInvocationLists(object[] a, object[] b, int start, int count)
328 for (int i = 0; i < count; i++)
330 if (!(a[start + i].Equals(b[i])))
336 // This method currently looks backward on the invocation list
337 // for an element that has Delegate based equality with value. (Doesn't
338 // look at the invocation list.) If this is found we remove it from
339 // this list and return a new delegate. If its not found a copy of the
340 // current list is returned.
341 protected override sealed Delegate? RemoveImpl(Delegate value)
343 // There is a special case were we are removing using a delegate as
344 // the value we need to check for this case
346 MulticastDelegate? v = value as MulticastDelegate;
350 if (!(v._invocationList is object[]))
352 if (!(_invocationList is object[] invocationList))
354 // they are both not real Multicast
355 if (this.Equals(value))
360 int invocationCount = (int)_invocationCount;
361 for (int i = invocationCount; --i >= 0;)
363 if (value.Equals(invocationList[i]))
365 if (invocationCount == 2)
367 // Special case - only one value left, either at the beginning or the end
368 return (Delegate)invocationList[1 - i];
372 object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, 1);
373 return NewMulticastDelegate(list, invocationCount - 1, true);
381 if (_invocationList is object[] invocationList)
383 int invocationCount = (int)_invocationCount;
384 int vInvocationCount = (int)v._invocationCount;
385 for (int i = invocationCount - vInvocationCount; i >= 0; i--)
387 if (EqualInvocationLists(invocationList, (v._invocationList as object[])!, i, vInvocationCount))
389 if (invocationCount - vInvocationCount == 0)
391 // Special case - no values left
394 else if (invocationCount - vInvocationCount == 1)
396 // Special case - only one value left, either at the beginning or the end
397 return (Delegate)invocationList[i != 0 ? 0 : invocationCount - 1];
401 object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, vInvocationCount);
402 return NewMulticastDelegate(list, invocationCount - vInvocationCount, true);
412 // This method returns the Invocation list of this multicast delegate.
413 public override sealed Delegate[] GetInvocationList()
416 if (!(_invocationList is object[] invocationList))
418 del = new Delegate[1];
423 // Create an array of delegate copies and each
424 // element into the array
425 del = new Delegate[(int)_invocationCount];
427 for (int i = 0; i < del.Length; i++)
428 del[i] = (Delegate)invocationList[i];
433 // Force inline as the true/false ternary takes it above ALWAYS_INLINE size even though the asm ends up smaller
434 [MethodImpl(MethodImplOptions.AggressiveInlining)]
435 public static bool operator ==(MulticastDelegate? d1, MulticastDelegate? d2)
437 // Test d2 first to allow branch elimination when inlined for null checks (== null)
438 // so it can become a simple test
441 // return true/false not the test result https://github.com/dotnet/coreclr/issues/914
442 return (d1 is null) ? true : false;
445 return ReferenceEquals(d2, d1) ? true : d2.Equals((object?)d1);
448 // Force inline as the true/false ternary takes it above ALWAYS_INLINE size even though the asm ends up smaller
449 [MethodImpl(MethodImplOptions.AggressiveInlining)]
450 public static bool operator !=(MulticastDelegate? d1, MulticastDelegate? d2)
452 // Can't call the == operator as it will call object==
454 // Test d2 first to allow branch elimination when inlined for not null checks (!= null)
455 // so it can become a simple test
458 // return true/false not the test result https://github.com/dotnet/coreclr/issues/914
459 return (d1 is null) ? false : true;
462 return ReferenceEquals(d2, d1) ? false : !d2.Equals(d1);
465 public override sealed int GetHashCode()
467 if (IsUnmanagedFunctionPtr())
468 return ValueType.GetHashCodeOfPtr(_methodPtr) ^ ValueType.GetHashCodeOfPtr(_methodPtrAux);
470 if (_invocationCount != (IntPtr)0)
472 if (_invocationList is Delegate t)
474 // this is a wrapper delegate so we need to unwrap and check the inner one
475 return t.GetHashCode();
479 if (!(_invocationList is object[] invocationList))
481 return base.GetHashCode();
486 for (int i = 0; i < (int)_invocationCount; i++)
488 hash = hash * 33 + invocationList[i].GetHashCode();
495 internal override object? GetTarget()
497 if (_invocationCount != (IntPtr)0)
499 // _invocationCount != 0 we are in one of these cases:
500 // - Multicast -> return the target of the last delegate in the list
501 // - wrapper delegate -> return the target of the inner delegate
502 // - unmanaged function pointer - return null
503 // - virtual open delegate - return null
504 if (InvocationListLogicallyNull())
506 // both open virtual and ftn pointer return null for the target
511 if (_invocationList is object[] invocationList)
513 int invocationCount = (int)_invocationCount;
514 return ((Delegate)invocationList[invocationCount - 1]).GetTarget();
518 if (_invocationList is Delegate receiver)
519 return receiver.GetTarget();
523 return base.GetTarget();
526 protected override MethodInfo GetMethodImpl()
528 if (_invocationCount != (IntPtr)0 && _invocationList != null)
531 if (_invocationList is object[] invocationList)
533 int index = (int)_invocationCount - 1;
534 return ((Delegate)invocationList[index]).Method;
537 if (_invocationList is MulticastDelegate innerDelegate)
539 // must be a wrapper delegate
540 return innerDelegate.GetMethodImpl();
543 else if (IsUnmanagedFunctionPtr())
545 // we handle unmanaged function pointers here because the generic ones (used for WinRT) would otherwise
546 // be treated as open delegates by the base implementation, resulting in failure to get the MethodInfo
547 if ((_methodBase == null) || !(_methodBase is MethodInfo))
549 IRuntimeMethodInfo method = FindMethodHandle();
550 RuntimeType declaringType = RuntimeMethodHandle.GetDeclaringType(method);
552 // need a proper declaring type instance method on a generic type
553 if (RuntimeTypeHandle.IsGenericTypeDefinition(declaringType) || RuntimeTypeHandle.HasInstantiation(declaringType))
555 // we are returning the 'Invoke' method of this delegate so use this.GetType() for the exact type
556 RuntimeType reflectedType = (RuntimeType)GetType();
557 declaringType = reflectedType;
559 _methodBase = (MethodInfo)RuntimeType.GetMethodBase(declaringType, method)!;
561 return (MethodInfo)_methodBase;
564 // Otherwise, must be an inner delegate of a Wrapper of an open virtual method. In that case, call base implementation
565 return base.GetMethodImpl();
568 // this should help inlining
570 [System.Diagnostics.DebuggerNonUserCode]
571 private void ThrowNullThisInDelegateToInstance()
573 throw new ArgumentException(SR.Arg_DlgtNullInst);
576 [System.Diagnostics.DebuggerNonUserCode]
577 private void CtorClosed(object target, IntPtr methodPtr)
580 ThrowNullThisInDelegateToInstance();
581 this._target = target;
582 this._methodPtr = methodPtr;
585 [System.Diagnostics.DebuggerNonUserCode]
586 private void CtorClosedStatic(object target, IntPtr methodPtr)
588 this._target = target;
589 this._methodPtr = methodPtr;
592 [System.Diagnostics.DebuggerNonUserCode]
593 private void CtorRTClosed(object target, IntPtr methodPtr)
595 this._target = target;
596 this._methodPtr = AdjustTarget(target, methodPtr);
599 [System.Diagnostics.DebuggerNonUserCode]
600 private void CtorOpened(object target, IntPtr methodPtr, IntPtr shuffleThunk)
603 this._methodPtr = shuffleThunk;
604 this._methodPtrAux = methodPtr;
607 [System.Diagnostics.DebuggerNonUserCode]
608 private void CtorVirtualDispatch(object target, IntPtr methodPtr, IntPtr shuffleThunk)
611 this._methodPtr = shuffleThunk;
612 this._methodPtrAux = GetCallStub(methodPtr);
615 [System.Diagnostics.DebuggerNonUserCode]
616 private void CtorCollectibleClosedStatic(object target, IntPtr methodPtr, IntPtr gchandle)
618 this._target = target;
619 this._methodPtr = methodPtr;
620 this._methodBase = System.Runtime.InteropServices.GCHandle.InternalGet(gchandle);
623 [System.Diagnostics.DebuggerNonUserCode]
624 private void CtorCollectibleOpened(object target, IntPtr methodPtr, IntPtr shuffleThunk, IntPtr gchandle)
627 this._methodPtr = shuffleThunk;
628 this._methodPtrAux = methodPtr;
629 this._methodBase = System.Runtime.InteropServices.GCHandle.InternalGet(gchandle);
632 [System.Diagnostics.DebuggerNonUserCode]
633 private void CtorCollectibleVirtualDispatch(object target, IntPtr methodPtr, IntPtr shuffleThunk, IntPtr gchandle)
636 this._methodPtr = shuffleThunk;
637 this._methodPtrAux = GetCallStub(methodPtr);
638 this._methodBase = System.Runtime.InteropServices.GCHandle.InternalGet(gchandle);