【问题标题】:Why SelectMany needs the one of M<S> -> (S -> M<C>) -> (S -> M<C> -> R) -> E<R>?为什么 SelectMany 需要 M<S> -> (S -> M<C>) -> (S -> M<C> -> R) -> E<R> 之一?
【发布时间】:2017-06-05 04:01:24
【问题描述】:

链接https://msdn.microsoft.com/en-us/library/bb534631(v=vs.110).aspx中,第三个签名是

// M<S> -> (S -> M<C>) -> (S -> M<C> -> R) -> E<R>
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, IEnumerable<TCollection>> collectionSelector,
    Func<TSource, TCollection, TResult> resultSelector
)

第一个具有典型 Monad 绑定函数 M&lt;A&gt; -&gt; (A -&gt; M&lt;B&gt;) -&gt; M&lt;B&gt; 签名的函数不应该足够吗?将resultSelector中的代码合并到collectionSelector中是不是很容易?

MSDN文档给出了一个例子来说明该方法的使用。

class Program
{
    static void Main(string[] args)
    {
        SelectManyEx3();
    }
    public static void SelectManyEx3()
    {
        PetOwner[] petOwners =
            { new PetOwner { Name="Higa",
                    Pets = new List<string>{ "Scruffy", "Sam" } },
                new PetOwner { Name="Ashkenazi",
                    Pets = new List<string>{ "Walker", "Sugar" } },
                new PetOwner { Name="Price",
                    Pets = new List<string>{ "Scratches", "Diesel" } },
                new PetOwner { Name="Hines",
                    Pets = new List<string>{ "Dusty" } } };

        // Project the pet owner's name and the pet's name.
        var query =
            petOwners
            .SelectMany(petOwner => petOwner.Pets, (petOwner, petName) => new { petOwner, petName })
            .Where(ownerAndPet => ownerAndPet.petName.StartsWith("S"))
            .Select(ownerAndPet =>
                    new
                    {
                        Owner = ownerAndPet.petOwner.Name,
                        Pet = ownerAndPet.petName
                    }
            );

        // Print the results.
        foreach (var obj in query1)
        {
            Console.WriteLine(obj);
        }
    }
}

class PetOwner
{
    public string Name { get; set; }
    public List<string> Pets { get; set; }
}

但是,var query = ... 可以使用第一个签名的SelectMany 重写如下?

var query = petOwners.SelectMany(o => o.Pets.Select(p => new { petOwner = o, petName =p}))

带有第三个签名的SelectMany 何时真正有用?

【问题讨论】:

  • 我认为第三个签名示例比创建 LINQ 子表达式更易于理解和简洁。
  • 有例子吗?

标签: c# monads


【解决方案1】:

如果他们使用下面的示例会更有帮助。看看我是如何在SelectMany 中添加Where 子句的。这种重载让您可以灵活地运行查询,然后对该查询的结果进行投影:

// Project the pet owner's name and the pet's name.
var query =
    petOwners
    .SelectMany(petOwner => petOwner.Pets.Where(x => x.StartsWith("S")), (petOwner, petName) => new { petOwner, petName })
    //.Where(ownerAndPet => ownerAndPet.petName.StartsWith("S"))
    .Select(ownerAndPet =>
            new
            {
                Owner = ownerAndPet.petOwner.Name,
                Pet = ownerAndPet.petName
            }
    );

【讨论】:

  • 要使用第一个签名,可以改写为petOwners .SelectMany(petOwner =&gt; petOwner.Pets.Where(x =&gt; x.StartsWith("S")).Select(petName =&gt; new {petOwner, petName}).Select(ownerAndPet =&gt; ....?
  • @dc7a9163d9 当然可以。但为了方便起见,您可以使用相同的方法对结果进行投影。如果您认为它做了一些无法以不同方式重写的事情,那么它不会。
【解决方案2】:

SelectMany( ) 方法的第 3 和第 4 个重载以及附加的“结果选择器”参数需要更多解释。

额外的选择器参数试图帮助您了解和理解父集合和子集合之间的关系。代码比较简洁,涉及到关系。

结果选择器是查询范围内可用的中间对象,可为您提供所需的信息,由您决定结果选择器中需要哪些数据来帮助您。

假设您需要一个结果集合,它不仅包含符合所有联赛中某些标准的球队的完整列​​表,还需要知道球队所在的联赛。

例如:

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 };

此语法使您能够在 where 子句中同时查询球队和联赛,并且您不会“丢失”父子关系的反向连接。

更多关于selectMany投影的详细解释可以看herehere

【讨论】:

  • 这个例子好像可以改写成var teamsAndTheirLeagues = from helper in leagues .SelectMany( l =&gt; l.Teams.Select(x =&gt; new {league = l, team = x}) where ....?
  • 是的,但这也是额外的输入。第三个签名更简洁,节省了额外的输入。
猜你喜欢
  • 1970-01-01
  • 2015-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-21
  • 2019-07-14
相关资源
最近更新 更多