Are delegates lighter than classes?

I tried to parse the generated C # executable, but I could not come to a conclusion. I would like to know that if there are really special entities for C # CLR delegates or just a compiler sackler?

I ask this because I am implementing a language that compiles in C #, and it would be much more interesting for me to compile anonymous functions as classes than as delegates. But I don’t want to use a design that I will regret later as they can be heavier in memory (I think Java PermGen bases my survey. Although I know that the CLR does not exist).

Thanks!

- change

to be a little more clear, I would like to know if there are (and what) differences between:

void Main() { Func<int, int, int> add = delegate(int a, int b) {return a + b;}; } 

and for example

 class AnonFuncion__1219023 : Fun3 { public override int invoke(int a, int b) { return a + b; } } 

- edit

I think there may be a big difference between:

 class Program { static int add(int a, int b) { return a + b; } static void Main() { Func<int, int, int> add = Program.add; } } 

and

 class Function__432892 : Fun3 { public override int invoke(int a, int b) { return Program.add(a, b); } } 

I read somewhere that the syntax is Func<int, int, int> add = Program.add; - this is only sugar for Func<int, int, int> add = delegate(int a, int b) { return Program.add; }; Func<int, int, int> add = delegate(int a, int b) { return Program.add; }; . But I really don't know if this is true. I could also see that the C # compiler already caches all of these instances, so they are built only once. I could do the same with my compiler.

+8
c # clr internals delegates il
source share
5 answers

I am surprised that you could not come to a conclusion by parsing the executable file. Let's take a look at something very simple:

  using System; class A { int _x; public A(int x) { _x = x; } public void Print(int y) { Console.WriteLine(_x + y); } } interface IPseudoDelegateVoidInt { void Call(int y); } class PseudoDelegateAPrint : IPseudoDelegateVoidInt { A _target; public PseudoDelegateAPrint(A target) { _target = target; } public void Call(int y) { _target.Print(y); } } class Program { delegate void RealVoidIntDelegate(int x); static void Main() { A a = new A(5); IPseudoDelegateVoidInt pdelegate = new PseudoDelegateAPrint(a); RealVoidIntDelegate rdelegate = new RealVoidIntDelegate(a.Print); pdelegate.Call(2); rdelegate(2); } } 

If we make it out, we get

  // Microsoft (R) .NET Framework IL Disassembler. Version 4.0.30319.1 // Copyright (c) Microsoft Corporation. All rights reserved. // Metadata version: v4.0.30319 .assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 4:0:0:0 } .assembly del { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. .hash algorithm 0x00008004 .ver 0:0:0:0 } .module del.exe // MVID: {87A2A843-A5F2-4D40-A96D-9940579DE26E} .imagebase 0x00400000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000001 // ILONLY // Image base: 0x0000000000B60000 // =============== CLASS MEMBERS DECLARATION =================== .class private auto ansi beforefieldinit A extends [mscorlib]System.Object { .field private int32 _x .method public hidebysig specialname rtspecialname instance void .ctor(int32 x) cil managed { // Code size 17 (0x11) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: nop IL_0008: ldarg.0 IL_0009: ldarg.1 IL_000a: stfld int32 A::_x IL_000f: nop IL_0010: ret } // end of method A::.ctor .method public hidebysig instance void Print(int32 y) cil managed { // Code size 16 (0x10) .maxstack 8 IL_0000: nop IL_0001: ldarg.0 IL_0002: ldfld int32 A::_x IL_0007: ldarg.1 IL_0008: add IL_0009: call void [mscorlib]System.Console::WriteLine(int32) IL_000e: nop IL_000f: ret } // end of method A::Print } // end of class A .class interface private abstract auto ansi IPseudoDelegateVoidInt { .method public hidebysig newslot abstract virtual instance void Call(int32 y) cil managed { } // end of method IPseudoDelegateVoidInt::Call } // end of class IPseudoDelegateVoidInt .class private auto ansi beforefieldinit PseudoDelegateAPrint extends [mscorlib]System.Object implements IPseudoDelegateVoidInt { .field private class A _target .method public hidebysig specialname rtspecialname instance void .ctor(class A target) cil managed { // Code size 17 (0x11) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: nop IL_0008: ldarg.0 IL_0009: ldarg.1 IL_000a: stfld class A PseudoDelegateAPrint::_target IL_000f: nop IL_0010: ret } // end of method PseudoDelegateAPrint::.ctor .method public hidebysig newslot virtual final instance void Call(int32 y) cil managed { // Code size 15 (0xf) .maxstack 8 IL_0000: nop IL_0001: ldarg.0 IL_0002: ldfld class A PseudoDelegateAPrint::_target IL_0007: ldarg.1 IL_0008: callvirt instance void A::Print(int32) IL_000d: nop IL_000e: ret } // end of method PseudoDelegateAPrint::Call } // end of class PseudoDelegateAPrint .class private auto ansi beforefieldinit Program extends [mscorlib]System.Object { .class auto ansi sealed nested private RealVoidIntDelegate extends [mscorlib]System.MulticastDelegate { .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed { } // end of method RealVoidIntDelegate::.ctor .method public hidebysig newslot virtual instance void Invoke(int32 x) runtime managed { } // end of method RealVoidIntDelegate::Invoke .method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(int32 x, class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed { } // end of method RealVoidIntDelegate::BeginInvoke .method public hidebysig newslot virtual instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed { } // end of method RealVoidIntDelegate::EndInvoke } // end of class RealVoidIntDelegate .method private hidebysig static void Main() cil managed { .entrypoint // Code size 45 (0x2d) .maxstack 3 .locals init (class A V_0, class IPseudoDelegateVoidInt V_1, class Program/RealVoidIntDelegate V_2) IL_0000: nop IL_0001: ldc.i4.5 IL_0002: newobj instance void A::.ctor(int32) IL_0007: stloc.0 IL_0008: ldloc.0 IL_0009: newobj instance void PseudoDelegateAPrint::.ctor(class A) IL_000e: stloc.1 IL_000f: ldloc.0 IL_0010: ldftn instance void A::Print(int32) IL_0016: newobj instance void Program/RealVoidIntDelegate::.ctor(object, native int) IL_001b: stloc.2 IL_001c: ldloc.1 IL_001d: ldc.i4.2 IL_001e: callvirt instance void IPseudoDelegateVoidInt::Call(int32) IL_0023: nop IL_0024: ldloc.2 IL_0025: ldc.i4.2 IL_0026: callvirt instance void Program/RealVoidIntDelegate::Invoke(int32) IL_002b: nop IL_002c: ret } // end of method Program::Main .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method Program::.ctor } // end of class Program // ============================================================= // *********** DISASSEMBLY COMPLETE *********************** // WARNING: Created Win32 resource file C:\Users\logan\del.res 

