Location>code7788 >text

NET Core Delegation Underpinnings

Popularity:162 ℃/2024-11-13 11:39:22

synopsis

NET provides a callback function mechanism through delegates, unlike C/C++, delegates ensure that callbacks are type-safe and allow multicast delegates. It also supports calling static/instance methods.

In short, function pointers in C++ have the following functional limitations. Delegation, as a superior alternative in C#, compensates for the shortcomings of function pointers.

  1. Type insecurity
    A function pointer can point to a function with a completely different method definition. Correctness is not checked during compilation. At runtime this can lead to different signatures causing the program to crash
  2. Only static methods are supported
    Only static methods are supported, not instance methods (which can only be bypassed through evil ways)
  3. Method chaining is not supported
    Can only point to one method definition

Similarities between function pointers and delegates

function pointer

typedef int (*func)(int, int);

commission

delegate int func(int a, int b);

primitive model

image

The delegate keyword is used as syntactic sugar, and the IL layer automatically generates Invoke/BeginInvoke/EndInvoke methods for this keyword; BeginInvoke/EndInvoke is no longer supported in .NET Core

seeing is believing

    public abstract partial class Delegate : ICloneable, ISerializable
    {
        // _target is the object we will invoke on
        internal object? _target; // The comments in the source code aren't quite right(null if static delegate)。That's the way it should be.:If an instance method is registered,thenthispointer on a gauge,如果是静态thendelegateInstance itself。

        // MethodBase, either cached after first request or assigned from a DynamicMethod
        // For open delegates to collectible types, this may be a LoaderAllocator object
        internal object? _methodBase; //(computing) cache

        // _methodPtr is a pointer to the method we will invoke
        // It could be a small thunk if this is a static or UM call
        internal IntPtr _methodPtr;//Entry to Instance Methods,see thatIntPtrThe keyword knows to interact with the unmanaged heap,必然就是函数pointer on a gauge了,

        // In the case of a static method passed to a delegate, this field stores
        // whatever _methodPtr would have stored: and _methodPtr points to a
        // small thunk which removes the "this" pointer before going on
        // to _methodPtrAux.
        internal IntPtr _methodPtrAux;//Entry to static methods
    }
    public abstract class MulticastDelegate : Delegate
    {
// Underlying cornerstone of the Multicast Delegate
        private object? _invocationList; private nint _invocationCount; private object?
        private nint _invocationCount;

// Instance delegate calls this method
private void CtorClosed(object target, IntPtr methodPtr)
        {
            if (target == null)
                ThrowNullThisInDelegateToInstance();
            this._target = target;
            this._methodPtr = methodPtr;// function pointer is pointed to _methodPtrAux
        }
// Static delegate calls this method
private void CtorOpened(object target, IntPtr methodPtr, IntPtr shuffleThunk)
        {
            this._target = this;// As mentioned above, the judgment that _target's annotation is incorrect is here
            this._methodPtr = shuffleThunk;//unlike instance delegates, this is pointed to a stubbed function
            this._methodPtrAux = methodPtr;//the function pointer is pointed to _methodPtrAux
        }
    }

How do delegates support both static and instance methods?

sample code (computing)
        static void Main(string[] args)
        {
            //1.Registering Instance Methods
            MyClass myObject = new MyClass();
            MyDelegate myDelegate2 = new MyDelegate();
            ("Hello from instance method");

            ();

            //2.Registering static methods
            MyDelegate myDelegate = ;
            ("Hello from static method");

            ();

        }
    }

    public delegate void MyDelegate(string message);

    public class MyClass
    {
        public static void StaticMethod(string message)
        {
            ("Static Method: " + message);
        }

        public void InstanceMethod(string message)
        {
            ("Instance Method: " + message);
        }
    }
            ("Hello from instance method");
00007ff9`521a19bd 488b4df0 mov rcx,qword ptr [rbp-10h]
00007ff9`521a19c1 48baa0040000c7010000 mov rdx,1C7000004A0h ("Hello from instance method")
00007ff9`521a19cb 488b4908 mov rcx,qword ptr [rcx+8]
00007ff9`521a19cf 488b45f0 mov rax,qword ptr [rbp-10h]
00007ff9`521a19d3 ff5018 call qword ptr [rax+18h] //recount (e.g. results of election)
00007ff9`521a19d6 90 nop
            ("Hello from static method");
00007ff9`521a1a54 488b4de8 mov rcx,qword ptr [rbp-18h]
00007ff9`521a1a58 48baf0040000c7010000 mov rdx,1C7000004F0h ("Hello from static method")
00007ff9`521a1a62 488b4908 mov rcx,qword ptr [rcx+8]
00007ff9`521a1a66 488b45e8 mov rax,qword ptr [rbp-18h]
00007ff9`521a1a6a ff5018 call qword ptr [rax+18h] //recount (e.g. results of election)
00007ff9`521a1a6d 90 nop

As you can see, both static and instance point to the address offset of rax+18h. So where exactly does +18 point to?
image

The essence of Invoke is to call the function pointer where _methodPtr is located

So one might ask, didn't the source code say earlier. The entrance to the static method is not _methodPtrAux? How to become _methodPtr.
In fact, if it is a static delegate. the JIT generates a staked method, and the staked method is called internally with the contents of the +20 offset. Thus calling _methodPtrAux

