Lambda使用局部变量编译行为

时间:2012-03-26 14:37:14

标签: .net c#-4.0 lambda

我很好奇编译器处理循环中声明的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函数最佳使用的答案,以及哪些案例在上述每个例子中都得到了最佳优化。

1 个答案:

答案 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的每个回调都会打印出一个不同的值。

应该注意in C# 5.0, there is a breaking change to this behavior for the foreach statement, but not the for statement

许多开发人员不了解闭包对循环的影响,这是更改的主要原因。