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.
- 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 - Only static methods are supported
Only static methods are supported, not instance methods (which can only be bypassed through evil ways) - 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
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?
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
- Instance Method Core Code
private void CtorClosed(object target, nint methodPtr)
{
if (target == null)
{
ThrowNullThisInDelegateToInstance();
}
_target = target;
_methodPtr = methodPtr;//_methodPtrReally carries function pointers
}
- 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);
}
-
The compiler layer intercepts the
This is simple, in the compiler if the definitions don't match it will report an error. -
The CLR Runtime inserts check commands into the assembly
Checking for inconsistencies will report an error without the whole program running down.
How does delegation support multicasting?
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?
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.
They are not different from delegates at the bottom level.
Lambd expression
Generic delegates are the cornerstone of Lambd, and underneath they are still delegates.
var list = new List<string>();
var w= (x => > 100);
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.
- An event is a syntactic sugar that has no new concepts of its own
- The relationship between a delegate and an event is equivalent to the relationship between a field and an attribute.
events as syntactic sugar, IL generates an underlyingcommissionAnd provide add_xxxx and remove_xxxx methods to encapsulate the delegate.
In fact, at the bottom, it's the same thing.