As you can see, RealVoidIntDelegate becomes “just” another class. They called it Invoke instead of Call , and they did not use the interface, but there were no special instructions, this is the same basic idea.

To gain a little understanding of the idea of ​​“lightness”, delegates do not have less weight than classes, because this delegate is a class. Especially in the case of functional alphabetic syntax, they really cannot be much easier. However, to “call this method on this object with these arguments” is the case where calling and creating this delegate is likely to be easier and then an extended delegate at home when compiling to C #.

+7
source share

Delegates are classes ... the .NET compiler creates a type for each delegate, and the code must create an instance of the delegate.

In any case, the difference in performance will be insignificant if you do not write one of the very rare applications where every second of nano is important.

+3
source share

There is no great secret. Guide John Skeet ...

If you look at 6.5.3 C # Specification , you'll see some examples of how anonymous delegate handlers are handled by the compiler. Short description:

IF anonymous method does not capture external variables,
THEN , it can be created as a static method for the enclosing type.

An IF anonymous method refers to elements of the enclosing type, for example. this.x
THEN it can be created as an instance method for an application type.

IF anonymous method captures a local variable,
THEN it can be created as a nested type inside the incoming type, with instance variables that match the captured variables, and an instance method that matches the delegate type.

The last example is more complex, and the easiest way to look at the code. Look at yourself, and I think that will eliminate all guesses.

+3
source share

It seems that the difference between them is very small, the upper fragment is effectively compiled into something almost identical to what you have in the lower fragment.

+1
source share

Other than metadata, there really is no such thing as a class when everything will be JIT compiled. The representation of the execution time of an object is an array of bytes large enough to store all the fields of the class and these are the base classes, plus the header of the object that stores the hash code, an identifier of a numeric type and a pointer to a common v-table that contains addresses of redefinitions of virtual functions.

A delegate is an object whose class comes from System.Delegate. It stores an array of pairs of pointers to objects and functions.

An anonymous function is a function that does not have a name. However, they are also usually associated with an object called a closure, which contains all the parameters and locales defined in the containing method that created the "anonymous delegate", which points to an anonymous function. (In fact, it usually contains only those variables that are actually available).

In any case, moving stack variables to the heap allows an anonymous delegate to live in it, defining the stack frame.

The easiest way to associate an anonymous function with a closure is to make it a method of the closure class generated by the compiler.

So, if you have lexical closures, you should (at least in some cases) use the class to implement anonymous functions.

If you do not have lexical closures, you can simply "anonymous function" as a regular function with the name generated by the compiler, next to the method declared by it. This is also a useful optimization in a shell where the language supports lexical closures, but one is not needed.

In general, anonymous functions are not useful without support for closures, so I would not include them in your language without support for closures.

+1
source share

All Articles