Joe Bauer
Joe Bauer

Reputation: 622

Linq query to select single string from multiple List<string>

I am having a hard time understanding why I am getting the results that I am.

I have two lists of strings:

var list1 = new List<String> {"item 1", "item 2"};
var list2 = new List<String> { "item 3", "item 4" };

Version 1 - Output: "item 2"

var item =
            from x in (list1.Concat(list2))
            where x.EndsWith("2")
            select x;

        Console.WriteLine(item.First());

Version 2 - Output: "i"

var item =
            from x in (list1.Concat(list2))
            where x.EndsWith("2")
            select x.First();

        Console.WriteLine(item.First());

Version 3 - Output: "System.Linq.Enumerable+WhereEnumerableIterator`1[System.String]"

var item =
            from x in (list1.Concat(list2))
            where x.EndsWith("2")
            select x;

        Console.WriteLine(item);

Given that version 2 outputs "i", I would expect version 3 to output "item 2". Why is this behavior occurring?

Upvotes: 4

Views: 5279

Answers (6)

FRL
FRL

Reputation: 776

//This raise a exception if no item found
var item=list1.Concat(list2).First(i => i.EndsWith("2"));
//this return default value (null) if no item found
var item2 = list1.Concat(list2).FirstOrDefault(i => i.EndsWith("2"));

Upvotes: 0

Rex M
Rex M

Reputation: 144122

In version 3, select x is returning a sequence of strings that match your critera; it just happens to be a sequence with one item in it.

Console.WriteLine internally calls .ToString() on whatever you pass into it. Since there is no meaningful string representation for IEnumerable<T>, the default in .NET is to print the string name of the type.

Based on your wording, I think part of your confusion does come from a misunderstanding of why version 2 works the way it does. In version 2, select x.First() is actually a bit of a quirk/coincidence, because a string is also an IEnumerable<char>, so you can do LINQ operations on the string. .First() returns the first element of that char sequence, for each result that matches your criteria. So you're saying:

"For each element which matches my criteria, select the first character, and then return the sequence of all the first characters for the matches."

So in fact, item in version 2 is an IEnumerable<char> with one element in it. Calling Console.WriteLine() on an IEnumerable<char> will just print the chars in order. So you get "i".

(note: I see after I answered this, the question was edited to call .First() both inside the projection and on the result, so the bit about passing IEnumerable<char> to Console.WriteLine isn't totally relevant anymore)

Keep in mind LINQ is pretty much about working with sets until you explicitly reduce them down. For example, Select is simply a projection or transformation. It returns the same number of items that were passed to it, transformed. Where reduces down the set, but it's still a set.

Upvotes: 5

Habib
Habib

Reputation: 223267

Your Version 2 is selecting first item/char from the string x.First(), whereas your first version is selecting first item from the result set-> First string.

Version 1 is like - Select First Item from the Result Set

var item =  (from x in (list1.Concat(list2))
            where x.EndsWith("2")
            select x).First(); //First complete string will be selected

and version 2 is like- Select First Item from a string in result set

var item =  from x in (list1.Concat(list2))
            where x.EndsWith("2")
            select x.First(); //only first char will be selected for string

Your third case is selecting an IEnumerable<string>, so when you call Console.WriteLine, it calls the default implementation of ToString and thus you get

"System.Linq.Enumerable+WhereEnumerableIterator`1[System.String]"

Upvotes: 2

zs2020
zs2020

Reputation: 54524

The type of the x in your Version 2 is String. The type of item in Version 1 is IEnumerable.

So your Version 2 return a list of characters which are the first characters of each string. In your Version 1, item.First() return the first element of the result set which is a string.

Upvotes: 0

BradleyDotNET
BradleyDotNET

Reputation: 61349

Because Where returns an IEnumerable.

You have written the equivalent of:

var whoKnows = list1.Concat(list2).Where(x => x.EndsWith("2"));
Console.WriteLine(whoKnows);

The ToString of a collection just returns the class name.

Upvotes: 0

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149538

When using First() you are materializing the list, causing the iterator to execute. That is eager execution. Your third version uses select which does not materialize the list abd uses Defeered Execution which returns an iterator, hence calling ToString() on it returning the iterators name

Upvotes: 0

Related Questions