【问题标题】:LINQ where clause, filter on boolean(grandchild) inside array(child) that's inside array(parent)LINQ where 子句,在数组(父)内的数组(子)内过滤布尔值(孙子)
【发布时间】:2017-05-19 18:26:58
【问题描述】:

如果这是重复的,请告诉我,我到处搜索。这可能只是一个我不熟悉的简单 LINQ 概念。


这是我的数据结构的简化版:

public interface ICourse
{
  List<ISession> Sessions { get; set; }
}

public interface ISession
{
  Boolean InRange { get; set; }
}

问题:我有一个List&lt;ICourse&gt;。我想返回所有课程 (ICourse),但过滤这些课程上的会话 (ISession),并且只包括 InRange (true) 的会话。

试过了:

List<ICourse> results = //data retrieval.
return results.Where(course => course.Sessions
              .Where(session => session.InRange).ToList<ISession>())
              .ToList<ICourse>();

错误: 无法将类型 'System.Collections.Generic.List&lt;ISession&gt;' 隐式转换为 'bool'

无法将 lambda 表达式转换为预期的委托类型,因为块中的某些返回类型不能隐式转换为委托返回类型。

显然,当我只使用一个Where 子句时它不会抱怨,即:

return results.Where(course => course.Sessions.Count > 0).ToList<ICourse>();

【问题讨论】:

    标签: c# arrays linq


    【解决方案1】:
    results
      .Where(course => 
        course.Sessions.Where(session => session.InRange).ToList<ISession>()
      )
      .ToList<ICourse>();
    

    Where() 是一个过滤器。它需要一个返回 bool 的 lambda。 Where 依次给你的 lambda 枚举中的每个项目,并且 lambda 应该回答一个是或否的问题:“你想在结果中包含这个东西吗?”

    这是您试图对该问题给出的答案:

      course.Sessions.Where(session => session.InRange).ToList<ISession>()
    

    返回ISession 的列表。 Where 询问你的 lambda,“你想要这个东西吗?”而不是“是”或“否”,您的 lambda 会说:“嘿,看看所有这些会话!”在 C# 中,这不是一个是或否的答案。

    编译器在一英里外看到这个喜剧例程,并在运行时发生任何愚蠢的事情之前拔掉插头。

    所以。

    如果您想要返回ICourse 的列表,但您希望他们只拥有他们开始的会话的子集,那么您不能在不创建新课程对象的情况下执行此操作。由于您在这里得到的是接口,所以我们不要尝试创建新对象。我怀疑你无论如何都想要。

    您可以获得InRange 的所有会话的平面列表:

    results.SelectMany(c => c.Sessions.Where(s => s.InRange)).ToList();
    

    您可以获得一个元组列表或匿名类型,它将对 ICourse 的引用与属于该课程的范围内会话列表配对:

    results.Select(c => 
        new Tuple<ICourse, List<ISession>>(
            c, 
            c.Sessions.Where(session => session.InRange).ToList()
        ))
    .ToList();
    

    您可以对其进行过滤以排除任何没有范围内会话的课程:

    results.Select(c => 
        new Tuple<ICourse, List<ISession>>(
            c, 
            c.Sessions.Where(session => session.InRange).ToList()
        ))
    .Where(t => t.Item2.Any())
    .ToList();
    

    如果这不只是在您的应用程序的某个小角落里临时发生的事情,我会敦促您编写一个自定义类来替换 Tuple

    或者在 C#7 中:

    public List<(ICourse Course, List<ISession> Sessions)> 
        GetInRangeSessions(IEnumerable<ICourse> courses) 
        =>
            courses
            .Select(c =>
                (Course: c, Sessions: c.Sessions.Where(session => session.InRange).ToList())
            )
            .ToList();
    

    只需在这种语言中添加一些烦人的多余括号,我们就可以挖掘小行星了。

    【讨论】:

    • 非常感谢您完整、精彩的回答。在您的帮助下,我能够使我的代码工作(很好)并扩大我对 LINQ 的理解(甚至更好)。
    【解决方案2】:

    Ed Plunkett 的回答是正确的,但我必须问,你为什么想要一个新的ICourse 对象列表。为什么不只使用完整的 ICourse 列表,而只过滤正在使用它们的会话:

     foreach(var c in Courses)
     {
          foreach(var s in c.Sessions.Where(session => session.InRange))
          {.... }
     }
    

    【讨论】:

      【解决方案3】:

      我认为最简单的想法就是写:

      List<Sessions> temporary = new List<Sessions>();
      
              foreach (Course s in courses)
              {
                  temporary = s.sessions.Where(x => x.InRange == true).ToList();
                  s.sessions = temporary;
                  temporary.Clear();
              }
      

      【讨论】:

        【解决方案4】:

        如果您想要所有课程,其中会话为 InRange,我会尝试...(未测试)

        List<ICourse> results = //data retrieval.
        return results.Where(course => course.Sessions.Where(session => session.InRange)).ToList<ISession>()).ToList<ICourse>();
        

        【讨论】:

        • 您的括号不平衡,因此很难准确判断您要在这里做什么(您有四个开头的( 和五个结尾的))。即使除此之外,我也不相信您将括号放在任何地方都可以让您返回课程,并对这些课程的会话进行适当的过滤......
        • 这无法编译并出现 OP 获取的相同错误。提交一个你还没有测试过的“敏捷”答案是可以的,但是你回去测试一下。
        猜你喜欢
        • 1970-01-01
        • 2015-04-10
        • 1970-01-01
        • 2019-06-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-24
        • 1970-01-01
        相关资源
        最近更新 更多