I was curious in what ways the compiler handled lambdas declared in loops. I’ve had a few cases where I’ve declared lambdas inline so that I can use variables local to the calling method within the lambda. I’ve included two examples below.
Will the compiler create N delegates for a lambda within a loop? What (and which) optimizations can the compiler make in such cases? Additionally, MSDN mentions to use lambdas over anonymous functions, but doesn’t go into depth as to why. So, why?
Example 1:
public static class MethodPlayground1
{
public static void TestMethod()
{
for (int i = 1; i <= 12; i++)
{
int methodLocal1 = i;
string methodLocal2 = i.ToString();
KeyValuePair<int, string> methodLocal3 = new KeyValuePair<int, string>(i, i.ToString());
// Case A
ThreadPool.QueueUserWorkItem((state) =>
{
int threadLocal1 = methodLocal1 + 1;
string threadLocal2 = methodLocal2 + " o'clock";
int threadLocal3 = methodLocal3.Key + 2;
string threadLocal4 = methodLocal3.Value + " oranges";
});
// Case B
ThreadPool.QueueUserWorkItem(delegate(object state)
{
int threadLocal1 = methodLocal1 + 1;
string threadLocal2 = methodLocal2 + " o'clock";
int threadLocal3 = methodLocal3.Key + 2;
string threadLocal4 = methodLocal3.Value + " oranges";
});
// Case C
ThreadPool.QueueUserWorkItem((state) =>
{
int threadLocal1 = methodLocal1 + 1;
string threadLocal2 = methodLocal2 + " o'clock";
int threadLocal3 = methodLocal3.Key + 2;
string threadLocal4 = methodLocal3.Value + " oranges";
});
// Case D
ThreadPool.QueueUserWorkItem(delegate(object state)
{
int threadLocal1 = methodLocal1 + 1;
string threadLocal2 = methodLocal2 + " o'clock";
int threadLocal3 = methodLocal3.Key + 2;
string threadLocal4 = methodLocal3.Value + " oranges";
});
// Case E
ThreadPool.QueueUserWorkItem(AsyncMethod, new object[] { methodLocal1, methodLocal2, methodLocal3 });
}
}
private static void AsyncMethod(object state)
{
object[] methodArgs = (object[])state;
int methodArg1 = (int)methodArgs[0];
string methodArg2 = (string)methodArgs[1];
KeyValuePair<int, string> methodArg3 = (KeyValuePair<int, string>)methodArgs[2];
int threadLocal1 = methodArg1 + 1;
string threadLocal2 = methodArg2 + " o'clock";
int threadLocal3 = methodArg3.Key + 2;
string threadLocal4 = methodArg3.Value + " oranges";
}
}
Example 2:
public static class MethodPlayground2
{
private static int PriorityNumber;
public static void TestMethod()
{
List<int> testList = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7 });
for (int i = 1; i <= 7; i++)
{
// Case A
testList.Sort((i1, i2) =>
{
if ((i1 == i) || (i2 == i))
{
if (i1 == i2) return 0;
else if (i1 == i) return -1;
else return 1;
}
else return i1.CompareTo(i2);
});
// Case B
testList.Sort(delegate(int i1, int i2)
{
if ((i1 == i) || (i2 == i))
{
if (i1 == i2) return 0;
else if (i1 == i) return -1;
else return 1;
}
else return i1.CompareTo(i2);
});
PriorityNumber = i;
// Case C
testList.Sort(IntCompareWithPriority1);
// Case D
testList.Sort(IntCompareWithPriority2);
// Case E
testList.Sort(IntCompareWithPriority3);
}
}
private static Comparison<int> IntCompareWithPriority1 = (i1, i2) =>
{
if ((i1 == PriorityNumber) || (i2 == PriorityNumber))
{
if (i1 == i2) return 0;
else if (i1 == PriorityNumber) return -1;
else return 1;
}
else return i1.CompareTo(i2);
};
private static Comparison<int> IntCompareWithPriority2 = delegate(int i1, int i2)
{
if ((i1 == PriorityNumber) || (i2 == PriorityNumber))
{
if (i1 == i2) return 0;
else if (i1 == PriorityNumber) return -1;
else return 1;
}
else return i1.CompareTo(i2);
};
private static int IntCompareWithPriority3(int i1, int i2)
{
if ((i1 == PriorityNumber) || (i2 == PriorityNumber))
{
if (i1 == i2) return 0;
else if (i1 == PriorityNumber) return -1;
else return 1;
}
else return i1.CompareTo(i2);
}
}
It seems that I’m not accomplishing much in Example 1. Using lambdas is a little easier to write, since I don’t have to do any casting. However, any of the method local variables that I want to pass into the delegate I can do so through the object state parameter.
In Example 2, it seems much cleaner to use the method local variables, as opposed to setting a static variable, especially since the static variable implementation doesn’t seem it would be thread-safe.
I’m looking for an answer as to the best use of lambda functions and which of the Cases are best optimized in each of the Examples above.
In your first case, the only benefit that you get using lambdas/anonymous methods over a method is that you avoid the casting between
objectand the type that you want to use; if you are relying heavily on value-types, then you’ll incur boxing every time that method (with a parameter of typeobject) is called.Normally, that’s not a big deal, but if your dealing with really high counts of the callback, it can start having an impact.
Lambdas (that are not
Expression<T>)/anonymous methods and a delegate that points to a method are no different; the C# compiler compiles the lambda/anonymous function into a method on a class that your code doesn’t see and then wires the delegate up to that.The MSDN page you link to states (emphasis mine):
Because they mention “inline code”, lambdas provide the most succinct way of writing code; since it’s inline, most would naturally want to have the the most brief representation possible. After all, what’s more readable, this:
Or this?
It’s more typing in the second case than in the first for the exact same thing. Additionally, if the method signature that took the delegate ever changed from a delegate to an
Expression<T>(presumably, to do some analysis of the lambda for some benefit), then code that calls that with a lambda would still compile, while the code that uses a delegate or anonymous function would not.Note, there is also only one method that is generated for the lambda/anonymous delegate. It doesn’t create multiple methods depending on where the variable is declared. What the compiler ends up doing is creating a method with a wider scope.
Using your (modified) example:
The method that the compiler creates will close over the loop, so that you might get repeated values for
i(depending on when theThreadPoolpicks up the call).However, when you assign the variable inside the loop, the compiler is smart enough to know it only needs the code inside, like so:
In the above, each callback to the
ThreadPoolwill print out a distinct value.It should be noted that in C# 5.0, there is a breaking change to this behavior for the
foreachstatement, but not theforstatement.Many developers don’t understand the impact of the closure over the loop, which is the primary reason for the change.