Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closures - Difference between capturing variables and reading them as arguments

Let's say we have this class:

// Provides deferred behaviour
public class Command<TResult>
{
     private Func<object[], TResult> _executeFunction;
     private object[] _args;

     public Command(Func<object[], TResult> execution, params object[] arguments)
     {
          _executeFunction = execution;
          _args = arguments;
     }

     public TResult Execute()
     {
          return _executeFunction(_args);
     }
}

What's the difference between these two anonymous functions?

int a = 1;
int b = 4;

// a and b are passed in as arguments to the function
Command<int> sum = new Command<int>(args => (int)args[0] + (int)args[1], a, b);

// a and b are captured by the function
Command<int> sum2 = new Command<int>(_ => a + b);

Console.WriteLine(sum.Execute()); //Prints out 5
Console.WriteLine(sum2.Execute()); //Prints out 5

I'm specifically looking for performance differences.

Also, we know that if some class is holding a reference of sum2, then a and b will live beyond the scope they were defined on, probably never getting collected by the GC if the function is still referenced anywhere.

Does the same happen with sum? (Considering the arguments are reference types and not value types as in this example)

like image 485
Matias Cicero Avatar asked Nov 08 '22 23:11

Matias Cicero


1 Answers

When you pass in the variables a and b, you are doing just that. The value of 1 and 4 are passed in respectively. However, when you refer to a and b within the context (or scope) of the lambda expression the values are "captured". The variables a and b, within the scope of the lambda expression are treated as references to the originals outside of the scope, meaning that if they are changed within the scope of the lambda -- so too are the originals. When compiled into IL, they reside in a class where the instances are shared.

static void Main()
{
    int a = 1;
    int b = 4;

    // a and b are passed in as arguments to the function
    var sum = new Command<int>(args => (int)args[0] + (int)args[1], a, b);

    // a and b are captured by the function
    var sum2 = new Command<int>(_ =>
    {
        var c = a + b;

        a++;
        b++;

        return c;
    });

    Console.WriteLine(sum.Execute()); //Prints out 5
    Console.WriteLine(sum2.Execute()); //Prints out 5

    Console.WriteLine("a = " + a); // Prints 2
    Console.WriteLine("b = " + b); // Prints 5

    Console.ReadLine();
}

There is really small difference in the IL, and I do not believe there is any performance implications worth avoiding one or the other. I would usually prefer the usage of lambda expressions for their readability.

A look at some of the IL generated from some C# lambdas.

like image 180
David Pine Avatar answered Nov 14 '22 22:11

David Pine