7

If I have a method like this

void SomeMethod () {
    Func<A,B> f =  a => /*Some code*/;
    ...
    b = f (a);
}

is f created every time SomeMethod is called? I mean, does that line take time to compute or does the compiler store the function somewhere on compile time skips it at execution?

2
  • 2
    A Func is an object and the body is "stored" in the same way a normal method is (so it is not "recomputed"). Or is this a question about if f, on subsequent invocations of SomeMethod, will be "reference equals"? (I believe it will be, but do not have a reference off-hand.) Commented Jul 15, 2014 at 20:54
  • @user2864740 If you compare the f of one call with the f from a later call, they will be different as references and instances, so ReferenceEquals will be false, but they will be equal according to the relevant override of Equals. The last fact is required by the C# Specification. Also note that operator == is overloaded here. Commented Jul 15, 2014 at 21:28

2 Answers 2

5

Consider this simple example:

static void Main(string[] args)
{
     Test();
     Test();
}
static void Test()
{
     Func<int, int> f = a => a * a;
     int b = f(2);
     Console.WriteLine(b);
}

The created method is:

enter image description here

And the IL code for <Test>b__0:

.method private hidebysig static int32  '<Test>b__0'(int32 a) cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       8 (0x8)
  .maxstack  2
  .locals init ([0] int32 CS$1$0000)
  IL_0000:  ldarg.0
  IL_0001:  ldarg.0
  IL_0002:  mul
  IL_0003:  stloc.0
  IL_0004:  br.s       IL_0006
  IL_0006:  ldloc.0
  IL_0007:  ret
} // end of method Program::'<Test>b__0'

IL code for Test method:

    .method private hidebysig static void  Test() cil managed
{
  // Code size       49 (0x31)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Func`2<int32,int32> f,
           [1] int32 b)
  IL_0000:  nop
  IL_0001:  ldsfld     class [mscorlib]System.Func`2<int32,int32> ConsoleApplication10.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  IL_0006:  brtrue.s   IL_001b
  IL_0008:  ldnull
  IL_0009:  ldftn      int32 ConsoleApplication10.Program::'<Test>b__0'(int32)
  IL_000f:  newobj     instance void class [mscorlib]System.Func`2<int32,int32>::.ctor(object,
                                                                                       native int)
  IL_0014:  stsfld     class [mscorlib]System.Func`2<int32,int32> ConsoleApplication10.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  IL_0019:  br.s       IL_001b
  IL_001b:  ldsfld     class [mscorlib]System.Func`2<int32,int32> ConsoleApplication10.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  IL_0020:  stloc.0
  IL_0021:  ldloc.0
  IL_0022:  ldc.i4.2
  IL_0023:  callvirt   instance !1 class [mscorlib]System.Func`2<int32,int32>::Invoke(!0)
  IL_0028:  stloc.1
  IL_0029:  ldloc.1
  IL_002a:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_002f:  nop
  IL_0030:  ret
} // end of method Program::Test

Every time you call Test method, the the cached anonymous delegate (which is Func<int,int>) is loaded, then the Invoke method is called.

So the answer is, anonymous method (<Test>b__0) is created only once by the compiler, it's not created every time you call the function.

Sign up to request clarification or add additional context in comments.

2 Comments

Exactly, the static method '<Test>b__0' from the IL is what I called StrangeName in my answer.
If some variables are trapped by closure, how does the compiler deal with this?
4

The C# compiler creates an ordinary (non-anonymous) method from the lambda arrow, then the delegate f will have that "generated" method on its invocation list.

Each time the SomeMethod runs, a new instance of Func<,> delegate is created which refers the ordinary (but generated) method.

It is just like:

void SomeMethod () {
    Func<A, B> f = StrangeName;
    ...
    b = f(a);
}

static B StrangeName(A a) {
    /*Some code*/
}

2 Comments

If some variables are trapped by closure, how does the compiler deal with this?
@CristianGarcia Excellent comment. If those variables are fields, they will be accessed by \*Some code*/. Maybe some instance references will need to be passed to StrangeName, or StrangeName might be made non-static. If those variables are local variables, they will be promoted to fields by the compiler, either fields in the same class or fields in a new generated class, let's call it StrangeNameClass. The details depend on just what variables are "trapped", and on implementation choices by the C# compiler authors.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.