Delegates in .NET: how are they constructed?
Asked Answered
H

3

14

While inspecting delegates in C# and .NET in general, I noticed some interesting facts:

Creating a delegate in C# creates a class derived from MulticastDelegate with a constructor:

.method public hidebysig specialname rtspecialname instance 
   void .ctor(object 'object', native int 'method') runtime managed { }

Meaning that it expects the instance and a pointer to the method. Yet the syntax of constructing a delegate in C# suggests that it has a constructor

new MyDelegate(int () target)

where I can recognise int () as a function instance (int *target() would be a function pointer in C++). So obviously the C# compiler picks out the correct method from the method group defined by the function name and constructs the delegate. So the first question would be, where does the C# compiler (or Visual Studio, to be precise) pick this constructor signature from ? I did not notice any special attributes or something that would make a distinction. Is this some sort of compiler/visualstudio magic ? If not, is the T (args) target construction valid in C# ? I did not manage to get anything with it to compile, e.g.:

int () target = MyMethod;

is invalid, so is doing anything with MyMetod, e.g. calling .ToString() on it (well this does make some sense, since that is technically a method group, but I imagine it should be possible to explicitly pick out a method by casting, e.g. (int())MyFunction. So is all of this purely compiler magic ? Looking at the construction through reflector reveals yet another syntax:

Func CS$1$0000 = new Func(null, (IntPtr) Foo);

This is consistent with the disassembled constructor signature, yet this does not compile!

One final interesting note is that the classes Delegate and MulticastDelegate have yet another sets of constructors:

.method family hidebysig specialname rtspecialname instance void .ctor(class System.Type target, string 'method') cil managed

Where does the transition from an instance and method pointer to a type and a string method name occur ? Can this be explained by the runtime managed keywords in the custom delegate constructor signature, i.e. does the runtime do it's job here ?

EDIT: ok, so I guess I should reformulate what I wanted to say by this question. Basically I'm suggesting that there's not only C# compiler / CLR magic involved in delegate construction, but also some Visual Studio magic, since Intellisense flips out some new syntax when suggesting the constructor arguments and even hides one of them (e.g. Reflector does not use this syntax and construtor, for that matter).

I was wondering whether this assertion is true, and whether the function instance syntax has some deeper meaning in C# or is it just some constant format implemented by the Visual Studio magic part for clarity (which makes sense, since it looks like invalid C#) ? In short, if I was implementing Intellisense, should I do some magic for delegates or could I construct the suggestion by some clever mechanism ?

FINAL EDIT: so, the popular consensus is that that's indeed VS magic. Seeing other examples (see Marc Gravell's comment) of such VS behavior convinces me that that is the case.

Holbrook answered 14/3, 2010 at 20:57 Comment(3)
CS$1$0000 is decompiler generated, and does never compile.Banquer
I know, the name is not the point here, it's the new construction that does not compile.Holbrook
Yah, just wanted to point out.Banquer
H
12

The first argument is resolved from the object reference (or null for static methods); no magic there.

Re the second argument, however - it is an unmanaged pointer (native int); in short, there is no alternative direct C# syntax that can use this constructor - it uses a specific IL instruction (ldftn) to resolve the function from metadata. However, you can use Delegate.CreateDelegate to create delegates via reflection. You can also use IL emit (DynamicMethod etc), but it isn't fun.

Highoctane answered 14/3, 2010 at 21:3 Comment(6)
OK, so method pointers are unsupported by C#, but still, where does the "function instance"-like syntax come from ? I understand going from a two parameter constructor to a single parameter c-tor as compiler magic, but where does visual studio get the idea of suggesting a single parameter constructor from (I can't find it anywhere else) ?Holbrook
@Saulius - because that is what the language spec demands; it doesn't talk about the constructors, but it states that the syntax is (for example) new EventHandler(someObject.SomeMethod); - I count one "input" to that, even if the compiler works some magic on it to generate the IL.Highoctane
@Marc Gravell: so how about my assertion about Visual Studio magic (look at the edit of the post) ? This part is the one that's bugging me - just out of curiosity, of course :)Holbrook
Intellisense actually doesn't know anything about the generated IL. It's using the symbol database and any available XML documentation comments to understand the symbols it finds in the program. When you encounter a delegate parameter, Intellisense knows about the specific delegate definition, so it can display an appropriate helpful prompt. This is a Visual Studio feature, which is independent from the convenience features the compiler provides to make delegates easier to use.Snider
@Dan Bryant: Visual studio does not need the documentation xml file for the signatures, only for the comments - I'm assuming that the signatures come from reflecting on the assembly, since that's all I have to add to a project to use intellisense on it. And since I always looked at the system assemblies as ordinary assemblies, I'm a little surprised to find this inconsistency.Holbrook
@Saulius - re the edit; I've never looked that deeply at it (the VS treatment) to be honest. I assume it is a bit of a special case, the same as hiding the IEnumerable<char> extension methods on string (even though they compile just fine).Highoctane
L
0

You first define the delegate (this is how Visual Studio knows the signature of the target method):

delegate void MyDelegate();

Then you construct delegate instances like this:

MyDelegate method = new MyDelegate({method name});

// If this was the method you wanted to invoke:
void MethodToInvoke()
{
    // do something
}

MyDelegate method = new MyDelegate(MethodToInvoke);

C# automatically picks the method matching the signature of the delegate.

Edit: When Visual Studio's Intellisense shows you the int () target suggestion, it is showing you the signature of C# methods you can use. The C# compiler translates the C# representation to IL. The IL implementation will look different, because IL is not a C style language, and the C# compiler provides syntactic sugar to abstract away the implementation details.

Lovins answered 14/3, 2010 at 21:2 Comment(4)
I think he means from a low-level perspective.Perithecium
@Saulius: The int () target in itself is not valid C#, but how else would they represent the target in Intellisense?Lovins
.. and that's the question - they have to show something, so are they just showing some made up syntax for clarity (simply visual studio magic just for delegates), or is it general has some deeper meaning ? :)Holbrook
@Saulius: I would assume that it's just a made up syntax for clarity.Lovins
B
0

This is just a guess, so don't shoot me if I'm wrong, but I think Intellisense is getting the signature for the target method from the Invoke method defined on the delegate. Reflector clearly shows that the Invoke method on System.Action<T> is:

[MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]
public virtual void Invoke(T obj);

Which is the same as the signature suggestion offered by Intellisense. The magic is Intellisense, when spotting a delegate type, looks at the Invoke method and suggests a constructor that takes a target that matches it.

Beefburger answered 14/3, 2010 at 22:13 Comment(2)
Yeah, the signature is that of the Invoke method, but the the syntax VS is showing is invalid C# !Holbrook
@Saulius: The tooltip displayed by VS shows the bare signature of the target method rather than all the extra info that would vary from target to target (like the parameter names and the accessibility modifier). As Marc pointed out, it's a special case where Intellisense spits out a bit of pseudo-code instead of what it reads from the assembly's metadata.Beefburger

© 2022 - 2024 — McMap. All rights reserved.