简体   繁体   中英

Different ways of using SelectMany()

I'd like to know how to use SelectMany() . It seems to take so many arguments and from my own research I noticed that SelectMany() might be the 'father' of all other select operations.

Select many allows you to select a property from your query source that is an IEnumerable<T> collection, but instead of returning a collection of collections (IEnumerable<IEnumerable<T>>) it will flatten the collections into a single collection.

Here's an example that you can run to demonstrate the differences between Select and SelectMany:

//set up some data for our example
var tuple1 = new { Name = "Tuple1", Values = new int [] { 1, 2, 3 } };
var tuple2 = new { Name = "Tuple2", Values = new int [] { 4, 5, 6 } };
var tuple3 = new { Name = "Tuple3", Values = new int [] { 7, 8, 9 } };

//put the tuples into a collection
var tuples = new [] { tuple1, tuple2, tuple3 };

//"tupleValues" is an IEnumerable<IEnumerable<int>> that contains { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }
var tupleValues = tuples.Select(t => t.Values);

//"tupleSelectManyValues" is an IEnumerable<int> that contains { 1, 2, 3, 4, 5, 6, 7, 8, 9 }
var tupleSelectManyValues = tuples.SelectMany(t => t.Values);

By using SelectMany you make it easier to query values within child collections.

There are several overloads to SelectMany . One of them allows you to keep track of any relationship between parent and children while traversing the hierarchy.

Example : suppose you have the following structure: League -> Teams -> Player

You can easily return a flat collection of players. However you may loose any reference to the team a player is part of.

Fortunately there is an overload for such purpose:

var teamsAndTheirLeagues = 
         from helper in leagues.SelectMany
               ( l => l.Teams
                 , ( league, team ) => new { league, team } )
                      where helper.team.Players.Count > 2 
                           && helper.league.Teams.Count < 10
                           select new 
                                  { LeagueID = helper.league.ID
                                    , Team = helper.team 
                                   };

The previous example is taken from Dan's IK blog:

http://blogs.interknowlogy.com/2008/10/10/use-linqs-selectmany-method-to-flatten-collections/

I strongly recommend you take a look at it.

SelectMany basically flattens and processes hierarchical data, and has two main forms

(for the purposes of examples, see this initial code)

class TestObj
{
    public string Name { get; set; }
    public List<string> Items { get; set; }
}

var hierarchicalCollection = new List<TestObj>();

hierarchicalCollection.Add(new TestObj() 
    {Items = new List<string>()
        {"testObj1-Item1", "testObj1-Item2"}, Name="t1"});
hierarchicalCollection.Add(new TestObj() 
    {Items = new List<string>()
        {"testObj2-Item1", "testObj2-Item2"}, Name="t2"});

option 1) creates a collection from a collection of collections (essentially flattening hierarchical data)

IEnumerable<string> flattenedCollection = 
    hierarchicalCollection.SelectMany(t => t.Items);

The result is:

"testObj1-Item1"
"testObj1-Item2"
"testObj2-Item1"
"testObj2-Item2"

option 2) creates a collection from a collection of collections, and then processes each item of the new collection via a reference to the original parent

IEnumerable<string> flattenedModifiedCollection = 
    hierarchicalCollection.SelectMany
        (t => t.Items, (t, i) => t.Name + " : " + i);

the result is:

"t1 : testObj1-Item1"
"t1 : testObj1-Item2"
"t2 : testObj2-Item1"
"t2 : testObj2-Item2"

each of the above useages has a variant, where the index of the item being processed is available to the transformation functions.

I use this extension all the time for diving into hierarchies.

Another cool way to do this when the Extensions get a bit messy is to use the formal LINQ way, like:

var vehicles = from cust in context.Customers
               from fleet in cust.Fleets
               from v in fleet.Vehicles
               select v;

This would be the equivalent of:

var vehicles = context.Customers.SelectMany(c => c.Fleets).SelectMany(f => f.Vehicles);

This can get a bit long winded when adding in where clauses and joins etc. Hope this helps!

Here is another (VB.NET) usage example:

'Original list
Dim l() As String = {"/d", "/bc:\Temp\In*;c:\Temp\Out", "/hABC", "/s123"}

'Processed list: will list first 2 characters from each string member.
Dim L1 As IEnumerable(Of String) = l.SelectMany(Function(x As String) {x.Substring(0, 2)})

Dim L2 As List(Of String) = l.SelectMany(Function(x As String) {x.Substring(0, 2)}).ToList

'Will return dictionary like list with keys==2 characters and values the rest from each string member.
Dim L3 As List(Of KeyValuePair(Of String, String)) = l.SelectMany(Function(x As String) {New KeyValuePair(Of String, String)(x.Substring(0, 2), x.Substring(2))}).ToList

I have had some fun using SelectMany in LINQ. The following link described returning an IEnumerable in a LINQ select clause, which returns a sequence of sequences, and using SelectMany to flatten that into a simple sequence. "Linq to XML using Let, Yield return and Selectmany" . It is not just a SelectMany use case, but part of an approach which generates multiple outputs from a single input in LINQ.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM