我很好奇编译器处理循环中声明的lambda的方式。我有几个案例,我已经将lambdas内联声明,以便我可以在lambda中使用调用方法的本地变量。我在下面列举了两个例子。
编译器是否会在循环中为lambda创建N个委托?在这种情况下,编译器可以进行哪些(和哪些)优化?此外,MSDN mentions to use lambdas over anonymous functions,但没有深入探讨原因。所以为什么?
示例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";
}
}
示例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);
}
}
在示例1中,我似乎没有做太多。使用lambdas更容易编写,因为我不需要做任何演员。但是,我希望传递给委托的任何方法局部变量都可以通过object state
参数完成。
在示例2中,使用方法局部变量似乎更清晰,而不是设置静态变量,特别是因为静态变量实现似乎不是线程安全的。
我正在寻找关于lambda函数最佳使用的答案,以及哪些案例在上述每个例子中都得到了最佳优化。
答案 0 :(得分:1)
在第一种情况下,使用lambdas / anonymous方法对方法的唯一好处是避免在object
和您要使用的类型之间进行转换;如果你在很大程度上依赖于值类型,那么每次调用该方法(类型为object
的参数)时都会产生拳击。
通常情况下,这不是什么大不了的事,但如果你处理的是非常高的回调数,它就会开始产生影响。
Lambda(不是Expression<T>
)/匿名方法和指向方法的委托没有区别; C#编译器将lambda / anonymous函数编译到代码看不到的类的方法中,然后将委托连接到那个。
MSDN page you link to州(强调我的):
lambda表达式取代匿名方法作为编写内联代码
的首选方式
因为他们提到“内联代码”,所以lambdas提供了编写代码最简洁的方式;因为它是内联的,所以大多数人自然希望能够获得最简短的表示。毕竟,更具可读性的是:
var query = myEnumerable.Where(x => x > 2);
还是这个?
var query = myEnumerable.Where(delegate(x) { return x > 2; });
第二种情况下输入的内容比完全相同的内容更多。另外,如果将委托的方法签名从委托更改为Expression<T>
(可能是为了某些好处而对lambda进行一些分析),那么使用lambda调用它的代码将仍然编译,而使用委托或匿名函数的代码不。
注意,还只为lambda / anonymous委托生成了一个方法。它不会创建多个方法,具体取决于声明变量的位置。编译器最终做的是创建一个具有更宽范围的方法。
使用您的(修改过的)示例:
public static void TestMethod()
{
// Scope of the lambda starts here.
for (int i = 1; i <= 12; i++)
{
// Case A
ThreadPool.QueueUserWorkItem((state) => {
Console.WriteLine(i);
});
}
// And ends here.
}
编译器创建的方法将关闭 over 循环,以便您可以获得i
的重复值(取决于ThreadPool
接听电话的时间)
但是,当您在循环中分配变量时,编译器足够聪明,知道它只需要 in 内的代码,如下所示:
public static void TestMethod()
{
for (int i = 1; i <= 12; i++)
{
// Scope of the lambda starts here.
// Create copy.
int copy = i;
// Case A
ThreadPool.QueueUserWorkItem((state) => {
Console.WriteLine(copy);
});
// And ends here.
}
}
在上文中,ThreadPool
的每个回调都会打印出一个不同的值。
许多开发人员不了解闭包对循环的影响,这是更改的主要原因。