Can I select multiple objects in a Linq query

后端 未结 7 442
有刺的猬
有刺的猬 2021-02-02 06:48

Can I return more than one item in a select? For instance I have a List of Fixtures (think football (or soccer for the yanks) fixtures). Each fixture contains a home and away t

7条回答
  •  迷失自我
    2021-02-02 07:45

    The following will return an IEnumerable:

    IEnumerable drew =
        from fixture in fixtures
        where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
        from team in new[]{fixture.HomeTeam, fixture.AwayTeam}
        select team;
    

    Or, with the fluent style of LINQ:

    IEnumerable drew =
        fixtures
        .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore))
        .SelectMany(fixture => new[]{fixture.HomeTeam, fixture.AwayTeam});
    

    Flattening and FlatMap

    This requirement is often called 'flattening'. That is, taking a > and converting it to a .

    SelectMany both maps (a fixture to an Array of Teams) and flattens (a sequence of Team Arrays to a sequence of Teams). It is similar to the "flatMap" function in other languages such as Java and JavaScript.

    It is possible to separate the Mapping and the Flattening:

    IEnumerable drew =
        fixtures
        .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore))
        // map 
        .Select(fixture => new[]{fixture.HomeTeam, fixture.AwayTeam})
        // flatten
        .SelectMany(teams => teams);
    

    Other Approaches

    Iterator Block

    The same can be achieved with an iterator block, but I suspect this is rarely the best approach:

    IEnumerable Drew(IEnumerable fixtures){
        var draws = 
          fixtures
          .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore));
    
        foreach(var fixture in draws){
            yield return fixture.HomeTeam;
            yield return fixture.AwayTeam;
        }
    }
    

    Union

    Union is also an option but has the potential to produce different results from the above:

    1. The order of results will be different. All Home results are returned then all Away results.

    2. Union enumerates fixtures twice, so, depending on how fixtures is implemented, there is the potential for fixtures to be updated between calls. E.g., if a new drawn fixture were added between calls then the Away team could be returned but not the Home team.

    As Mike Powell describes:

    IEnumerable drew =
        ( from fixture in fixtures
          where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
          select fixture.HomeTeam
        ).Union(
          from fixture in fixtures
          where fixture.Played  && (fixture.HomeScore == fixture.AwayScore)
          select fixture.AwayTeam );
    

    Depending on how fixtures is sourced/implemented it may be worth considering 'caching' the drawn fixtures to avoid having to enumerate fixtures twice.

    var draws = 
        ( from fixture in fixtures
          where fixture.Played  && (fixture.HomeScore == fixture.AwayScore)
          select fixture
        ).ToList();
    
    IEnumerable drew =
        (from draw in draws select draw.HomeTeam)
        .Union(from draw in draws select draw.AwayTeam);
    

    Or using the fluent style:

    var draws = 
        fixtures
        .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore))
        .ToList();
    
    IEnumerable drew =
        draws.Select(fixture => fixture.HomeTeam)
        .Union(draws.Select(fixture => fixture.AwayTeam));
    

    Modifying the Fixture class

    One could consider adding "ParticipatingTeams" to the Fixture class to get:

    IEnumerable drew =
        from fixture in fixtures
        where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
        from team in fixture.ParticipatingTeams
        select team;
    

    but as @MattDeKrey points out that requires a contract change.

    Code Samples

    Code samples are available on Repl.it

提交回复
热议问题