The difference between the instance and static core code, if you are interested, you can look at their compilations

  1. Instance Method Core Code
private void CtorClosed(object target, nint methodPtr)
{
	if (target == null)
	{
		ThrowNullThisInDelegateToInstance();
	}
	_target = target;
	_methodPtr = methodPtr;//_methodPtrReally carries function pointers
}

  1. Static Methods Core Code
private void CtorOpened(object target, nint methodPtr, nint shuffleThunk)
{
	_target = this;
	_methodPtr = shuffleThunk;//_methodPtrIt's just a stake function.
	_methodPtrAux = methodPtr;//The real pointer is in the_methodPtrAuxcenter
}

How does delegation support type safety?

Click to view code
    internal class Program
    {
        static void Main(string[] args)
        {
            //1. Compiler-level errors
            //var myDelegate = new MyDelegate();

            //2. Runtime layer type conversion error
            var myDelegate = new MyDelegate();
            MyMaxDelegate myMaxDelegate = (MyMaxDelegate)(object)myDelegate;

            ();
        }

        public delegate void MyDelegate(string message);
        public delegate int MyMaxDelegate(int a, int b);


    }
  1. The compiler layer intercepts the
    This is simple, in the compiler if the definitions don't match it will report an error.
    image

  2. The CLR Runtime inserts check commands into the assembly
    Checking for inconsistencies will report an error without the whole program running down.
    image

How does delegation support multicasting?

image

Adding a Multicast Delegation

Delegates use += or to add a new delegate. The underlying call is CombineImpl, implemented by the subclass MulticastDelegate.
and ultimately produce aNew commissions

for loops the Combine delegate 1000 times, which produces 1000 objects.

		//simplified version
        protected sealed override Delegate CombineImpl(Delegate? follow)
        {
            MulticastDelegate dFollow = (MulticastDelegate)follow;
            object[]? resultList;
            int followCount = 1;
            object[]? followList = dFollow._invocationList as object[];
            if (followList != null)
                followCount = (int)dFollow._invocationCfollowListount;

            int resultCount;
			            if (!(_invocationList is object[] invocationList))
            {
                resultCount = 1 + followCount;
                resultList = new object[resultCount];
                resultList[0] = this;
                if (followList == null)
                {
                    resultList[1] = dFollow;
                }
                else
                {
                    for (int i = 0; i < followCount; i++)
                        resultList[1 + i] = followList[i];
                }
                return NewMulticastDelegate(resultList, resultCount);
            }
			//xxxxxxxxxx
        }
		//Critical core,Combine the combinedDelegateCompose a new object,and fillinvocationList,invocationCount
        private MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready)
        {
            // First, allocate a new multicast delegate just like this one, . same type as the this object
            MulticastDelegate result = (MulticastDelegate)InternalAllocLike(this);

            // Performance optimization - if this already points to a true multicast delegate,
            // copy _methodPtr and _methodPtrAux fields rather than calling into the EE to get them
            if (thisIsMultiCastAlready)
            {
                result._methodPtr = this._methodPtr;
                result._methodPtrAux = this._methodPtrAux;
            }
            else
            {
                result._methodPtr = GetMulticastInvoke();
                result._methodPtrAux = GetInvokeMethod();
            }
            result._target = result;
            result._invocationList = invocationList;
            result._invocationCount = invocationCount;

            return result;
        }

Execution of Multicast Delegation

As mentioned above, the essence of Invoke is to call the function pointer where _methodPtr is located.
Naturally, then, the person responsible for executing the multicast must be _methodPtr.
From the source code above, we know that MulticastDelegate has to call GetMulticastInvoke() once during initialization, let's see what it is?
image

Oh exempt, it is also an unmanaged method, interested students can check the c++ source code of coreclr by themselves. The mystery is in it, I am limited, I am afraid of misleading people.

The short answer is that the _methodPtr method is at the bottom of coreclr, and the for loop executes the invocationList's delegate queue.

Thinking about a question, what if it's just a simple for loop and one of the delegates gets stuck/fails to execute?
Tip: There are many override methods in the MulticastDelegate class.

Unmanaged delegates (function pointers)

C#, as a superset of C++, is also alias C++++ . It can also be said to be the manual gear of C++ (JAVA is the automatic gear of C++).
Naturally, what C++ has, C# has to have too. So in C# 11, function pointers were introduced, which are more powerful while inheriting all the drawbacks of C++ (except for assisting with type-safety checking during compilation).
/zh-cn/dotnet/csharp/language-reference/unsafe-code#function-pointers

generalized delegation

In order to reduce your workload and avoid creating too many delegate definitions. the BCL provides Action/Func to facilitate this and reduce the amount of code you have to work with.
image

They are not different from delegates at the bottom level.
image

Lambd expression

Generic delegates are the cornerstone of Lambd, and underneath they are still delegates.

            var list = new List<string>();
            var w= (x =>  > 100);

image

The relationship between events and delegates

The CLR event model is based on delegates and the relationship between them. Like a further encapsulation of delegates.

  1. An event is a syntactic sugar that has no new concepts of its own
  2. The relationship between a delegate and an event is equivalent to the relationship between a field and an attribute.

image
events as syntactic sugar, IL generates an underlyingcommissionAnd provide add_xxxx and remove_xxxx methods to encapsulate the delegate.
image
In fact, at the bottom, it's the same thing.