LINQ查询如何处理对象的引用有什么特别之处吗?

时间:2018-02-08 09:46:40

标签: c# linq reference

我对C#(来自C ++背景)和LINQ非常陌生。我读过,每次迭代时LINQ查询都会重新执行,并且除非底层数据发生变化,否则它总是会产生相同的输出。所以我决定用下面的代码测试一下。更改数组中的单个元素(#2)按预期工作,但更改基础对象(#4)不会:

    static void Main()
    {
        int[] scores = new int[] { 97, 92, 81, 60 };

        /* *** NEW CODE *** 

        Func<int[]> funcScores = () => scores;
        Console.WriteLine(funcScores()[0]); // 97 as expected
        scores[0] = 200;
        Console.WriteLine(funcScores()[0]); // 200 as expected
        scores = new int[] { 1000, 2000, 3000 };
        Console.WriteLine(funcScores()[0]); // 1000 as expected

        */

        IEnumerable<int> scoreQuery = GetScores(scores);
        //IEnumerable<int> scoreQuery = GetScores(ref scores); #8

        foreach (int i in scoreQuery)
        {
            Console.Write(i + " "); // #1 outputs 97, 92, 81 as expected
        }

        scores[2] = 343; // #2 change underlying data

        Console.WriteLine("\n");
        foreach (int i in scoreQuery)
        {
            Console.Write(i + " "); // #3 outputs 97, 92, 343 as expected
        }

        scores = new int[] { 100, 200, 3, 5, 6 }; // #4 replace underlying object
        //scoreQuery = GetScores(scores); #7

        Console.WriteLine("\n");
        foreach (int i in scoreQuery)
        {
            Console.Write(i + " "); // #5 outputs as #3 rather than 100, 200
        }

        Console.WriteLine("\n");
        foreach (var item in scores)
        {
            Console.Write(item + "\t"); // #6 sanity check scores is 100,200,3,5,6
        }
    }

    private static IEnumerable<int> GetScores(int[] scores)
    {
        Console.WriteLine("non-ref version");
        return from score in scores
               where score > 80
               select score;
    }

    private static IEnumerable<int> GetScores(ref int[] scores)
    {
        Console.WriteLine("ref version");
        return from score in scores
               where score > 80
               select score;
    }

重新创建查询(#7)会'修复'它,但不是我想要的。所以我认为我需要做的就是通过ref(#8)传递分数,但这没什么区别。

我哪里错了?我认为我对引用与包含它们的变量的理解存在脱节,或者这是否与LINQ查询的具体内容有关?

4 个答案:

答案 0 :(得分:0)

致电时

IEnumerable<int> scoreQuery = GetScores(scores);

scoreQuery实际上是对分数的引用。然后在线:

scores = new int[] { 100, 200, 3, 5, 6 }; 

你创建一个新数组,并替换存储在变量分数中的引用,但scoreQuery仍引用原始数组。

答案 1 :(得分:0)

你的错误不是LINQ,而是一般的OOP:
无论您如何将初始数组传递给GetScores,该对象始终由方法返回的IEnumerable<int>引用。稍后更改调用方法中的引用不会更改该其他引用。

答案 2 :(得分:0)

这句话:

  

我读过,每次迭代时LINQ查询都会重新执行,并且除非基础数据发生变化,否则它总是产生相同的输出。

指的是如果源对象中的数据发生更改,并且您重复查询,则新的迭代将反映源对象中的更改。它不是指用另一个对象更改源对象。

所有LINQ运算符都按值获取其参数。例如Select

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);

这些LINQ方法是查询语法使用的方法,因此将参数ref传递给您的方法没有任何区别,因为它们将通过值传递给基础LINQ方法。

答案 3 :(得分:0)

当你写这一行时:

scores = new int[] { 100, 200, 3, 5, 6 }; // #4 replace underlying object

您只需将scores重新引用到另一个 nothing 的数组,该数组与最初传递给GetScores方法的数组有关。这与LINQ无关,它是关于如何在传递给方法时处理引用类型(以及数组的内容)。

考虑一下:

var myValue = new MyObject { MyProperty = 1 };
DoSomething(myValue);

现在,当您重新分配myValue时,请执行以下操作:

myVale = new MyObject { MyProperty = 5 };

您希望DoSomething如何反映这一点?它不知道关于新实例的任何,因为它是用原始实例调用的。在您的代码中也会发生同样的情况:GetScores将根据传递给它的实例创建一个迭代器。创建新实例不会改变迭代器的工作方式。