开发者

Explain Linq Microsoft Select - Indexed [Example]

I'm running throuth Microsoft's 101 LINQ Samples, and I'm stumped on how this query knows how to assign the correct int value to the correct int field:

public void Linq12()
{
    int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

    var numsInPlace = numbers.Select((num, index) => new { Num = num, InPlace = (num == index) });

    Console.WriteLine("Number: In-place?");
    foreach (var n in numsInPlace)
    {
        Console.WriteLine("{0}: {1}", n.Num, n.InPlace);
    }
}

I saw in SO #336758 that there have been errors in the exampl开发者_开发百科es before, but it is much more likely that I am just missing something.

Could someone explain this and how the compiler knows how to interpret this data correctly?

EDIT:

OK, I think my confusion comes from the LINQ extension that enables the Select feature to work. The Func and two int parameters IEnumerable<TResult> IEnumerable<int>.Select(Func<int,int,TResult> selector) are most likely the key to my lack of understanding.

Explain Linq Microsoft Select - Indexed [Example]


I'm not really sure what you are asking of but the Select iterates over the list starting at index 0. If the value of the element at the current index is equal to the index it will set the InPlace property in the anonymous object to true. My guess is that the code above prints true for 3, 6 and 7, right?

It would also make it easier to explain if you write what you don't understand.

Jon Skeet has written a series of blog post where he implement linq, read about Select here: Reimplementation of Select

UPDATE: I noticed in one of your comment to one of the other comments and it seems like it is the lambda and not linq itself that is confusing you. If you read Skeet's blog post you see that Select has two overloads:

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

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

The Select with index matches the second overload. As you can see it is an extension of IEnumerable<TSource> which in your case is the list of ints and therefor you are calling the Select on an IEnumerable<int> and the signature of Select becomes: Select<int, TResult>(this IEnumerable<int> source, Func<int, int, TResult> selector). As you can see I changed TSource against int, since that is the generic type of your IEnumerable<int>. I still have TResult since you are using anonymous type. So that might explain some parts?


Looks correct to me.

First you have an anonymous type with Num and InPlace being created. Then the LINQ Select is just iterating over the elements with the element and the index of that element. If you were to rewrite it without linq and anonymous classes, it would look like this:

class NumsInPlace
{
    public int Num { get; set; }
    public bool InPlace { get; set; }
}
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

List<NumsInPlace> numsInPlace = new List<int>();
for (int index = 0; i < numbers.length; i++)
{
    int num = numers[index];
    numsInPlace.Add(new NumsInPlace() { Num = num, InPlace = (index == num) });
}

Console.WriteLine("Number: In-place?");
foreach (var n in numsInPlace)
{
    Console.WriteLine("{0}: {1}", n.Num, n.InPlace);
}

The MSDN on Enumerable.Select has the details, but the projection function (num, index) always has the item first, then the index second (if supplied).


how does the compiler know that LINQ Select is using the index value as an index to the array?

The compiler doesn't know about index values. The implementation of that overload of Select knows about index values.

//all the compiler sees is a method that accepts 2 int parameters and returns a bool.
Func<int, int, bool> twoIntFunc = (x, y) => (x == y);

//The compiler sees there's an overload of Enumerable.Select which accepts such a method.
IEnumerable<bool> query = numbers.Select(twoIntFunc);

Enumerable.Select's implementation does the rest of the work by calling that method with the appropriate parameters.

The first argument to selector represents the element to process. The second argument to selector represents the zero-based index of that element in the source sequence.

So - Select will call your method with (5, 0) first, then Select calls it with (4, 1).


The lambda expression (num, index) => new { Num = num, InPlace = (num == index) } is executed once for each element in the input sequence and passed the item and it's index as the arguments.

Update

Lambda expressions can be implicitly typed, that is, from the required type, the compiler can figure out (or imply) what types you intend the arguments to be.

(num, index) => new { Num = num, InPlace = (num == index) }

is equivalent to

someAnonymousType MyMethod(int num, int index)
{
    return new 
    {
        Num = num,
        InPlace = (num == index)
    };
}

obviously you can't write the latter because you can't type the name of an anonymous type, but the compiler can ;)

The compiler knows this because the overload of Select that you're using accepts a Func<TSource, Int32, TResult>, this is a Func that takes, two arguments of type TSource (the type of your IEnumberable<T>, in this case int) and an Int32 (which represents the index) and returns an object of TResult, being whatever you choose to return from your function, in this case, an anonymous type.

The lambda can be cast to the required type and therefore it just works.


The second argument in the select is the index, which increments as the compiler traverses the array of numbers. The compiler will see

num index   num = index
5   0   false
4   1   false
1   2   false
3   3   true
9   4   false
8   5   false
6   6   true
7   7   true
2   8   false
0   9   false


It provides true for 3,6 and 7. You need to remember it starts with the index at 0.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