代码之家  ›  专栏  ›  技术社区  ›  kofucii

LINQ表达式中的值是否通过引用传递?

  •  3
  • kofucii  · 技术社区  · 14 年前

    我在读曼宁关于LINQ的书,有一个例子:

        static class QueryReuse
        {
           static double Square(double n)
           {
             Console.WriteLine("Computing Square("+n+")...");
             return Math.Pow(n, 2);
           }
           public static void Main()
           {
             int[] numbers = {1, 2, 3};
             var query =
                      from n in numbers
                      select Square(n);
    
             foreach (var n in query)
                  Console.WriteLine(n);
    
             for (int i = 0; i < numbers.Length; i++)
                  numbers[i] = numbers[i]+10;
    
             Console.WriteLine("- Collection updated -");
    
             foreach (var n in query)
                 Console.WriteLine(n);
        }
    }
    

    具有以下输出:

    Computing Square(1)...
    1
    Computing Square(2)...
    4
    Computing Square(3)...
    9
    - Collection updated -
    Computing Square(11)...
    121
    Computing Square(12)...
    144
    Computing Square(13)...
    169
    

    这是否意味着“数字”是通过引用传递的?这种行为与懒惰的执行和屈服有关吗?或者我走错了路?

    5 回复  |  直到 14 年前
        1
  •  7
  •   Jon Skeet    14 年前

    这与懒惰的执行有关。每次遍历查询时,都会看到 numbers 再一次。实际上,如果您更改 虽然 在执行查询时,也会看到这种变化。这一切都在改变世界 目录 数组的。

    请注意,查询会记住 但该值是一个引用,而不是 数组的。所以如果你改变 数字

    numbers = new int[] { 10, 9, 8, 7 };
    

    那就改变了 反映在查询中。

    更复杂的是,如果在查询的其他部分使用变量,如下所示:

    int x = 3;
    
    var query = from n in numbers
                where n == x
                select Square(n);
    

    变量 x 而不是它的价值。。。变化如此之大 将更改计算查询的结果。这是因为查询表达式实际上被转换为:

    var query = numbers.Where(n => n == x)
                       .Select(n => Square(n));
    

    数字 不是-这就是为什么他们的行为有点不同。

        2
  •  6
  •   Eamon Nerbonne    14 年前

    引用 numbers 价值

    那是什么意思?

    var arr = new[]{1,2,3,};
    var q = arr.Select(i=>i*2);
    Console.WriteLine(string.Join(", ",q.ToArray())); //prints 2, 4, 6
    arr[0]=-1;
    Console.WriteLine(string.Join(", ",q.ToArray())); //prints -2, 4, 6
    // q refers to the original array, but that array has changed.
    arr = new[]{2,3,4};
    Console.WriteLine(string.Join(", ",q.ToArray())); //prints -2, 4, 6
    //since q still refers to the original array, not the variable arr!
    

    一般来说,如果您更改变量而不是它们的底层对象,它会很快变得混乱,因此最好避免这样的更改。

    例如:

    var arr = new[]{1,2,};
    var arr2 = new[]{1,2,};
    var q = from a in arr
            from b in arr2
            select a*b;
    
    // q is 1,2,2,4
    arr = new[]{0,1}; //irrelevant, arr's reference was passed by value
    // q is still 1,2,2,4
    
    arr2 = new[]{0,1}; //unfortunately, relevant
    // q is now 0, 1, 0, 2
    

    要理解这一点,您需要了解编译过程的细节。查询表达式被定义为等价于扩展方法语法( arr.Select... )它大量使用闭包。因此,实际上只有第一个可枚举或可查询的引用通过值传递,其余的引用在闭包中捕获,这意味着它们的引用通过引用传递。困惑了吗? 避免这样更改变量以保持代码的可维护性和可读性。

        3
  •  3
  •   Mark Byers    14 年前

    当您请求查询的结果时,它将使用执行查询时的当前值(而不是创建查询时的值)来计算查询。如果对同一查询求值两次,那么如果基础数据发生了更改,则可以得到不同的结果,如问题中提供的示例所示。

        4
  •  0
  •   veggerby    14 年前

    这是因为 numbers 是一个 closure 结合枚举的延迟执行,它给出了这个结果。

        5
  •  -1
  •   Albin Sunnanbo    14 年前

    是的,那个 numbers