File: System\Delegate.cs
Web Access
Project: src\src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Threading;

using Internal.Reflection.Augments;
using Internal.Runtime;
using Internal.Runtime.Augments;
using Internal.Runtime.CompilerServices;

namespace System
{
    public abstract partial class Delegate : ICloneable, ISerializable
    {
        // V1 API: Create closed instance delegates. Method name matching is case sensitive.
        [RequiresUnreferencedCode("The target method might be removed")]
        protected Delegate(object target, string method)
        {
            // This constructor cannot be used by application code. To create a delegate by specifying the name of a method, an
            // overload of the public static CreateDelegate method is used. This will eventually end up calling into the internal
            // implementation of CreateDelegate below, and does not invoke this constructor.
            // The constructor is just for API compatibility with the public contract of the Delegate class.
            throw new PlatformNotSupportedException();
        }

        // V1 API: Create open static delegates. Method name matching is case insensitive.
        protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllMethods)] Type target, string method)
        {
            // This constructor cannot be used by application code. To create a delegate by specifying the name of a method, an
            // overload of the public static CreateDelegate method is used. This will eventually end up calling into the internal
            // implementation of CreateDelegate below, and does not invoke this constructor.
            // The constructor is just for API compatibility with the public contract of the Delegate class.
            throw new PlatformNotSupportedException();
        }

        // New Delegate Implementation

        private object _firstParameter;
        private object _helperObject;
        private nint _extraFunctionPointerOrData;
        private IntPtr _functionPointer;

        // _helperObject may point to an array of delegates if this is a multicast delegate. We use this wrapper to distinguish between
        // our own array of delegates and user provided Wrapper[]. As a added benefit, this wrapper also eliminates array co-variance
        // overhead for our own array of delegates.
        private struct Wrapper
        {
            public Wrapper(Delegate value) => Value = value;
            public Delegate Value;
        }

        // WARNING: These constants are also declared in System.Private.TypeLoader\Internal\Runtime\TypeLoader\CallConverterThunk.cs
        // Do not change their values without updating the values in the calling convention converter component
        private protected const int MulticastThunk = 0;
        private protected const int ClosedStaticThunk = 1;
        private protected const int OpenStaticThunk = 2;
        private protected const int ClosedInstanceThunkOverGenericMethod = 3; // This may not exist
        private protected const int OpenInstanceThunk = 4;        // This may not exist
        private protected const int ObjectArrayThunk = 5;         // This may not exist

        //
        // If the thunk does not exist, the function will return IntPtr.Zero.
        private protected virtual IntPtr GetThunk(int whichThunk)
        {
            // NativeAOT doesn't support Universal Shared Code, so let's make this method return null.
            return IntPtr.Zero;
        }

        /// <summary>
        /// The reflection apis use this api to figure out what MethodInfo is related
        /// to a delegate.
        /// </summary>
        /// <param name="typeOfFirstParameterIfInstanceDelegate">
        ///   This value indicates which type an delegate's function pointer is associated with
        ///   This value is ONLY set for delegates where the function pointer points at an instance method
        /// </param>
        /// <param name="isOpenResolver">
        ///   This value indicates if the returned pointer is an open resolver structure.
        /// </param>
        internal unsafe IntPtr GetDelegateLdFtnResult(out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver)
        {
            typeOfFirstParameterIfInstanceDelegate = default(RuntimeTypeHandle);
            isOpenResolver = false;

            if (_extraFunctionPointerOrData != 0)
            {
                if (GetThunk(OpenInstanceThunk) == _functionPointer)
                {
                    typeOfFirstParameterIfInstanceDelegate = ((OpenMethodResolver*)_extraFunctionPointerOrData)->DeclaringType;
                    isOpenResolver = true;
                }
                return _extraFunctionPointerOrData;
            }
            else
            {
                if (_firstParameter != null)
                    typeOfFirstParameterIfInstanceDelegate = new RuntimeTypeHandle(_firstParameter.GetMethodTable());

                return _functionPointer;
            }
        }

        // This function is known to the compiler.
        private void InitializeClosedInstance(object firstParameter, IntPtr functionPointer)
        {
            if (firstParameter is null)
                throw new ArgumentException(SR.Arg_DlgtNullInst);

            _functionPointer = functionPointer;
            _firstParameter = firstParameter;
        }

        // This function is known to the compiler.
        private void InitializeClosedInstanceSlow(object firstParameter, IntPtr functionPointer)
        {
            // This method is like InitializeClosedInstance, but it handles ALL cases. In particular, it handles generic method with fun function pointers.

            if (firstParameter is null)
                throw new ArgumentException(SR.Arg_DlgtNullInst);

            if (!FunctionPointerOps.IsGenericMethodPointer(functionPointer))
            {
                _functionPointer = functionPointer;
                _firstParameter = firstParameter;
            }
            else
            {
                _firstParameter = this;
                _functionPointer = GetThunk(ClosedInstanceThunkOverGenericMethod);
                _extraFunctionPointerOrData = functionPointer;
                _helperObject = firstParameter;
            }
        }

        // This function is known to the compiler.
        private void InitializeClosedInstanceWithGVMResolution(object firstParameter, RuntimeMethodHandle tokenOfGenericVirtualMethod)
        {
            if (firstParameter is null)
                throw new NullReferenceException();

            IntPtr functionResolution = TypeLoaderExports.GVMLookupForSlot(firstParameter, tokenOfGenericVirtualMethod);

            if (functionResolution == IntPtr.Zero)
            {
                // TODO! What to do when GVM resolution fails. Should never happen
                throw new InvalidOperationException();
            }
            if (!FunctionPointerOps.IsGenericMethodPointer(functionResolution))
            {
                _functionPointer = functionResolution;
                _firstParameter = firstParameter;
            }
            else
            {
                _firstParameter = this;
                _functionPointer = GetThunk(ClosedInstanceThunkOverGenericMethod);
                _extraFunctionPointerOrData = functionResolution;
                _helperObject = firstParameter;
            }

            return;
        }

        // This function is known to the compiler.
        private void InitializeClosedInstanceToInterface(object firstParameter, IntPtr dispatchCell)
        {
            if (firstParameter is null)
                throw new NullReferenceException();

            _functionPointer = RuntimeImports.RhpResolveInterfaceMethod(firstParameter, dispatchCell);
            _firstParameter = firstParameter;
        }

        // This is used to implement MethodInfo.CreateDelegate() in a desktop-compatible way. Yes, the desktop really
        // let you use that api to invoke an instance method with a null 'this'.
        private void InitializeClosedInstanceWithoutNullCheck(object firstParameter, IntPtr functionPointer)
        {
            if (!FunctionPointerOps.IsGenericMethodPointer(functionPointer))
            {
                _functionPointer = functionPointer;
                _firstParameter = firstParameter;
            }
            else
            {
                _firstParameter = this;
                _functionPointer = GetThunk(ClosedInstanceThunkOverGenericMethod);
                _extraFunctionPointerOrData = functionPointer;
                _helperObject = firstParameter;
            }
        }

        // This function is known to the compiler.
        private void InitializeClosedStaticThunk(object firstParameter, IntPtr functionPointer, IntPtr functionPointerThunk)
        {
            _extraFunctionPointerOrData = functionPointer;
            _helperObject = firstParameter;
            _functionPointer = functionPointerThunk;
            _firstParameter = this;
        }

        // This function is known to the compiler.
        private void InitializeOpenStaticThunk(object _ /*firstParameter*/, IntPtr functionPointer, IntPtr functionPointerThunk)
        {
            // This sort of delegate is invoked by calling the thunk function pointer with the arguments to the delegate + a reference to the delegate object itself.
            _firstParameter = this;
            _functionPointer = functionPointerThunk;
            _extraFunctionPointerOrData = functionPointer;
        }

        private void InitializeOpenInstanceThunkDynamic(IntPtr functionPointer, IntPtr functionPointerThunk)
        {
            // This sort of delegate is invoked by calling the thunk function pointer with the arguments to the delegate + a reference to the delegate object itself.
            _firstParameter = this;
            _functionPointer = functionPointerThunk;
            _extraFunctionPointerOrData = functionPointer;
        }

        // This function is only ever called by the open instance method thunk, and in that case,
        // _extraFunctionPointerOrData always points to an OpenMethodResolver
        [MethodImpl(MethodImplOptions.NoInlining)]
        private IntPtr GetActualTargetFunctionPointer(object thisObject)
        {
            return OpenMethodResolver.ResolveMethod(_extraFunctionPointerOrData, thisObject);
        }

        internal bool IsDynamicDelegate() => GetThunk(MulticastThunk) == IntPtr.Zero;

        [DebuggerGuidedStepThroughAttribute]
        protected virtual object? DynamicInvokeImpl(object?[]? args)
        {
            if (IsDynamicDelegate())
            {
                // DynamicDelegate case
                object? result = ((Func<object?[]?, object?>)_helperObject)(args);
                DebugAnnotations.PreviousCallContainsDebuggerStepInCode();
                return result;
            }
            else
            {
                DynamicInvokeInfo dynamicInvokeInfo = ReflectionAugments.GetDelegateDynamicInvokeInfo(GetType());

                object? result = dynamicInvokeInfo.Invoke(_firstParameter, _functionPointer,
                    args, binderBundle: null, wrapInTargetInvocationException: true);
                DebugAnnotations.PreviousCallContainsDebuggerStepInCode();
                return result;
            }
        }

        protected virtual MethodInfo GetMethodImpl()
        {
            // NOTE: this implementation is mirrored in GetDiagnosticMethodInfo below

            // Multi-cast delegates return the Method of the last delegate in the list
            if (_helperObject is Wrapper[] invocationList)
            {
                int invocationCount = (int)_extraFunctionPointerOrData;
                return invocationList[invocationCount - 1].Value.GetMethodImpl();
            }

            // Return the delegate Invoke method for marshalled function pointers and LINQ expressions
            if ((_firstParameter is NativeFunctionPointerWrapper) || (_functionPointer == GetThunk(ObjectArrayThunk)))
            {
                return GetType().GetMethod("Invoke");
            }

            return ReflectionAugments.GetDelegateMethod(this);
        }

        internal DiagnosticMethodInfo GetDiagnosticMethodInfo()
        {
            // NOTE: this implementation is mirrored in GetMethodImpl above

            // Multi-cast delegates return the diagnostic method info of the last delegate in the list
            if (_helperObject is Wrapper[] invocationList)
            {
                int invocationCount = (int)_extraFunctionPointerOrData;
                return invocationList[invocationCount - 1].Value.GetDiagnosticMethodInfo();
            }

            // Return the delegate Invoke method for marshalled function pointers and LINQ expressions
            if ((_firstParameter is NativeFunctionPointerWrapper) || (_functionPointer == GetThunk(ObjectArrayThunk)))
            {
                Type t = GetType();
                return new DiagnosticMethodInfo("Invoke", t.FullName, t.Module.Assembly.FullName);
            }

            IntPtr ldftnResult = GetDelegateLdFtnResult(out RuntimeTypeHandle _, out bool isOpenResolver);
            if (isOpenResolver)
            {
                MethodInfo mi = ReflectionAugments.GetDelegateMethod(this);
                Type? declaringType = mi.DeclaringType;
                if (declaringType.IsConstructedGenericType)
                    declaringType = declaringType.GetGenericTypeDefinition();
                return new DiagnosticMethodInfo(mi.Name, declaringType.FullName, mi.Module.Assembly.FullName);
            }
            else
            {
                IntPtr functionPointer;
                if (FunctionPointerOps.IsGenericMethodPointer(ldftnResult))
                {
                    unsafe
                    {
                        GenericMethodDescriptor* realTargetData = FunctionPointerOps.ConvertToGenericDescriptor(ldftnResult);
                        functionPointer = RuntimeAugments.GetCodeTarget(realTargetData->MethodFunctionPointer);
                    }
                }
                else
                {
                    nint unboxedPointer = RuntimeAugments.GetCodeTarget(ldftnResult);
                    if (unboxedPointer == ldftnResult)
                        unboxedPointer = RuntimeAugments.GetTargetOfUnboxingAndInstantiatingStub(ldftnResult);

                    functionPointer = unboxedPointer != 0 ? unboxedPointer : ldftnResult;
                }
                return RuntimeAugments.StackTraceCallbacksIfAvailable?.TryGetDiagnosticMethodInfoFromStartAddress(functionPointer);
            }
        }

        public object? Target
        {
            get
            {
                // Multi-cast delegates return the Target of the last delegate in the list
                if (_helperObject is Wrapper[] invocationList)
                {
                    int invocationCount = (int)_extraFunctionPointerOrData;
                    return invocationList[invocationCount - 1].Value.Target;
                }

                // Closed static delegates place a value in _helperObject that they pass to the target method.
                if (_functionPointer == GetThunk(ClosedStaticThunk) ||
                    _functionPointer == GetThunk(ClosedInstanceThunkOverGenericMethod) ||
                    _functionPointer == GetThunk(ObjectArrayThunk))
                    return _helperObject;

                // Other non-closed thunks can be identified as the _firstParameter field points at this.
                if (object.ReferenceEquals(_firstParameter, this))
                {
                    return null;
                }

                // NativeFunctionPointerWrapper used by marshalled function pointers is not returned as a public target
                if (_firstParameter is NativeFunctionPointerWrapper)
                {
                    return null;
                }

                // Closed instance delegates place a value in _firstParameter, and we've ruled out all other types of delegates
                return _firstParameter;
            }
        }

        // V2 api: Creates open or closed delegates to static or instance methods - relaxed signature checking allowed.
        public static Delegate CreateDelegate(Type type, object? firstArgument, MethodInfo method, bool throwOnBindFailure) => ReflectionAugments.CreateDelegate(type, firstArgument, method, throwOnBindFailure);

        // V1 api: Creates open delegates to static or instance methods - relaxed signature checking allowed.
        public static Delegate CreateDelegate(Type type, MethodInfo method, bool throwOnBindFailure) => ReflectionAugments.CreateDelegate(type, method, throwOnBindFailure);

        // V1 api: Creates closed delegates to instance methods only, relaxed signature checking disallowed.
        [RequiresUnreferencedCode("The target method might be removed")]
        public static Delegate CreateDelegate(Type type, object target, string method, bool ignoreCase, bool throwOnBindFailure) => ReflectionAugments.CreateDelegate(type, target, method, ignoreCase, throwOnBindFailure);

        // V1 api: Creates open delegates to static methods only, relaxed signature checking disallowed.
        public static Delegate CreateDelegate(Type type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllMethods)] Type target, string method, bool ignoreCase, bool throwOnBindFailure) => ReflectionAugments.CreateDelegate(type, target, method, ignoreCase, throwOnBindFailure);

        internal IntPtr TryGetOpenStaticFunctionPointer() => (GetThunk(OpenStaticThunk) == _functionPointer) ? _extraFunctionPointerOrData : 0;

        internal NativeFunctionPointerWrapper? TryGetNativeFunctionPointerWrapper() => _firstParameter as NativeFunctionPointerWrapper;

        internal static unsafe bool InternalEqualTypes(object a, object b)
        {
            return a.GetMethodTable() == b.GetMethodTable();
        }

        // Returns a new delegate of the specified type whose implementation is provided by the
        // provided delegate.
        internal static unsafe Delegate CreateObjectArrayDelegate(Type t, Func<object?[], object?> handler)
        {
            RuntimeTypeHandle typeHandle = t.TypeHandle;

            MethodTable* delegateEEType = typeHandle.ToMethodTable();
            Debug.Assert(delegateEEType != null);
            Debug.Assert(delegateEEType->IsCanonical);

            RuntimeAugments.EnsureMethodTableSafeToAllocate(delegateEEType);

            Delegate del = (Delegate)(RuntimeImports.RhNewObject(delegateEEType));

            IntPtr objArrayThunk = del.GetThunk(Delegate.ObjectArrayThunk);
            if (objArrayThunk == IntPtr.Zero)
            {
                throw new InvalidOperationException();
            }

            del._helperObject = handler;
            del._functionPointer = objArrayThunk;
            del._firstParameter = del;
            return del;
        }

        //
        // Internal (and quite unsafe) helper to create delegates of an arbitrary type. This is used to support Reflection invoke.
        //
        // Note that delegates constructed the normal way do not come through here. The IL transformer generates the equivalent of
        // this code customized for each delegate type.
        //
        internal static unsafe Delegate CreateDelegate(MethodTable* delegateEEType, IntPtr ldftnResult, object thisObject, bool isStatic, bool isOpen)
        {
            RuntimeAugments.EnsureMethodTableSafeToAllocate(delegateEEType);

            Delegate del = (Delegate)RuntimeImports.RhNewObject(delegateEEType);

            // What? No constructor call? That's right, and it's not an oversight. All "construction" work happens in
            // the Initialize() methods. This helper has a hard dependency on this invariant.

            if (isStatic)
            {
                if (isOpen)
                {
                    IntPtr thunk = del.GetThunk(Delegate.OpenStaticThunk);
                    del.InitializeOpenStaticThunk(null, ldftnResult, thunk);
                }
                else
                {
                    IntPtr thunk = del.GetThunk(Delegate.ClosedStaticThunk);
                    del.InitializeClosedStaticThunk(thisObject, ldftnResult, thunk);
                }
            }
            else
            {
                if (isOpen)
                {
                    IntPtr thunk = del.GetThunk(Delegate.OpenInstanceThunk);
                    del.InitializeOpenInstanceThunkDynamic(ldftnResult, thunk);
                }
                else
                {
                    del.InitializeClosedInstanceWithoutNullCheck(thisObject, ldftnResult);
                }
            }
            return del;
        }

        private unsafe Delegate NewMulticastDelegate(Wrapper[] invocationList, int invocationCount, bool thisIsMultiCastAlready = false)
        {
            // First, allocate a new delegate just like this one, i.e. same type as the this object
            Delegate result = Unsafe.As<Delegate>(RuntimeImports.RhNewObject(this.GetMethodTable()));

            // Performance optimization - if this already points to a true multicast delegate,
            // copy _functionPointer field rather than calling GetThunk to get it
            result._functionPointer = thisIsMultiCastAlready ? _functionPointer : GetThunk(MulticastThunk);
            result._firstParameter = result;
            result._helperObject = invocationList;
            result._extraFunctionPointerOrData = (IntPtr)invocationCount;

            return result;
        }

        private static bool TrySetSlot(Wrapper[] a, int index, Delegate o)
        {
            if (a[index].Value == null && Interlocked.CompareExchange(ref a[index].Value, o, null) == null)
                return true;

            // The slot may be already set because we have added and removed the same method before.
            // Optimize this case, because it's cheaper than copying the array.
            if (a[index].Value is Delegate dd)
            {
                if (object.ReferenceEquals(dd._firstParameter, o._firstParameter) &&
                    object.ReferenceEquals(dd._helperObject, o._helperObject) &&
                    dd._extraFunctionPointerOrData == o._extraFunctionPointerOrData &&
                    dd._functionPointer == o._functionPointer)
                {
                    return true;
                }
            }
            return false;
        }

        // This method will combine this delegate with the passed delegate
        //  to form a new delegate.
        protected virtual Delegate CombineImpl(Delegate? d)
        {
            if (d is null)
                return this;

            // Verify that the types are the same...
            if (!InternalEqualTypes(this, d))
                throw new ArgumentException(SR.Arg_DlgtTypeMis);

            if (IsDynamicDelegate())
                throw new InvalidOperationException();

            int followCount = 1;
            Wrapper[]? followList = d._helperObject as Wrapper[];
            if (followList != null)
                followCount = (int)d._extraFunctionPointerOrData;

            int resultCount;
            Wrapper[]? resultList;
            if (_helperObject is not Wrapper[] invocationList)
            {
                resultCount = 1 + followCount;
                resultList = new Wrapper[resultCount];
                resultList[0] = new Wrapper(this);
                if (followList == null)
                {
                    resultList[1] = new Wrapper(d);
                }
                else
                {
                    for (int i = 0; i < followCount; i++)
                        resultList[1 + i] = followList[i];
                }
                return NewMulticastDelegate(resultList, resultCount);
            }
            else
            {
                int invocationCount = (int)_extraFunctionPointerOrData;
                resultCount = invocationCount + followCount;
                resultList = null;
                if (resultCount <= invocationList.Length)
                {
                    resultList = invocationList;
                    if (followList == null)
                    {
                        if (!TrySetSlot(resultList, invocationCount, d))
                            resultList = null;
                    }
                    else
                    {
                        for (int i = 0; i < followCount; i++)
                        {
                            if (!TrySetSlot(resultList, invocationCount + i, followList[i].Value))
                            {
                                resultList = null;
                                break;
                            }
                        }
                    }
                }

                if (resultList == null)
                {
                    int allocCount = invocationList.Length;
                    while (allocCount < resultCount)
                        allocCount *= 2;

                    resultList = new Wrapper[allocCount];

                    for (int i = 0; i < invocationCount; i++)
                        resultList[i] = invocationList[i];

                    if (followList == null)
                    {
                        resultList[invocationCount] = new Wrapper(d);
                    }
                    else
                    {
                        for (int i = 0; i < followCount; i++)
                            resultList[invocationCount + i] = followList[i];
                    }
                }
                return NewMulticastDelegate(resultList, resultCount, true);
            }
        }

        private static Wrapper[] DeleteFromInvocationList(Wrapper[] invocationList, int invocationCount, int deleteIndex, int deleteCount)
        {
            int allocCount = invocationList.Length;
            while (allocCount / 2 >= invocationCount - deleteCount)
                allocCount /= 2;

            Wrapper[] newInvocationList = new Wrapper[allocCount];

            for (int i = 0; i < deleteIndex; i++)
                newInvocationList[i] = invocationList[i];

            for (int i = deleteIndex + deleteCount; i < invocationCount; i++)
                newInvocationList[i - deleteCount] = invocationList[i];

            return newInvocationList;
        }

        private static bool EqualInvocationLists(Wrapper[] a, Wrapper[] b, int start, int count)
        {
            for (int i = 0; i < count; i++)
            {
                if (!(a[start + i].Value.Equals(b[i].Value)))
                    return false;
            }
            return true;
        }

        // This method currently looks backward on the invocation list
        //  for an element that has Delegate based equality with value.  (Doesn't
        //  look at the invocation list.)  If this is found we remove it from
        //  this list and return a new delegate.  If its not found a copy of the
        //  current list is returned.
        protected virtual Delegate? RemoveImpl(Delegate d)
        {
            // There is a special case were we are removing using a delegate as
            //    the value we need to check for this case
            //
            if (d is null)
                return this;
            if (d._helperObject is not Wrapper[] dInvocationList)
            {
                if (_helperObject is not Wrapper[] invocationList)
                {
                    // they are both not real Multicast
                    if (this.Equals(d))
                        return null;
                }
                else
                {
                    int invocationCount = (int)_extraFunctionPointerOrData;
                    for (int i = invocationCount; --i >= 0;)
                    {
                        if (d.Equals(invocationList[i].Value))
                        {
                            if (invocationCount == 2)
                            {
                                // Special case - only one value left, either at the beginning or the end
                                return invocationList[1 - i].Value;
                            }
                            else
                            {
                                Wrapper[] list = DeleteFromInvocationList(invocationList, invocationCount, i, 1);
                                return NewMulticastDelegate(list, invocationCount - 1, true);
                            }
                        }
                    }
                }
            }
            else
            {
                if (_helperObject is Wrapper[] invocationList)
                {
                    int invocationCount = (int)_extraFunctionPointerOrData;
                    int dInvocationCount = (int)d._extraFunctionPointerOrData;
                    for (int i = invocationCount - dInvocationCount; i >= 0; i--)
                    {
                        if (EqualInvocationLists(invocationList, dInvocationList, i, dInvocationCount))
                        {
                            if (invocationCount - dInvocationCount == 0)
                            {
                                // Special case - no values left
                                return null;
                            }
                            else if (invocationCount - dInvocationCount == 1)
                            {
                                // Special case - only one value left, either at the beginning or the end
                                return invocationList[i != 0 ? 0 : invocationCount - 1].Value;
                            }
                            else
                            {
                                Wrapper[] list = DeleteFromInvocationList(invocationList, invocationCount, i, dInvocationCount);
                                return NewMulticastDelegate(list, invocationCount - dInvocationCount, true);
                            }
                        }
                    }
                }
            }

            return this;
        }

        public virtual Delegate[] GetInvocationList()
        {
            if (_helperObject is Wrapper[] invocationList)
            {
                // Create an array of delegate copies and each
                //    element into the array
                int invocationCount = (int)_extraFunctionPointerOrData;

                var del = new Delegate[invocationCount];
                for (int i = 0; i < del.Length; i++)
                    del[i] = invocationList[i].Value;
                return del;
            }

            return new Delegate[] { this };
        }

        public override bool Equals([NotNullWhen(true)] object? obj)
        {
            if (obj == null)
                return false;
            if (object.ReferenceEquals(this, obj))
                return true;
            if (!InternalEqualTypes(this, obj))
                return false;

            // Since this is a Delegate and we know the types are the same, obj should also be a Delegate
            Debug.Assert(obj is Delegate, "Shouldn't have failed here since we already checked the types are the same!");
            var d = Unsafe.As<Delegate>(obj);

            if (_helperObject is Wrapper[] invocationList)
            {
                if (d._extraFunctionPointerOrData != _extraFunctionPointerOrData)
                    return false;

                if (d._helperObject is not Wrapper[] dInvocationList)
                    return false;

                int invocationCount = (int)_extraFunctionPointerOrData;
                for (int i = 0; i < invocationCount; i++)
                {
                    if (!invocationList[i].Value.Equals(dInvocationList[i].Value))
                        return false;
                }
                return true;
            }

            if (_firstParameter is NativeFunctionPointerWrapper nativeFunctionPointerWrapper)
            {
                if (d._firstParameter is not NativeFunctionPointerWrapper dnativeFunctionPointerWrapper)
                    return false;

                return nativeFunctionPointerWrapper.NativeFunctionPointer == dnativeFunctionPointerWrapper.NativeFunctionPointer;
            }

            if (!object.ReferenceEquals(_helperObject, d._helperObject) ||
                (!FunctionPointerOps.Compare(_extraFunctionPointerOrData, d._extraFunctionPointerOrData)) ||
                (!FunctionPointerOps.Compare(_functionPointer, d._functionPointer)))
            {
                return false;
            }

            // Those delegate kinds with thunks put themselves into the _firstParameter, so we can't
            // blindly compare the _firstParameter fields for equality.
            if (object.ReferenceEquals(_firstParameter, this))
            {
                return object.ReferenceEquals(d._firstParameter, d);
            }

            return object.ReferenceEquals(_firstParameter, d._firstParameter);
        }

        public override int GetHashCode()
        {
            if (_helperObject is Wrapper[] invocationList)
            {
                int multiCastHash = 0;
                for (int i = 0; i < (int)_extraFunctionPointerOrData; i++)
                {
                    multiCastHash = multiCastHash * 33 + invocationList[i].Value.GetHashCode();
                }
                return multiCastHash;
            }

            if (_firstParameter is NativeFunctionPointerWrapper nativeFunctionPointerWrapper)
            {
                return nativeFunctionPointerWrapper.NativeFunctionPointer.GetHashCode();
            }

            int hash = RuntimeHelpers.GetHashCode(_helperObject) +
                7 * FunctionPointerOps.GetHashCode(_extraFunctionPointerOrData) +
                13 * FunctionPointerOps.GetHashCode(_functionPointer);

            if (!object.ReferenceEquals(_firstParameter, this))
            {
                hash += 17 * RuntimeHelpers.GetHashCode(_firstParameter);
            }

            return hash;
        }

        public bool HasSingleTarget => _helperObject is not Wrapper[];

        // Used by delegate invocation list enumerator
        internal Delegate? TryGetAt(int index)
        {
            if (_helperObject is Wrapper[] invocationList)
            {
                return ((uint)index < (uint)_extraFunctionPointerOrData) ? invocationList[index].Value : null;
            }

            return (index == 0) ? this : null;
        }
    }
}